commit 940b8d9429ec06eda3d8398050569d4728024815 Author: 1147192855@qq.com <1147192855@qq.com> Date: Mon Feb 19 13:55:05 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abbafd6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,186 @@ +# Miscellaneous +./marking_app/*.class +./marking_app/*.log +./marking_app/*.pyc +./marking_app/*.swp +./marking_app/.DS_Store +./marking_app/.atom/ +./marking_app/.buildlog/ +./marking_app/.history +./marking_app/.svn/ +./marking_app/migrate_working_dir/ +./marking_app/**/*.g.dart +./marking_app/*.g.dart +./marking_app/*/*.g.dart +./marking_app/.dart_tool/* +./marking_app/.fvm/* +./marking_app/.fvm/fvm_config.json +./marking_app/.fvm/flutter_sdk/* + +# IntelliJ related +./marking_app/*.iml +./marking_app/*.ipr +./marking_app/*.iws +./marking_app/.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +./marking_app/**/doc/api/ +./marking_app/**/ios/Flutter/.last_build_id +./marking_app/.dart_tool/ +./marking_app/.flutter-plugins +./marking_app/.flutter-plugins-dependencies +./marking_app/.packages +./marking_app/.pub-cache/ +./marking_app/.pub/ +./marking_app/build/ +./marking_app/pubspec.lock + +# Web related +./marking_app/lib/generated_plugin_registrant.dart + +# Symbolication related +./marking_app/app.*.symbols + +# Obfuscation related +./marking_app/app.*.map.json + +# Android Studio will place build artifacts here +./marking_app/android/app/debug +./marking_app/android/app/profile +./marking_app/android/app/release + + + +*/*.g.dart +marking_app/lib/common/model/marking/marking_submit_result.g.dart +marking_app/lib/common/model/marking/progress_of_marking.g.dart +marking_app/lib/utils/image/gallery_example_item_model.g.dart +marking_app/macos/Flutter/GeneratedPluginRegistrant.swift +marking_app/pubspec.lock +marking_app/lib/common/model/common/base_page.g.dart +marking_app/lib/common/model/common/base_page_data.g.dart +marking_app/lib/common/model/common/base_structure_result.g.dart +marking_app/lib/common/model/marking/current_review_task.g.dart +marking_app/lib/common/model/marking/marking_item.g.dart +marking_app/lib/common/model/marking/marking_list_params.g.dart +marking_app/lib/common/model/marking/marking_text_question.g.dart +marking_app/lib/common/model/marking/marking_text_question_params.g.dart +marking_app/lib/common/model/marking/submit_exam_params.g.dart +marking_app/lib/common/model/marking/submit_exam_small_params.g.dart +marking_app/lib/common/model/progress/progress_item.g.dart +marking_app/lib/common/model/progress/progress_page_params.g.dart +marking_app/lib/common/model/progress/progress_statistics.g.dart +marking_app/lib/common/model/review/additional_conditions_for_review.g.dart +marking_app/lib/common/model/review/review_item.g.dart +marking_app/lib/common/model/review/review_page_params.g.dart +marking_app/lib/common/model/review/review_tab.g.dart +marking_app/lib/common/model/sys/system_version.g.dart +marking_app/lib/common/model/user/user_info.g.dart +marking_app/lib/common/model/user/user_login.g.dart +marking_app/lib/common/model/user/user_login_params.g.dart +marking_app/lib/utils/request/rest_client.g.dart +marking_app/windows/flutter/generated_plugin_registrant.cc +marking_app/linux/flutter/generated_plugin_registrant.cc +marking_app/linux/flutter/generated_plugin_registrant.h +marking_app/linux/flutter/generated_plugins.cmake +marking_app/windows/flutter/generated_plugin_registrant.h +marking_app/windows/flutter/generated_plugins.cmake +marking_app/lib/common/model/marking/do_marking_keyboard_model.g.dart +marking_app/lib/common/model/common/upload_img_secret_key.g.dart +marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.g.dart +marking_app/lib/common/model/marking/marking_text_question_tab_params.g.dart +marking_app/lib/common/model/marking/marking_tag_single_params.g.dart +marking_app/lib/common/model/marking/marking_text_question_tab.g.dart +marking_app/lib/common/model/marking/marking_text_question_tab_step_size.g.dart +marking_app/lib/utils/request/rest_client_report.g.dart +marking_app/lib/pages/reports/services/report_home_services.g.dart +marking_app/lib/pages/reports/index.g.dart +marking_app/lib/common/model/user/user_info_report.g.dart +marking_app/lib/common/model/report/marked_item_params.g.dart +marking_app/lib/common/model/report/marked_item.g.dart +marking_app/lib/common/model/common/base_structure_result_report.g.dart +marking_app/lib/common/model/common/base_page_data_report.g.dart +marking_app/lib/common/model/common/base_page_report.g.dart +marking_app/lib/pages/reports/report_personal_subject.g.dart +marking_app/lib/common/model/report/report_for_class_teacher_params.g.dart +marking_app/lib/common/model/report/report_for_marking_pagerdetail_model.g.dart +marking_app/lib/common/model/report/report_for_marking_pagerdetail_params.g.dart +marking_app/lib/common/model/report/report_for_subject_student_model.g.dart +marking_app/lib/common/model/report/report_for_subject_student_params.g.dart +marking_app/lib/common/model/report/report_for_subject_teacher_model.g.dart +marking_app/lib/common/model/report/report_for_subject_teacher_params.g.dart +marking_app/lib/common/model/report/report_home_model.g.dart +marking_app/lib/common/model/report/report_for_class_teacher_model.g.dart +marking_app/lib/pages/reports/report_class_teacher.g.dart +marking_app/lib/pages/reports/report_subject_teacher.g.dart +marking_app/lib/common/model/report/radar_chart_model.g.dart +marking_app/lib/common/model/report/report_histogram_model.g.dart +marking_app/lib/common/model/marking/annotation_graffiti_switch.g.dart +marking_app/lib/common/model/marking/review_records_item.g.dart +marking_app/lib/common/model/marking/review_records_params.g.dart +marking_app/lib/components/marking/review_records_view.g.dart + + + + +marking_app/lib/common/model/event_bus/job_submit_bus.g.dart +marking_app/lib/common/model/event_bus/large_question_scoring_bus.g.dart +marking_app/lib/common/model/event_bus/question_switch_bus.g.dart +marking_app/lib/common/model/job/marking_text_question_job.g.dart +marking_app/lib/common/model/job/marking_text_question_job_params.g.dart +marking_app/lib/common/model/job/marking_text_question_job_tab_params.g.dart +marking_app/lib/pages/home/index.g.dart +marking_app/lib/pages/homework_correction/components/setPreference.g.dart +marking_app/lib/pages/homework_correction/do_papers.g.dart + +marking_app/lib/common/model/event_bus/job_currenttab_finish_addtion.g.dart +marking_app/lib/common/model/event_bus/job_large_question_scoring_bus.g.dart +marking_app/lib/common/model/event_bus/job_question_switch_bus.g.dart +marking_app/lib/common/model/job/review_again_list_params.g.dart +marking_app/lib/pages/homework_correction/components/keyboardBox.g.dart +marking_app/lib/components/PictureOverview.g.dart +marking_app/lib/common/model/marking/marking_abnormal_res.g.dart +marking_app/lib/common/model/marking/marking_statistics.g.dart +marking_app/lib/common/model/marking/submit_exam_abnormal_params.g.dart +marking_app/lib/pages/marking/do_papers.g.dart +marking_app/lib/common/model/job/test_questions_image_info.g.dart +marking_app/.fvm/fvm_config.json +marking_app/.fvm/flutter_sdk +marking_app/lib/common/model/job/upload_file_interface_config_params.g.dart +marking_app/lib/common/model/job/upload_file_interface_config.g.dart +marking_app/lib/common/model/marking/marking_zoom.g.dart +marking_app/lib/pages/homework_correction/components/jobPictureOverview.g.dart +marking_app/lib/pages/homework_correction/components/theExamPaperDrawing.g.dart +marking_app/lib/common/model/job/job_note_taking_trajectory.g.dart +marking_app/lib/pages/homework_correction/components/trajectoryView.g.dart +marking_app/lib/common/model/marking/marking_keyboard_sliding_position.g.dart +marking_app/lib/common/model/marking/rating_progress_model.g.dart +marking_app/lib/common/model/marking/marking_common_score_items.g.dart +marking_app/lib/components/marking/marking_seting.g.dart +marking_app/lib/common/model/marking/switch_keyboard_to_reload_images.g.dart +marking_app/lib/pages/homework_correction/components/homework_tasks_view_item.g.dart +marking_app/lib/pages/homework_correction/components/new_version_of_homework/homework_tasks_view_item.g.dart +marking_app/lib/common/model/event_bus/jobs/job_do_papers_switch_operation_bus.g.dart +marking_app/lib/pages/homework_correction/do_papers_job.g.dart +marking_app/lib/common/model/event_bus/jobs/job_do_papers_student_bus.g.dart +marking_app/lib/common/model/job/job_concerned_with_student_params.g.dart +marking_app/lib/common/model/job/job_concerned_with_student.g.dart +marking_app/lib/common/model/job/job_page_tab.g.dart +marking_app/lib/common/model/job/job_review_submission.g.dart +marking_app/lib/common/model/job/job_task_item.g.dart +marking_app/lib/pages/homework_correction/index.g.dart +marking_app/lib/pages/homework_correction/eventBus/marking_text_question_job_tab_params_bus.g.dart +marking_app/lib/pages/homework_correction/index.g.dart +marking_app/lib/pages/homework_correction/index.g.dart +marking_app/lib/pages/homework_correction/eventBus/job_check_switching_question_tab_bus.g.dart +marking_app/lib/common/model/job/job_collect_params.g.dart +marking_app/lib/pages/homework_correction/eventBus/do_papers_job_custom_paint_size_bus.g.dart +marking_app/lib/pages/homework_correction/eventBus/do_papers_job_refresh_bus.g.dart +marking_app/lib/pages/homework_correction/eventBus/job_do_papers_switch_operation_sub_bus.g.dart +marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.g.dart +marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.g.dart diff --git a/marking_app/.gitignore b/marking_app/.gitignore new file mode 100644 index 0000000..a8e938c --- /dev/null +++ b/marking_app/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/marking_app/.metadata b/marking_app/.metadata new file mode 100644 index 0000000..95f73f5 --- /dev/null +++ b/marking_app/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: android + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: ios + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: linux + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: macos + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: web + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + - platform: windows + create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/marking_app/.prettierrc b/marking_app/.prettierrc new file mode 100644 index 0000000..c415223 --- /dev/null +++ b/marking_app/.prettierrc @@ -0,0 +1 @@ +{ "semi": false, "singleQuote": true, "printWidth": 800 } diff --git a/marking_app/README.md b/marking_app/README.md new file mode 100644 index 0000000..0b70d96 --- /dev/null +++ b/marking_app/README.md @@ -0,0 +1,16 @@ +# marking_app + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/marking_app/android/.gitignore b/marking_app/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/marking_app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/marking_app/android/app/build.gradle b/marking_app/android/app/build.gradle new file mode 100644 index 0000000..6a8595d --- /dev/null +++ b/marking_app/android/app/build.gradle @@ -0,0 +1,97 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +def keystorePropertiesFile = rootProject.file("key.properties") +def keystoreProperties = new Properties() +keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + +android { + compileSdkVersion 33 + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + signingConfigs { + + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + debug { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.marking_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + // minSdkVersion flutter.minSdkVersion + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled false //删除无用代码 + shrinkResources false //删除无用资源 + } + debug { + signingConfig signingConfigs.debug + minifyEnabled false //删除无用代码 + shrinkResources false //删除无用资源 + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/marking_app/android/app/src/debug/AndroidManifest.xml b/marking_app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..ffb04b7 --- /dev/null +++ b/marking_app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/marking_app/android/app/src/main/AndroidManifest.xml b/marking_app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5dc79c3 --- /dev/null +++ b/marking_app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/android/app/src/main/kotlin/com/example/marking_app/MainActivity.kt b/marking_app/android/app/src/main/kotlin/com/example/marking_app/MainActivity.kt new file mode 100644 index 0000000..d3518dc --- /dev/null +++ b/marking_app/android/app/src/main/kotlin/com/example/marking_app/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.marking_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/marking_app/android/app/src/main/res/drawable-v21/launch_background.xml b/marking_app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..0206865 --- /dev/null +++ b/marking_app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/marking_app/android/app/src/main/res/drawable/launch_background.xml b/marking_app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..e2440a0 --- /dev/null +++ b/marking_app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/marking_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/marking_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..07a3b1b Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-hdpi/launch_image.png b/marking_app/android/app/src/main/res/mipmap-hdpi/launch_image.png new file mode 100644 index 0000000..34d1cb9 Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-hdpi/launch_image.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/marking_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..a3dfd04 Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-mdpi/launch_image.png b/marking_app/android/app/src/main/res/mipmap-mdpi/launch_image.png new file mode 100644 index 0000000..5d037ff Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-mdpi/launch_image.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/marking_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..982c945 Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-xhdpi/launch_image.png b/marking_app/android/app/src/main/res/mipmap-xhdpi/launch_image.png new file mode 100644 index 0000000..d3780f4 Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-xhdpi/launch_image.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/marking_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..7a169ff Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-xxhdpi/launch_image.png b/marking_app/android/app/src/main/res/mipmap-xxhdpi/launch_image.png new file mode 100644 index 0000000..1ae01f1 Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-xxhdpi/launch_image.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/marking_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..559ec6a Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/marking_app/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png b/marking_app/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png new file mode 100644 index 0000000..6684cdc Binary files /dev/null and b/marking_app/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png differ diff --git a/marking_app/android/app/src/main/res/values-night/styles.xml b/marking_app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/marking_app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/marking_app/android/app/src/main/res/values/styles.xml b/marking_app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..fdcd67e --- /dev/null +++ b/marking_app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/marking_app/android/app/src/profile/AndroidManifest.xml b/marking_app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..6281e88 --- /dev/null +++ b/marking_app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/android/build.gradle b/marking_app/android/build.gradle new file mode 100644 index 0000000..d55b317 --- /dev/null +++ b/marking_app/android/build.gradle @@ -0,0 +1,40 @@ +buildscript { + ext.kotlin_version = '1.7.0' + repositories { + // google() + // mavenCentral() + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/jcenter' } + maven { url 'https://maven.aliyun.com/nexus/content/groups/public'} + maven { url 'https://maven.aliyun.com/repository/gradle-plugin'} + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + // google() + // mavenCentral() + + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://maven.aliyun.com/repository/jcenter' } + maven { url 'https://maven.aliyun.com/nexus/content/groups/public'} + maven { url 'https://maven.aliyun.com/repository/gradle-plugin'} + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/marking_app/android/gradle.properties b/marking_app/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/marking_app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/marking_app/android/gradle/wrapper/gradle-wrapper.properties b/marking_app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cc5527d --- /dev/null +++ b/marking_app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/marking_app/android/settings.gradle b/marking_app/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/marking_app/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/marking_app/assets/icons/demo.css b/marking_app/assets/icons/demo.css new file mode 100644 index 0000000..a67054a --- /dev/null +++ b/marking_app/assets/icons/demo.css @@ -0,0 +1,539 @@ +/* Logo 字体 */ +@font-face { + font-family: "iconfont logo"; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); +} + +.logo { + font-family: "iconfont logo"; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown>p, +.markdown>blockquote, +.markdown>.highlight, +.markdown>ol, +.markdown>ul { + width: 80%; +} + +.markdown ul>li { + list-style: circle; +} + +.markdown>ul li, +.markdown blockquote ul>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown>ul li p, +.markdown>ol li p { + margin: 0.6em 0; +} + +.markdown ol>li { + list-style: decimal; +} + +.markdown>ol li, +.markdown blockquote ol>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown>table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown>table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown>table th, +.markdown>table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown>table th { + background: #F7F7F7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown>br, +.markdown>p>br { + clear: both; +} + + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre)>code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre)>code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/marking_app/assets/icons/demo_index.html b/marking_app/assets/icons/demo_index.html new file mode 100644 index 0000000..ae14068 --- /dev/null +++ b/marking_app/assets/icons/demo_index.html @@ -0,0 +1,1476 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + +

+ +
+
+
    + +
  • + +
    Frame
    +
    &#xe63d;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe63a;
    +
  • + +
  • + +
    Frame
    +
    &#xe63b;
    +
  • + +
  • + +
    Frame-2
    +
    &#xe63c;
    +
  • + +
  • + +
    Frame
    +
    &#xe639;
    +
  • + +
  • + +
    Frame
    +
    &#xe635;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe636;
    +
  • + +
  • + +
    Frame-3
    +
    &#xe637;
    +
  • + +
  • + +
    Frame-2
    +
    &#xe638;
    +
  • + +
  • + +
    Frame
    +
    &#xe634;
    +
  • + +
  • + +
    继续阅卷
    +
    &#xe632;
    +
  • + +
  • + +
    Frame
    +
    &#xe631;
    +
  • + +
  • + +
    阅卷记录
    +
    &#xe630;
    +
  • + +
  • + +
    Frame
    +
    &#xe62e;
    +
  • + +
  • + +
    Frame1
    +
    &#xe62f;
    +
  • + +
  • + +
    1
    +
    &#xe629;
    +
  • + +
  • + +
    Frame
    +
    &#xe627;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe628;
    +
  • + +
  • + +
    Frame
    +
    &#xe625;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe626;
    +
  • + +
  • + +
    Frame
    +
    &#xe61b;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe61c;
    +
  • + +
  • + +
    Frame-2
    +
    &#xe61d;
    +
  • + +
  • + +
    Frame-3
    +
    &#xe61e;
    +
  • + +
  • + +
    Frame-4
    +
    &#xe61f;
    +
  • + +
  • + +
    Frame-5
    +
    &#xe620;
    +
  • + +
  • + +
    Frame-6
    +
    &#xe621;
    +
  • + +
  • + +
    Frame-7
    +
    &#xe622;
    +
  • + +
  • + +
    Frame-8
    +
    &#xe623;
    +
  • + +
  • + +
    Frame-9
    +
    &#xe624;
    +
  • + +
  • + +
    equal
    +
    &#xe61a;
    +
  • + +
  • + +
    warning
    +
    &#xe601;
    +
  • + +
  • + +
    error
    +
    &#xe602;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe608;
    +
  • + +
  • + +
    Frame
    +
    &#xe609;
    +
  • + +
  • + +
    Frame-2
    +
    &#xe60a;
    +
  • + +
  • + +
    Frame-3
    +
    &#xe60b;
    +
  • + +
  • + +
    Frame-4
    +
    &#xe60c;
    +
  • + +
  • + +
    Frame-9
    +
    &#xe60d;
    +
  • + +
  • + +
    Frame-8
    +
    &#xe60e;
    +
  • + +
  • + +
    Frame-13
    +
    &#xe60f;
    +
  • + +
  • + +
    Frame-12
    +
    &#xe610;
    +
  • + +
  • + +
    Frame-15
    +
    &#xe611;
    +
  • + +
  • + +
    Frame-17
    +
    &#xe612;
    +
  • + +
  • + +
    Frame-11
    +
    &#xe613;
    +
  • + +
  • + +
    Frame-16
    +
    &#xe614;
    +
  • + +
  • + +
    Frame-7
    +
    &#xe615;
    +
  • + +
  • + +
    Frame-14
    +
    &#xe616;
    +
  • + +
  • + +
    Frame-10
    +
    &#xe617;
    +
  • + +
  • + +
    Frame-5
    +
    &#xe618;
    +
  • + +
  • + +
    Frame-6
    +
    &#xe619;
    +
  • + +
  • + +
    Frame-2
    +
    &#xe603;
    +
  • + +
  • + +
    Frame-3
    +
    &#xe604;
    +
  • + +
  • + +
    Frame-1
    +
    &#xe605;
    +
  • + +
  • + +
    Frame
    +
    &#xe606;
    +
  • + +
  • + +
    Frame-4
    +
    &#xe607;
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1706671294868') format('woff2'),
+       url('iconfont.woff?t=1706671294868') format('woff'),
+       url('iconfont.ttf?t=1706671294868') format('truetype');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + Frame +
    +
    .icon-Frame13 +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-113 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame12 +
    +
  • + +
  • + +
    + Frame-2 +
    +
    .icon-Frame-24 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame10 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame9 +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-112 +
    +
  • + +
  • + +
    + Frame-3 +
    +
    .icon-Frame-33 +
    +
  • + +
  • + +
    + Frame-2 +
    +
    .icon-Frame-23 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame8 +
    +
  • + +
  • + +
    + 继续阅卷 +
    +
    .icon-Frame7 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame6 +
    +
  • + +
  • + +
    + 阅卷记录 +
    +
    .icon-yuejuanjilu +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame5 +
    +
  • + +
  • + +
    + Frame1 +
    +
    .icon-Frame11 +
    +
  • + +
  • + +
    + 1 +
    +
    .icon-a-1 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame4 +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-110 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame3 +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-19 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame2 +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-18 +
    +
  • + +
  • + +
    + Frame-2 +
    +
    .icon-Frame-22 +
    +
  • + +
  • + +
    + Frame-3 +
    +
    .icon-Frame-32 +
    +
  • + +
  • + +
    + Frame-4 +
    +
    .icon-Frame-42 +
    +
  • + +
  • + +
    + Frame-5 +
    +
    .icon-Frame-51 +
    +
  • + +
  • + +
    + Frame-6 +
    +
    .icon-Frame-61 +
    +
  • + +
  • + +
    + Frame-7 +
    +
    .icon-Frame-71 +
    +
  • + +
  • + +
    + Frame-8 +
    +
    .icon-Frame-81 +
    +
  • + +
  • + +
    + Frame-9 +
    +
    .icon-Frame-91 +
    +
  • + +
  • + +
    + equal +
    +
    .icon-equal +
    +
  • + +
  • + +
    + warning +
    +
    .icon-warning +
    +
  • + +
  • + +
    + error +
    +
    .icon-error +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-11 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame1 +
    +
  • + +
  • + +
    + Frame-2 +
    +
    .icon-Frame-21 +
    +
  • + +
  • + +
    + Frame-3 +
    +
    .icon-Frame-31 +
    +
  • + +
  • + +
    + Frame-4 +
    +
    .icon-Frame-41 +
    +
  • + +
  • + +
    + Frame-9 +
    +
    .icon-Frame-9 +
    +
  • + +
  • + +
    + Frame-8 +
    +
    .icon-Frame-8 +
    +
  • + +
  • + +
    + Frame-13 +
    +
    .icon-Frame-13 +
    +
  • + +
  • + +
    + Frame-12 +
    +
    .icon-Frame-12 +
    +
  • + +
  • + +
    + Frame-15 +
    +
    .icon-Frame-15 +
    +
  • + +
  • + +
    + Frame-17 +
    +
    .icon-Frame-17 +
    +
  • + +
  • + +
    + Frame-11 +
    +
    .icon-Frame-111 +
    +
  • + +
  • + +
    + Frame-16 +
    +
    .icon-Frame-16 +
    +
  • + +
  • + +
    + Frame-7 +
    +
    .icon-Frame-7 +
    +
  • + +
  • + +
    + Frame-14 +
    +
    .icon-Frame-14 +
    +
  • + +
  • + +
    + Frame-10 +
    +
    .icon-Frame-10 +
    +
  • + +
  • + +
    + Frame-5 +
    +
    .icon-Frame-5 +
    +
  • + +
  • + +
    + Frame-6 +
    +
    .icon-Frame-6 +
    +
  • + +
  • + +
    + Frame-2 +
    +
    .icon-Frame-2 +
    +
  • + +
  • + +
    + Frame-3 +
    +
    .icon-Frame-3 +
    +
  • + +
  • + +
    + Frame-1 +
    +
    .icon-Frame-1 +
    +
  • + +
  • + +
    + Frame +
    +
    .icon-Frame +
    +
  • + +
  • + +
    + Frame-4 +
    +
    .icon-Frame-4 +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    Frame
    +
    #icon-Frame13
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-113
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame12
    +
  • + +
  • + +
    Frame-2
    +
    #icon-Frame-24
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame10
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame9
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-112
    +
  • + +
  • + +
    Frame-3
    +
    #icon-Frame-33
    +
  • + +
  • + +
    Frame-2
    +
    #icon-Frame-23
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame8
    +
  • + +
  • + +
    继续阅卷
    +
    #icon-Frame7
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame6
    +
  • + +
  • + +
    阅卷记录
    +
    #icon-yuejuanjilu
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame5
    +
  • + +
  • + +
    Frame1
    +
    #icon-Frame11
    +
  • + +
  • + +
    1
    +
    #icon-a-1
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame4
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-110
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame3
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-19
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame2
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-18
    +
  • + +
  • + +
    Frame-2
    +
    #icon-Frame-22
    +
  • + +
  • + +
    Frame-3
    +
    #icon-Frame-32
    +
  • + +
  • + +
    Frame-4
    +
    #icon-Frame-42
    +
  • + +
  • + +
    Frame-5
    +
    #icon-Frame-51
    +
  • + +
  • + +
    Frame-6
    +
    #icon-Frame-61
    +
  • + +
  • + +
    Frame-7
    +
    #icon-Frame-71
    +
  • + +
  • + +
    Frame-8
    +
    #icon-Frame-81
    +
  • + +
  • + +
    Frame-9
    +
    #icon-Frame-91
    +
  • + +
  • + +
    equal
    +
    #icon-equal
    +
  • + +
  • + +
    warning
    +
    #icon-warning
    +
  • + +
  • + +
    error
    +
    #icon-error
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-11
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame1
    +
  • + +
  • + +
    Frame-2
    +
    #icon-Frame-21
    +
  • + +
  • + +
    Frame-3
    +
    #icon-Frame-31
    +
  • + +
  • + +
    Frame-4
    +
    #icon-Frame-41
    +
  • + +
  • + +
    Frame-9
    +
    #icon-Frame-9
    +
  • + +
  • + +
    Frame-8
    +
    #icon-Frame-8
    +
  • + +
  • + +
    Frame-13
    +
    #icon-Frame-13
    +
  • + +
  • + +
    Frame-12
    +
    #icon-Frame-12
    +
  • + +
  • + +
    Frame-15
    +
    #icon-Frame-15
    +
  • + +
  • + +
    Frame-17
    +
    #icon-Frame-17
    +
  • + +
  • + +
    Frame-11
    +
    #icon-Frame-111
    +
  • + +
  • + +
    Frame-16
    +
    #icon-Frame-16
    +
  • + +
  • + +
    Frame-7
    +
    #icon-Frame-7
    +
  • + +
  • + +
    Frame-14
    +
    #icon-Frame-14
    +
  • + +
  • + +
    Frame-10
    +
    #icon-Frame-10
    +
  • + +
  • + +
    Frame-5
    +
    #icon-Frame-5
    +
  • + +
  • + +
    Frame-6
    +
    #icon-Frame-6
    +
  • + +
  • + +
    Frame-2
    +
    #icon-Frame-2
    +
  • + +
  • + +
    Frame-3
    +
    #icon-Frame-3
    +
  • + +
  • + +
    Frame-1
    +
    #icon-Frame-1
    +
  • + +
  • + +
    Frame
    +
    #icon-Frame
    +
  • + +
  • + +
    Frame-4
    +
    #icon-Frame-4
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/marking_app/assets/icons/iconfont.css b/marking_app/assets/icons/iconfont.css new file mode 100644 index 0000000..239d1a0 --- /dev/null +++ b/marking_app/assets/icons/iconfont.css @@ -0,0 +1,239 @@ +@font-face { + font-family: "iconfont"; /* Project id 3423846 */ + src: url('iconfont.woff2?t=1706671294868') format('woff2'), + url('iconfont.woff?t=1706671294868') format('woff'), + url('iconfont.ttf?t=1706671294868') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-Frame13:before { + content: "\e63d"; +} + +.icon-Frame-113:before { + content: "\e63a"; +} + +.icon-Frame12:before { + content: "\e63b"; +} + +.icon-Frame-24:before { + content: "\e63c"; +} + +.icon-Frame10:before { + content: "\e639"; +} + +.icon-Frame9:before { + content: "\e635"; +} + +.icon-Frame-112:before { + content: "\e636"; +} + +.icon-Frame-33:before { + content: "\e637"; +} + +.icon-Frame-23:before { + content: "\e638"; +} + +.icon-Frame8:before { + content: "\e634"; +} + +.icon-Frame7:before { + content: "\e632"; +} + +.icon-Frame6:before { + content: "\e631"; +} + +.icon-yuejuanjilu:before { + content: "\e630"; +} + +.icon-Frame5:before { + content: "\e62e"; +} + +.icon-Frame11:before { + content: "\e62f"; +} + +.icon-a-1:before { + content: "\e629"; +} + +.icon-Frame4:before { + content: "\e627"; +} + +.icon-Frame-110:before { + content: "\e628"; +} + +.icon-Frame3:before { + content: "\e625"; +} + +.icon-Frame-19:before { + content: "\e626"; +} + +.icon-Frame2:before { + content: "\e61b"; +} + +.icon-Frame-18:before { + content: "\e61c"; +} + +.icon-Frame-22:before { + content: "\e61d"; +} + +.icon-Frame-32:before { + content: "\e61e"; +} + +.icon-Frame-42:before { + content: "\e61f"; +} + +.icon-Frame-51:before { + content: "\e620"; +} + +.icon-Frame-61:before { + content: "\e621"; +} + +.icon-Frame-71:before { + content: "\e622"; +} + +.icon-Frame-81:before { + content: "\e623"; +} + +.icon-Frame-91:before { + content: "\e624"; +} + +.icon-equal:before { + content: "\e61a"; +} + +.icon-warning:before { + content: "\e601"; +} + +.icon-error:before { + content: "\e602"; +} + +.icon-Frame-11:before { + content: "\e608"; +} + +.icon-Frame1:before { + content: "\e609"; +} + +.icon-Frame-21:before { + content: "\e60a"; +} + +.icon-Frame-31:before { + content: "\e60b"; +} + +.icon-Frame-41:before { + content: "\e60c"; +} + +.icon-Frame-9:before { + content: "\e60d"; +} + +.icon-Frame-8:before { + content: "\e60e"; +} + +.icon-Frame-13:before { + content: "\e60f"; +} + +.icon-Frame-12:before { + content: "\e610"; +} + +.icon-Frame-15:before { + content: "\e611"; +} + +.icon-Frame-17:before { + content: "\e612"; +} + +.icon-Frame-111:before { + content: "\e613"; +} + +.icon-Frame-16:before { + content: "\e614"; +} + +.icon-Frame-7:before { + content: "\e615"; +} + +.icon-Frame-14:before { + content: "\e616"; +} + +.icon-Frame-10:before { + content: "\e617"; +} + +.icon-Frame-5:before { + content: "\e618"; +} + +.icon-Frame-6:before { + content: "\e619"; +} + +.icon-Frame-2:before { + content: "\e603"; +} + +.icon-Frame-3:before { + content: "\e604"; +} + +.icon-Frame-1:before { + content: "\e605"; +} + +.icon-Frame:before { + content: "\e606"; +} + +.icon-Frame-4:before { + content: "\e607"; +} + diff --git a/marking_app/assets/icons/iconfont.js b/marking_app/assets/icons/iconfont.js new file mode 100644 index 0000000..915b38b --- /dev/null +++ b/marking_app/assets/icons/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_3423846='',function(c){var a=(a=document.getElementsByTagName("script"))[a.length-1],l=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var h,i,t,o,m,v=function(a,l){l.parentNode.insertBefore(a,l)};if(l&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}h=function(){var a,l=document.createElement("div");l.innerHTML=c._iconfont_svg_string_3423846,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?v(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),h()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(t=h,o=c.document,m=!1,F(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,e())})}function e(){m||(m=!0,t())}function F(){try{o.documentElement.doScroll("left")}catch(a){return void setTimeout(F,50)}e()}}(window); \ No newline at end of file diff --git a/marking_app/assets/icons/iconfont.json b/marking_app/assets/icons/iconfont.json new file mode 100644 index 0000000..d6dcfc0 --- /dev/null +++ b/marking_app/assets/icons/iconfont.json @@ -0,0 +1,401 @@ +{ + "id": "3423846", + "name": "阅卷平台", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "39175701", + "name": "Frame", + "font_class": "Frame13", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "39173378", + "name": "Frame-1", + "font_class": "Frame-113", + "unicode": "e63a", + "unicode_decimal": 58938 + }, + { + "icon_id": "39173379", + "name": "Frame", + "font_class": "Frame12", + "unicode": "e63b", + "unicode_decimal": 58939 + }, + { + "icon_id": "39173377", + "name": "Frame-2", + "font_class": "Frame-24", + "unicode": "e63c", + "unicode_decimal": 58940 + }, + { + "icon_id": "39036939", + "name": "Frame", + "font_class": "Frame10", + "unicode": "e639", + "unicode_decimal": 58937 + }, + { + "icon_id": "38951584", + "name": "Frame", + "font_class": "Frame9", + "unicode": "e635", + "unicode_decimal": 58933 + }, + { + "icon_id": "38951583", + "name": "Frame-1", + "font_class": "Frame-112", + "unicode": "e636", + "unicode_decimal": 58934 + }, + { + "icon_id": "38951582", + "name": "Frame-3", + "font_class": "Frame-33", + "unicode": "e637", + "unicode_decimal": 58935 + }, + { + "icon_id": "38951581", + "name": "Frame-2", + "font_class": "Frame-23", + "unicode": "e638", + "unicode_decimal": 58936 + }, + { + "icon_id": "38561727", + "name": "Frame", + "font_class": "Frame8", + "unicode": "e634", + "unicode_decimal": 58932 + }, + { + "icon_id": "38318831", + "name": "继续阅卷", + "font_class": "Frame7", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "38307813", + "name": "Frame", + "font_class": "Frame6", + "unicode": "e631", + "unicode_decimal": 58929 + }, + { + "icon_id": "38304788", + "name": "阅卷记录", + "font_class": "yuejuanjilu", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "38108247", + "name": "Frame", + "font_class": "Frame5", + "unicode": "e62e", + "unicode_decimal": 58926 + }, + { + "icon_id": "38108264", + "name": "Frame1", + "font_class": "Frame11", + "unicode": "e62f", + "unicode_decimal": 58927 + }, + { + "icon_id": "36030549", + "name": "1", + "font_class": "a-1", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "36025847", + "name": "Frame", + "font_class": "Frame4", + "unicode": "e627", + "unicode_decimal": 58919 + }, + { + "icon_id": "36025848", + "name": "Frame-1", + "font_class": "Frame-110", + "unicode": "e628", + "unicode_decimal": 58920 + }, + { + "icon_id": "35726114", + "name": "Frame", + "font_class": "Frame3", + "unicode": "e625", + "unicode_decimal": 58917 + }, + { + "icon_id": "35726115", + "name": "Frame-1", + "font_class": "Frame-19", + "unicode": "e626", + "unicode_decimal": 58918 + }, + { + "icon_id": "35408271", + "name": "Frame", + "font_class": "Frame2", + "unicode": "e61b", + "unicode_decimal": 58907 + }, + { + "icon_id": "35408272", + "name": "Frame-1", + "font_class": "Frame-18", + "unicode": "e61c", + "unicode_decimal": 58908 + }, + { + "icon_id": "35408273", + "name": "Frame-2", + "font_class": "Frame-22", + "unicode": "e61d", + "unicode_decimal": 58909 + }, + { + "icon_id": "35408283", + "name": "Frame-3", + "font_class": "Frame-32", + "unicode": "e61e", + "unicode_decimal": 58910 + }, + { + "icon_id": "35408284", + "name": "Frame-4", + "font_class": "Frame-42", + "unicode": "e61f", + "unicode_decimal": 58911 + }, + { + "icon_id": "35408288", + "name": "Frame-5", + "font_class": "Frame-51", + "unicode": "e620", + "unicode_decimal": 58912 + }, + { + "icon_id": "35408289", + "name": "Frame-6", + "font_class": "Frame-61", + "unicode": "e621", + "unicode_decimal": 58913 + }, + { + "icon_id": "35408298", + "name": "Frame-7", + "font_class": "Frame-71", + "unicode": "e622", + "unicode_decimal": 58914 + }, + { + "icon_id": "35408300", + "name": "Frame-8", + "font_class": "Frame-81", + "unicode": "e623", + "unicode_decimal": 58915 + }, + { + "icon_id": "35408317", + "name": "Frame-9", + "font_class": "Frame-91", + "unicode": "e624", + "unicode_decimal": 58916 + }, + { + "icon_id": "30190804", + "name": "equal", + "font_class": "equal", + "unicode": "e61a", + "unicode_decimal": 58906 + }, + { + "icon_id": "30183282", + "name": "warning", + "font_class": "warning", + "unicode": "e601", + "unicode_decimal": 58881 + }, + { + "icon_id": "30183283", + "name": "error", + "font_class": "error", + "unicode": "e602", + "unicode_decimal": 58882 + }, + { + "icon_id": "29930405", + "name": "Frame-1", + "font_class": "Frame-11", + "unicode": "e608", + "unicode_decimal": 58888 + }, + { + "icon_id": "29930406", + "name": "Frame", + "font_class": "Frame1", + "unicode": "e609", + "unicode_decimal": 58889 + }, + { + "icon_id": "29930407", + "name": "Frame-2", + "font_class": "Frame-21", + "unicode": "e60a", + "unicode_decimal": 58890 + }, + { + "icon_id": "29930408", + "name": "Frame-3", + "font_class": "Frame-31", + "unicode": "e60b", + "unicode_decimal": 58891 + }, + { + "icon_id": "29930409", + "name": "Frame-4", + "font_class": "Frame-41", + "unicode": "e60c", + "unicode_decimal": 58892 + }, + { + "icon_id": "29930410", + "name": "Frame-9", + "font_class": "Frame-9", + "unicode": "e60d", + "unicode_decimal": 58893 + }, + { + "icon_id": "29930411", + "name": "Frame-8", + "font_class": "Frame-8", + "unicode": "e60e", + "unicode_decimal": 58894 + }, + { + "icon_id": "29930412", + "name": "Frame-13", + "font_class": "Frame-13", + "unicode": "e60f", + "unicode_decimal": 58895 + }, + { + "icon_id": "29930413", + "name": "Frame-12", + "font_class": "Frame-12", + "unicode": "e610", + "unicode_decimal": 58896 + }, + { + "icon_id": "29930414", + "name": "Frame-15", + "font_class": "Frame-15", + "unicode": "e611", + "unicode_decimal": 58897 + }, + { + "icon_id": "29930415", + "name": "Frame-17", + "font_class": "Frame-17", + "unicode": "e612", + "unicode_decimal": 58898 + }, + { + "icon_id": "29930416", + "name": "Frame-11", + "font_class": "Frame-111", + "unicode": "e613", + "unicode_decimal": 58899 + }, + { + "icon_id": "29930417", + "name": "Frame-16", + "font_class": "Frame-16", + "unicode": "e614", + "unicode_decimal": 58900 + }, + { + "icon_id": "29930418", + "name": "Frame-7", + "font_class": "Frame-7", + "unicode": "e615", + "unicode_decimal": 58901 + }, + { + "icon_id": "29930419", + "name": "Frame-14", + "font_class": "Frame-14", + "unicode": "e616", + "unicode_decimal": 58902 + }, + { + "icon_id": "29930420", + "name": "Frame-10", + "font_class": "Frame-10", + "unicode": "e617", + "unicode_decimal": 58903 + }, + { + "icon_id": "29930421", + "name": "Frame-5", + "font_class": "Frame-5", + "unicode": "e618", + "unicode_decimal": 58904 + }, + { + "icon_id": "29930422", + "name": "Frame-6", + "font_class": "Frame-6", + "unicode": "e619", + "unicode_decimal": 58905 + }, + { + "icon_id": "29929656", + "name": "Frame-2", + "font_class": "Frame-2", + "unicode": "e603", + "unicode_decimal": 58883 + }, + { + "icon_id": "29929657", + "name": "Frame-3", + "font_class": "Frame-3", + "unicode": "e604", + "unicode_decimal": 58884 + }, + { + "icon_id": "29929658", + "name": "Frame-1", + "font_class": "Frame-1", + "unicode": "e605", + "unicode_decimal": 58885 + }, + { + "icon_id": "29929659", + "name": "Frame", + "font_class": "Frame", + "unicode": "e606", + "unicode_decimal": 58886 + }, + { + "icon_id": "29929660", + "name": "Frame-4", + "font_class": "Frame-4", + "unicode": "e607", + "unicode_decimal": 58887 + } + ] +} diff --git a/marking_app/assets/icons/iconfont.ttf b/marking_app/assets/icons/iconfont.ttf new file mode 100644 index 0000000..ed7cf67 Binary files /dev/null and b/marking_app/assets/icons/iconfont.ttf differ diff --git a/marking_app/assets/icons/iconfont.woff b/marking_app/assets/icons/iconfont.woff new file mode 100644 index 0000000..3cb60a1 Binary files /dev/null and b/marking_app/assets/icons/iconfont.woff differ diff --git a/marking_app/assets/icons/iconfont.woff2 b/marking_app/assets/icons/iconfont.woff2 new file mode 100644 index 0000000..7a32915 Binary files /dev/null and b/marking_app/assets/icons/iconfont.woff2 differ diff --git a/marking_app/assets/images/1.png b/marking_app/assets/images/1.png new file mode 100644 index 0000000..7c3d1ff Binary files /dev/null and b/marking_app/assets/images/1.png differ diff --git a/marking_app/assets/images/2.0x/2 (2).png b/marking_app/assets/images/2.0x/2 (2).png new file mode 100644 index 0000000..2881c42 Binary files /dev/null and b/marking_app/assets/images/2.0x/2 (2).png differ diff --git a/marking_app/assets/images/2.0x/abnormal_img.png b/marking_app/assets/images/2.0x/abnormal_img.png new file mode 100644 index 0000000..b79c142 Binary files /dev/null and b/marking_app/assets/images/2.0x/abnormal_img.png differ diff --git a/marking_app/assets/images/2.0x/default_user_dead.png b/marking_app/assets/images/2.0x/default_user_dead.png new file mode 100644 index 0000000..06d463c Binary files /dev/null and b/marking_app/assets/images/2.0x/default_user_dead.png differ diff --git a/marking_app/assets/images/2.0x/detailed_report_entry_bgm.png b/marking_app/assets/images/2.0x/detailed_report_entry_bgm.png new file mode 100644 index 0000000..8df8cf7 Binary files /dev/null and b/marking_app/assets/images/2.0x/detailed_report_entry_bgm.png differ diff --git a/marking_app/assets/images/2.0x/do_exit_marking.png b/marking_app/assets/images/2.0x/do_exit_marking.png new file mode 100644 index 0000000..d8787ee Binary files /dev/null and b/marking_app/assets/images/2.0x/do_exit_marking.png differ diff --git a/marking_app/assets/images/2.0x/do_marking_answer.png b/marking_app/assets/images/2.0x/do_marking_answer.png new file mode 100644 index 0000000..1a8797d Binary files /dev/null and b/marking_app/assets/images/2.0x/do_marking_answer.png differ diff --git a/marking_app/assets/images/2.0x/do_marking_test_paper.png b/marking_app/assets/images/2.0x/do_marking_test_paper.png new file mode 100644 index 0000000..7d2898a Binary files /dev/null and b/marking_app/assets/images/2.0x/do_marking_test_paper.png differ diff --git a/marking_app/assets/images/2.0x/equal.png b/marking_app/assets/images/2.0x/equal.png new file mode 100644 index 0000000..ab3517e Binary files /dev/null and b/marking_app/assets/images/2.0x/equal.png differ diff --git a/marking_app/assets/images/2.0x/exam_selection.png b/marking_app/assets/images/2.0x/exam_selection.png new file mode 100644 index 0000000..c53a0fa Binary files /dev/null and b/marking_app/assets/images/2.0x/exam_selection.png differ diff --git a/marking_app/assets/images/2.0x/fall.png b/marking_app/assets/images/2.0x/fall.png new file mode 100644 index 0000000..5039cda Binary files /dev/null and b/marking_app/assets/images/2.0x/fall.png differ diff --git a/marking_app/assets/images/2.0x/guide_page_gesture.png b/marking_app/assets/images/2.0x/guide_page_gesture.png new file mode 100644 index 0000000..2c36734 Binary files /dev/null and b/marking_app/assets/images/2.0x/guide_page_gesture.png differ diff --git a/marking_app/assets/images/2.0x/hide_question_id.png b/marking_app/assets/images/2.0x/hide_question_id.png new file mode 100644 index 0000000..95efc3b Binary files /dev/null and b/marking_app/assets/images/2.0x/hide_question_id.png differ diff --git a/marking_app/assets/images/2.0x/home_image.png b/marking_app/assets/images/2.0x/home_image.png new file mode 100644 index 0000000..abc67d1 Binary files /dev/null and b/marking_app/assets/images/2.0x/home_image.png differ diff --git a/marking_app/assets/images/2.0x/ic_home_normal.png b/marking_app/assets/images/2.0x/ic_home_normal.png new file mode 100644 index 0000000..64354dc Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_home_normal.png differ diff --git a/marking_app/assets/images/2.0x/ic_home_press.png b/marking_app/assets/images/2.0x/ic_home_press.png new file mode 100644 index 0000000..4d1cefb Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_home_press.png differ diff --git a/marking_app/assets/images/2.0x/ic_marking_normal.png b/marking_app/assets/images/2.0x/ic_marking_normal.png new file mode 100644 index 0000000..b9c0e6a Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_marking_normal.png differ diff --git a/marking_app/assets/images/2.0x/ic_marking_press.png b/marking_app/assets/images/2.0x/ic_marking_press.png new file mode 100644 index 0000000..d289d31 Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_marking_press.png differ diff --git a/marking_app/assets/images/2.0x/ic_mine_normal.png b/marking_app/assets/images/2.0x/ic_mine_normal.png new file mode 100644 index 0000000..719e115 Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_mine_normal.png differ diff --git a/marking_app/assets/images/2.0x/ic_mine_press.png b/marking_app/assets/images/2.0x/ic_mine_press.png new file mode 100644 index 0000000..5ada58e Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_mine_press.png differ diff --git a/marking_app/assets/images/2.0x/ic_report_normal.png b/marking_app/assets/images/2.0x/ic_report_normal.png new file mode 100644 index 0000000..2bbb924 Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_report_normal.png differ diff --git a/marking_app/assets/images/2.0x/ic_report_press.png b/marking_app/assets/images/2.0x/ic_report_press.png new file mode 100644 index 0000000..9bce2db Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_report_press.png differ diff --git a/marking_app/assets/images/2.0x/ic_work_normal.png b/marking_app/assets/images/2.0x/ic_work_normal.png new file mode 100644 index 0000000..cd54b72 Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_work_normal.png differ diff --git a/marking_app/assets/images/2.0x/ic_work_press.png b/marking_app/assets/images/2.0x/ic_work_press.png new file mode 100644 index 0000000..23d22f2 Binary files /dev/null and b/marking_app/assets/images/2.0x/ic_work_press.png differ diff --git a/marking_app/assets/images/2.0x/login_bgi.png b/marking_app/assets/images/2.0x/login_bgi.png new file mode 100644 index 0000000..87f7580 Binary files /dev/null and b/marking_app/assets/images/2.0x/login_bgi.png differ diff --git a/marking_app/assets/images/2.0x/logo.png b/marking_app/assets/images/2.0x/logo.png new file mode 100644 index 0000000..ad38e5a Binary files /dev/null and b/marking_app/assets/images/2.0x/logo.png differ diff --git a/marking_app/assets/images/2.0x/marking_settings.png b/marking_app/assets/images/2.0x/marking_settings.png new file mode 100644 index 0000000..0150b30 Binary files /dev/null and b/marking_app/assets/images/2.0x/marking_settings.png differ diff --git a/marking_app/assets/images/2.0x/not_data_bgm.png b/marking_app/assets/images/2.0x/not_data_bgm.png new file mode 100644 index 0000000..9e12212 Binary files /dev/null and b/marking_app/assets/images/2.0x/not_data_bgm.png differ diff --git a/marking_app/assets/images/2.0x/personal_bgi.png b/marking_app/assets/images/2.0x/personal_bgi.png new file mode 100644 index 0000000..f633bd6 Binary files /dev/null and b/marking_app/assets/images/2.0x/personal_bgi.png differ diff --git a/marking_app/assets/images/2.0x/report_home_icon_burst.png b/marking_app/assets/images/2.0x/report_home_icon_burst.png new file mode 100644 index 0000000..613e82c Binary files /dev/null and b/marking_app/assets/images/2.0x/report_home_icon_burst.png differ diff --git a/marking_app/assets/images/2.0x/report_home_icon_subject.png b/marking_app/assets/images/2.0x/report_home_icon_subject.png new file mode 100644 index 0000000..19f142a Binary files /dev/null and b/marking_app/assets/images/2.0x/report_home_icon_subject.png differ diff --git a/marking_app/assets/images/2.0x/report_home_top_img.png b/marking_app/assets/images/2.0x/report_home_top_img.png new file mode 100644 index 0000000..10ec946 Binary files /dev/null and b/marking_app/assets/images/2.0x/report_home_top_img.png differ diff --git a/marking_app/assets/images/2.0x/report_level_bgm.png b/marking_app/assets/images/2.0x/report_level_bgm.png new file mode 100644 index 0000000..3a1caf6 Binary files /dev/null and b/marking_app/assets/images/2.0x/report_level_bgm.png differ diff --git a/marking_app/assets/images/2.0x/review_error.png b/marking_app/assets/images/2.0x/review_error.png new file mode 100644 index 0000000..34f1c12 Binary files /dev/null and b/marking_app/assets/images/2.0x/review_error.png differ diff --git a/marking_app/assets/images/2.0x/review_loding.png b/marking_app/assets/images/2.0x/review_loding.png new file mode 100644 index 0000000..2e5cd4e Binary files /dev/null and b/marking_app/assets/images/2.0x/review_loding.png differ diff --git a/marking_app/assets/images/2.0x/rise.png b/marking_app/assets/images/2.0x/rise.png new file mode 100644 index 0000000..65e2696 Binary files /dev/null and b/marking_app/assets/images/2.0x/rise.png differ diff --git a/marking_app/assets/images/2.0x/role_selection.png b/marking_app/assets/images/2.0x/role_selection.png new file mode 100644 index 0000000..806c04a Binary files /dev/null and b/marking_app/assets/images/2.0x/role_selection.png differ diff --git a/marking_app/assets/images/2.0x/test_paper_loading_failed.png b/marking_app/assets/images/2.0x/test_paper_loading_failed.png new file mode 100644 index 0000000..d86cfb1 Binary files /dev/null and b/marking_app/assets/images/2.0x/test_paper_loading_failed.png differ diff --git a/marking_app/assets/images/2.0x/upgrade_dialog_bgc.png b/marking_app/assets/images/2.0x/upgrade_dialog_bgc.png new file mode 100644 index 0000000..96ff3dd Binary files /dev/null and b/marking_app/assets/images/2.0x/upgrade_dialog_bgc.png differ diff --git a/marking_app/assets/images/2.0x/vertical_screen_btn.png b/marking_app/assets/images/2.0x/vertical_screen_btn.png new file mode 100644 index 0000000..f4cc7e9 Binary files /dev/null and b/marking_app/assets/images/2.0x/vertical_screen_btn.png differ diff --git a/marking_app/assets/images/3.0x/30 (1).png b/marking_app/assets/images/3.0x/30 (1).png new file mode 100644 index 0000000..df1fa35 Binary files /dev/null and b/marking_app/assets/images/3.0x/30 (1).png differ diff --git a/marking_app/assets/images/3.0x/abnormal_img.png b/marking_app/assets/images/3.0x/abnormal_img.png new file mode 100644 index 0000000..2c599b5 Binary files /dev/null and b/marking_app/assets/images/3.0x/abnormal_img.png differ diff --git a/marking_app/assets/images/3.0x/default_user_dead.png b/marking_app/assets/images/3.0x/default_user_dead.png new file mode 100644 index 0000000..92578fc Binary files /dev/null and b/marking_app/assets/images/3.0x/default_user_dead.png differ diff --git a/marking_app/assets/images/3.0x/detailed_report_entry_bgm.png b/marking_app/assets/images/3.0x/detailed_report_entry_bgm.png new file mode 100644 index 0000000..a2293be Binary files /dev/null and b/marking_app/assets/images/3.0x/detailed_report_entry_bgm.png differ diff --git a/marking_app/assets/images/3.0x/do_exit_marking.png b/marking_app/assets/images/3.0x/do_exit_marking.png new file mode 100644 index 0000000..02dd2ad Binary files /dev/null and b/marking_app/assets/images/3.0x/do_exit_marking.png differ diff --git a/marking_app/assets/images/3.0x/do_marking_answer.png b/marking_app/assets/images/3.0x/do_marking_answer.png new file mode 100644 index 0000000..6bea8f8 Binary files /dev/null and b/marking_app/assets/images/3.0x/do_marking_answer.png differ diff --git a/marking_app/assets/images/3.0x/do_marking_test_paper.png b/marking_app/assets/images/3.0x/do_marking_test_paper.png new file mode 100644 index 0000000..f5da23c Binary files /dev/null and b/marking_app/assets/images/3.0x/do_marking_test_paper.png differ diff --git a/marking_app/assets/images/3.0x/equal.png b/marking_app/assets/images/3.0x/equal.png new file mode 100644 index 0000000..84f147b Binary files /dev/null and b/marking_app/assets/images/3.0x/equal.png differ diff --git a/marking_app/assets/images/3.0x/exam_selection.png b/marking_app/assets/images/3.0x/exam_selection.png new file mode 100644 index 0000000..3dbcb28 Binary files /dev/null and b/marking_app/assets/images/3.0x/exam_selection.png differ diff --git a/marking_app/assets/images/3.0x/fall.png b/marking_app/assets/images/3.0x/fall.png new file mode 100644 index 0000000..539bef5 Binary files /dev/null and b/marking_app/assets/images/3.0x/fall.png differ diff --git a/marking_app/assets/images/3.0x/guide_page_gesture.png b/marking_app/assets/images/3.0x/guide_page_gesture.png new file mode 100644 index 0000000..01f2ad2 Binary files /dev/null and b/marking_app/assets/images/3.0x/guide_page_gesture.png differ diff --git a/marking_app/assets/images/3.0x/hide_question_id.png b/marking_app/assets/images/3.0x/hide_question_id.png new file mode 100644 index 0000000..1c56b4d Binary files /dev/null and b/marking_app/assets/images/3.0x/hide_question_id.png differ diff --git a/marking_app/assets/images/3.0x/home_image.png b/marking_app/assets/images/3.0x/home_image.png new file mode 100644 index 0000000..46b8647 Binary files /dev/null and b/marking_app/assets/images/3.0x/home_image.png differ diff --git a/marking_app/assets/images/3.0x/ic_home_normal.png b/marking_app/assets/images/3.0x/ic_home_normal.png new file mode 100644 index 0000000..44b7e1c Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_home_normal.png differ diff --git a/marking_app/assets/images/3.0x/ic_home_press.png b/marking_app/assets/images/3.0x/ic_home_press.png new file mode 100644 index 0000000..19ab970 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_home_press.png differ diff --git a/marking_app/assets/images/3.0x/ic_marking_normal.png b/marking_app/assets/images/3.0x/ic_marking_normal.png new file mode 100644 index 0000000..e5534c1 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_marking_normal.png differ diff --git a/marking_app/assets/images/3.0x/ic_marking_press.png b/marking_app/assets/images/3.0x/ic_marking_press.png new file mode 100644 index 0000000..6bbc332 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_marking_press.png differ diff --git a/marking_app/assets/images/3.0x/ic_mine_normal.png b/marking_app/assets/images/3.0x/ic_mine_normal.png new file mode 100644 index 0000000..e410817 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_mine_normal.png differ diff --git a/marking_app/assets/images/3.0x/ic_mine_press.png b/marking_app/assets/images/3.0x/ic_mine_press.png new file mode 100644 index 0000000..512e001 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_mine_press.png differ diff --git a/marking_app/assets/images/3.0x/ic_report_normal.png b/marking_app/assets/images/3.0x/ic_report_normal.png new file mode 100644 index 0000000..99ea9a7 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_report_normal.png differ diff --git a/marking_app/assets/images/3.0x/ic_report_press.png b/marking_app/assets/images/3.0x/ic_report_press.png new file mode 100644 index 0000000..36d1056 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_report_press.png differ diff --git a/marking_app/assets/images/3.0x/ic_work_normal.png b/marking_app/assets/images/3.0x/ic_work_normal.png new file mode 100644 index 0000000..deb6e0d Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_work_normal.png differ diff --git a/marking_app/assets/images/3.0x/ic_work_press.png b/marking_app/assets/images/3.0x/ic_work_press.png new file mode 100644 index 0000000..ca13777 Binary files /dev/null and b/marking_app/assets/images/3.0x/ic_work_press.png differ diff --git a/marking_app/assets/images/3.0x/login_bgi.png b/marking_app/assets/images/3.0x/login_bgi.png new file mode 100644 index 0000000..d91ff82 Binary files /dev/null and b/marking_app/assets/images/3.0x/login_bgi.png differ diff --git a/marking_app/assets/images/3.0x/logo.png b/marking_app/assets/images/3.0x/logo.png new file mode 100644 index 0000000..620ccc7 Binary files /dev/null and b/marking_app/assets/images/3.0x/logo.png differ diff --git a/marking_app/assets/images/3.0x/marking_settings.png b/marking_app/assets/images/3.0x/marking_settings.png new file mode 100644 index 0000000..296e601 Binary files /dev/null and b/marking_app/assets/images/3.0x/marking_settings.png differ diff --git a/marking_app/assets/images/3.0x/not_data_bgm.png b/marking_app/assets/images/3.0x/not_data_bgm.png new file mode 100644 index 0000000..4f5bd3e Binary files /dev/null and b/marking_app/assets/images/3.0x/not_data_bgm.png differ diff --git a/marking_app/assets/images/3.0x/personal_bgi.png b/marking_app/assets/images/3.0x/personal_bgi.png new file mode 100644 index 0000000..83e7ee9 Binary files /dev/null and b/marking_app/assets/images/3.0x/personal_bgi.png differ diff --git a/marking_app/assets/images/3.0x/report_home_icon_burst.png b/marking_app/assets/images/3.0x/report_home_icon_burst.png new file mode 100644 index 0000000..d3bb3fa Binary files /dev/null and b/marking_app/assets/images/3.0x/report_home_icon_burst.png differ diff --git a/marking_app/assets/images/3.0x/report_home_icon_subject.png b/marking_app/assets/images/3.0x/report_home_icon_subject.png new file mode 100644 index 0000000..91337c3 Binary files /dev/null and b/marking_app/assets/images/3.0x/report_home_icon_subject.png differ diff --git a/marking_app/assets/images/3.0x/report_home_top_img.png b/marking_app/assets/images/3.0x/report_home_top_img.png new file mode 100644 index 0000000..0034a35 Binary files /dev/null and b/marking_app/assets/images/3.0x/report_home_top_img.png differ diff --git a/marking_app/assets/images/3.0x/report_level_bgm.png b/marking_app/assets/images/3.0x/report_level_bgm.png new file mode 100644 index 0000000..d588eec Binary files /dev/null and b/marking_app/assets/images/3.0x/report_level_bgm.png differ diff --git a/marking_app/assets/images/3.0x/review_error.png b/marking_app/assets/images/3.0x/review_error.png new file mode 100644 index 0000000..c89ba12 Binary files /dev/null and b/marking_app/assets/images/3.0x/review_error.png differ diff --git a/marking_app/assets/images/3.0x/review_loding.png b/marking_app/assets/images/3.0x/review_loding.png new file mode 100644 index 0000000..5c5232a Binary files /dev/null and b/marking_app/assets/images/3.0x/review_loding.png differ diff --git a/marking_app/assets/images/3.0x/rise.png b/marking_app/assets/images/3.0x/rise.png new file mode 100644 index 0000000..6cb2839 Binary files /dev/null and b/marking_app/assets/images/3.0x/rise.png differ diff --git a/marking_app/assets/images/3.0x/role_selection.png b/marking_app/assets/images/3.0x/role_selection.png new file mode 100644 index 0000000..0b885b3 Binary files /dev/null and b/marking_app/assets/images/3.0x/role_selection.png differ diff --git a/marking_app/assets/images/3.0x/test_paper_loading_failed.png b/marking_app/assets/images/3.0x/test_paper_loading_failed.png new file mode 100644 index 0000000..7b15c69 Binary files /dev/null and b/marking_app/assets/images/3.0x/test_paper_loading_failed.png differ diff --git a/marking_app/assets/images/3.0x/upgrade_dialog_bgc.png b/marking_app/assets/images/3.0x/upgrade_dialog_bgc.png new file mode 100644 index 0000000..832f72b Binary files /dev/null and b/marking_app/assets/images/3.0x/upgrade_dialog_bgc.png differ diff --git a/marking_app/assets/images/3.0x/vertical_screen_btn.png b/marking_app/assets/images/3.0x/vertical_screen_btn.png new file mode 100644 index 0000000..ca4f1f9 Binary files /dev/null and b/marking_app/assets/images/3.0x/vertical_screen_btn.png differ diff --git a/marking_app/assets/images/4.0x/4 (2).png b/marking_app/assets/images/4.0x/4 (2).png new file mode 100644 index 0000000..9af04b7 Binary files /dev/null and b/marking_app/assets/images/4.0x/4 (2).png differ diff --git a/marking_app/assets/images/4.0x/abnormal_img.png b/marking_app/assets/images/4.0x/abnormal_img.png new file mode 100644 index 0000000..6c92c79 Binary files /dev/null and b/marking_app/assets/images/4.0x/abnormal_img.png differ diff --git a/marking_app/assets/images/4.0x/default_user_dead.png b/marking_app/assets/images/4.0x/default_user_dead.png new file mode 100644 index 0000000..1f98182 Binary files /dev/null and b/marking_app/assets/images/4.0x/default_user_dead.png differ diff --git a/marking_app/assets/images/4.0x/detailed_report_entry_bgm.png b/marking_app/assets/images/4.0x/detailed_report_entry_bgm.png new file mode 100644 index 0000000..5672008 Binary files /dev/null and b/marking_app/assets/images/4.0x/detailed_report_entry_bgm.png differ diff --git a/marking_app/assets/images/4.0x/do_exit_marking.png b/marking_app/assets/images/4.0x/do_exit_marking.png new file mode 100644 index 0000000..0efe020 Binary files /dev/null and b/marking_app/assets/images/4.0x/do_exit_marking.png differ diff --git a/marking_app/assets/images/4.0x/do_marking_answer.png b/marking_app/assets/images/4.0x/do_marking_answer.png new file mode 100644 index 0000000..08277cc Binary files /dev/null and b/marking_app/assets/images/4.0x/do_marking_answer.png differ diff --git a/marking_app/assets/images/4.0x/do_marking_test_paper.png b/marking_app/assets/images/4.0x/do_marking_test_paper.png new file mode 100644 index 0000000..59d326b Binary files /dev/null and b/marking_app/assets/images/4.0x/do_marking_test_paper.png differ diff --git a/marking_app/assets/images/4.0x/equal.png b/marking_app/assets/images/4.0x/equal.png new file mode 100644 index 0000000..157b9e1 Binary files /dev/null and b/marking_app/assets/images/4.0x/equal.png differ diff --git a/marking_app/assets/images/4.0x/exam_selection.png b/marking_app/assets/images/4.0x/exam_selection.png new file mode 100644 index 0000000..e7706c8 Binary files /dev/null and b/marking_app/assets/images/4.0x/exam_selection.png differ diff --git a/marking_app/assets/images/4.0x/fall.png b/marking_app/assets/images/4.0x/fall.png new file mode 100644 index 0000000..333372a Binary files /dev/null and b/marking_app/assets/images/4.0x/fall.png differ diff --git a/marking_app/assets/images/4.0x/guide_page_gesture.png b/marking_app/assets/images/4.0x/guide_page_gesture.png new file mode 100644 index 0000000..7a2a2f7 Binary files /dev/null and b/marking_app/assets/images/4.0x/guide_page_gesture.png differ diff --git a/marking_app/assets/images/4.0x/hide_question_id.png b/marking_app/assets/images/4.0x/hide_question_id.png new file mode 100644 index 0000000..d768eda Binary files /dev/null and b/marking_app/assets/images/4.0x/hide_question_id.png differ diff --git a/marking_app/assets/images/4.0x/home_image.png b/marking_app/assets/images/4.0x/home_image.png new file mode 100644 index 0000000..f5bfd22 Binary files /dev/null and b/marking_app/assets/images/4.0x/home_image.png differ diff --git a/marking_app/assets/images/4.0x/ic_home_normal.png b/marking_app/assets/images/4.0x/ic_home_normal.png new file mode 100644 index 0000000..d12ff70 Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_home_normal.png differ diff --git a/marking_app/assets/images/4.0x/ic_home_press.png b/marking_app/assets/images/4.0x/ic_home_press.png new file mode 100644 index 0000000..fd6267a Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_home_press.png differ diff --git a/marking_app/assets/images/4.0x/ic_marking_normal.png b/marking_app/assets/images/4.0x/ic_marking_normal.png new file mode 100644 index 0000000..92eed63 Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_marking_normal.png differ diff --git a/marking_app/assets/images/4.0x/ic_marking_press.png b/marking_app/assets/images/4.0x/ic_marking_press.png new file mode 100644 index 0000000..cc7b8e3 Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_marking_press.png differ diff --git a/marking_app/assets/images/4.0x/ic_mine_normal.png b/marking_app/assets/images/4.0x/ic_mine_normal.png new file mode 100644 index 0000000..eeed1a9 Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_mine_normal.png differ diff --git a/marking_app/assets/images/4.0x/ic_mine_press.png b/marking_app/assets/images/4.0x/ic_mine_press.png new file mode 100644 index 0000000..b994e8e Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_mine_press.png differ diff --git a/marking_app/assets/images/4.0x/ic_report_normal.png b/marking_app/assets/images/4.0x/ic_report_normal.png new file mode 100644 index 0000000..fad62fe Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_report_normal.png differ diff --git a/marking_app/assets/images/4.0x/ic_report_press.png b/marking_app/assets/images/4.0x/ic_report_press.png new file mode 100644 index 0000000..7ea1e8d Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_report_press.png differ diff --git a/marking_app/assets/images/4.0x/ic_work_normal.png b/marking_app/assets/images/4.0x/ic_work_normal.png new file mode 100644 index 0000000..29bd81d Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_work_normal.png differ diff --git a/marking_app/assets/images/4.0x/ic_work_press.png b/marking_app/assets/images/4.0x/ic_work_press.png new file mode 100644 index 0000000..431435f Binary files /dev/null and b/marking_app/assets/images/4.0x/ic_work_press.png differ diff --git a/marking_app/assets/images/4.0x/login_bgi.png b/marking_app/assets/images/4.0x/login_bgi.png new file mode 100644 index 0000000..64bf904 Binary files /dev/null and b/marking_app/assets/images/4.0x/login_bgi.png differ diff --git a/marking_app/assets/images/4.0x/logo.png b/marking_app/assets/images/4.0x/logo.png new file mode 100644 index 0000000..a1a62e0 Binary files /dev/null and b/marking_app/assets/images/4.0x/logo.png differ diff --git a/marking_app/assets/images/4.0x/marking_settings.png b/marking_app/assets/images/4.0x/marking_settings.png new file mode 100644 index 0000000..dfb9409 Binary files /dev/null and b/marking_app/assets/images/4.0x/marking_settings.png differ diff --git a/marking_app/assets/images/4.0x/not_data_bgm.png b/marking_app/assets/images/4.0x/not_data_bgm.png new file mode 100644 index 0000000..400c065 Binary files /dev/null and b/marking_app/assets/images/4.0x/not_data_bgm.png differ diff --git a/marking_app/assets/images/4.0x/personal_bgi.png b/marking_app/assets/images/4.0x/personal_bgi.png new file mode 100644 index 0000000..a15351f Binary files /dev/null and b/marking_app/assets/images/4.0x/personal_bgi.png differ diff --git a/marking_app/assets/images/4.0x/report_home_icon_burst.png b/marking_app/assets/images/4.0x/report_home_icon_burst.png new file mode 100644 index 0000000..f1b8009 Binary files /dev/null and b/marking_app/assets/images/4.0x/report_home_icon_burst.png differ diff --git a/marking_app/assets/images/4.0x/report_home_icon_subject.png b/marking_app/assets/images/4.0x/report_home_icon_subject.png new file mode 100644 index 0000000..f4c941a Binary files /dev/null and b/marking_app/assets/images/4.0x/report_home_icon_subject.png differ diff --git a/marking_app/assets/images/4.0x/report_home_top_img.png b/marking_app/assets/images/4.0x/report_home_top_img.png new file mode 100644 index 0000000..786fa3d Binary files /dev/null and b/marking_app/assets/images/4.0x/report_home_top_img.png differ diff --git a/marking_app/assets/images/4.0x/report_level_bgm.png b/marking_app/assets/images/4.0x/report_level_bgm.png new file mode 100644 index 0000000..e337828 Binary files /dev/null and b/marking_app/assets/images/4.0x/report_level_bgm.png differ diff --git a/marking_app/assets/images/4.0x/review_error.png b/marking_app/assets/images/4.0x/review_error.png new file mode 100644 index 0000000..6fdaf54 Binary files /dev/null and b/marking_app/assets/images/4.0x/review_error.png differ diff --git a/marking_app/assets/images/4.0x/review_loding.png b/marking_app/assets/images/4.0x/review_loding.png new file mode 100644 index 0000000..bb339fb Binary files /dev/null and b/marking_app/assets/images/4.0x/review_loding.png differ diff --git a/marking_app/assets/images/4.0x/rise.png b/marking_app/assets/images/4.0x/rise.png new file mode 100644 index 0000000..afda948 Binary files /dev/null and b/marking_app/assets/images/4.0x/rise.png differ diff --git a/marking_app/assets/images/4.0x/role_selection.png b/marking_app/assets/images/4.0x/role_selection.png new file mode 100644 index 0000000..d456fce Binary files /dev/null and b/marking_app/assets/images/4.0x/role_selection.png differ diff --git a/marking_app/assets/images/4.0x/test_paper_loading_failed.png b/marking_app/assets/images/4.0x/test_paper_loading_failed.png new file mode 100644 index 0000000..02adf68 Binary files /dev/null and b/marking_app/assets/images/4.0x/test_paper_loading_failed.png differ diff --git a/marking_app/assets/images/4.0x/upgrade_dialog_bgc.png b/marking_app/assets/images/4.0x/upgrade_dialog_bgc.png new file mode 100644 index 0000000..442b964 Binary files /dev/null and b/marking_app/assets/images/4.0x/upgrade_dialog_bgc.png differ diff --git a/marking_app/assets/images/4.0x/vertical_screen_btn.png b/marking_app/assets/images/4.0x/vertical_screen_btn.png new file mode 100644 index 0000000..43b456f Binary files /dev/null and b/marking_app/assets/images/4.0x/vertical_screen_btn.png differ diff --git a/marking_app/assets/images/abnormal_img.png b/marking_app/assets/images/abnormal_img.png new file mode 100644 index 0000000..f3b3f74 Binary files /dev/null and b/marking_app/assets/images/abnormal_img.png differ diff --git a/marking_app/assets/images/default_user_dead.png b/marking_app/assets/images/default_user_dead.png new file mode 100644 index 0000000..2c68da1 Binary files /dev/null and b/marking_app/assets/images/default_user_dead.png differ diff --git a/marking_app/assets/images/detailed_report_entry_bgm.png b/marking_app/assets/images/detailed_report_entry_bgm.png new file mode 100644 index 0000000..05b372d Binary files /dev/null and b/marking_app/assets/images/detailed_report_entry_bgm.png differ diff --git a/marking_app/assets/images/do_exit_marking.png b/marking_app/assets/images/do_exit_marking.png new file mode 100644 index 0000000..448102e Binary files /dev/null and b/marking_app/assets/images/do_exit_marking.png differ diff --git a/marking_app/assets/images/do_marking_answer.png b/marking_app/assets/images/do_marking_answer.png new file mode 100644 index 0000000..1a219de Binary files /dev/null and b/marking_app/assets/images/do_marking_answer.png differ diff --git a/marking_app/assets/images/do_marking_test_paper.png b/marking_app/assets/images/do_marking_test_paper.png new file mode 100644 index 0000000..4f7fdc3 Binary files /dev/null and b/marking_app/assets/images/do_marking_test_paper.png differ diff --git a/marking_app/assets/images/equal.png b/marking_app/assets/images/equal.png new file mode 100644 index 0000000..3043017 Binary files /dev/null and b/marking_app/assets/images/equal.png differ diff --git a/marking_app/assets/images/exam_selection.png b/marking_app/assets/images/exam_selection.png new file mode 100644 index 0000000..703ed52 Binary files /dev/null and b/marking_app/assets/images/exam_selection.png differ diff --git a/marking_app/assets/images/fall.png b/marking_app/assets/images/fall.png new file mode 100644 index 0000000..06ee370 Binary files /dev/null and b/marking_app/assets/images/fall.png differ diff --git a/marking_app/assets/images/guide_page_gesture.png b/marking_app/assets/images/guide_page_gesture.png new file mode 100644 index 0000000..11ff5db Binary files /dev/null and b/marking_app/assets/images/guide_page_gesture.png differ diff --git a/marking_app/assets/images/hide_question_id.png b/marking_app/assets/images/hide_question_id.png new file mode 100644 index 0000000..77d72e0 Binary files /dev/null and b/marking_app/assets/images/hide_question_id.png differ diff --git a/marking_app/assets/images/home_image.png b/marking_app/assets/images/home_image.png new file mode 100644 index 0000000..a741eb7 Binary files /dev/null and b/marking_app/assets/images/home_image.png differ diff --git a/marking_app/assets/images/ic_home_normal.png b/marking_app/assets/images/ic_home_normal.png new file mode 100644 index 0000000..30eefd2 Binary files /dev/null and b/marking_app/assets/images/ic_home_normal.png differ diff --git a/marking_app/assets/images/ic_home_press.png b/marking_app/assets/images/ic_home_press.png new file mode 100644 index 0000000..efff317 Binary files /dev/null and b/marking_app/assets/images/ic_home_press.png differ diff --git a/marking_app/assets/images/ic_marking_normal.png b/marking_app/assets/images/ic_marking_normal.png new file mode 100644 index 0000000..19d22ca Binary files /dev/null and b/marking_app/assets/images/ic_marking_normal.png differ diff --git a/marking_app/assets/images/ic_marking_press.png b/marking_app/assets/images/ic_marking_press.png new file mode 100644 index 0000000..383778c Binary files /dev/null and b/marking_app/assets/images/ic_marking_press.png differ diff --git a/marking_app/assets/images/ic_mine_normal.png b/marking_app/assets/images/ic_mine_normal.png new file mode 100644 index 0000000..607d01d Binary files /dev/null and b/marking_app/assets/images/ic_mine_normal.png differ diff --git a/marking_app/assets/images/ic_mine_press.png b/marking_app/assets/images/ic_mine_press.png new file mode 100644 index 0000000..9ffeb2d Binary files /dev/null and b/marking_app/assets/images/ic_mine_press.png differ diff --git a/marking_app/assets/images/ic_report_normal.png b/marking_app/assets/images/ic_report_normal.png new file mode 100644 index 0000000..3e784da Binary files /dev/null and b/marking_app/assets/images/ic_report_normal.png differ diff --git a/marking_app/assets/images/ic_report_press.png b/marking_app/assets/images/ic_report_press.png new file mode 100644 index 0000000..d00dd6f Binary files /dev/null and b/marking_app/assets/images/ic_report_press.png differ diff --git a/marking_app/assets/images/ic_work_normal.png b/marking_app/assets/images/ic_work_normal.png new file mode 100644 index 0000000..a8cba73 Binary files /dev/null and b/marking_app/assets/images/ic_work_normal.png differ diff --git a/marking_app/assets/images/ic_work_press.png b/marking_app/assets/images/ic_work_press.png new file mode 100644 index 0000000..827705e Binary files /dev/null and b/marking_app/assets/images/ic_work_press.png differ diff --git a/marking_app/assets/images/login_bgi.png b/marking_app/assets/images/login_bgi.png new file mode 100644 index 0000000..d102c3c Binary files /dev/null and b/marking_app/assets/images/login_bgi.png differ diff --git a/marking_app/assets/images/logo.png b/marking_app/assets/images/logo.png new file mode 100644 index 0000000..c50a5e2 Binary files /dev/null and b/marking_app/assets/images/logo.png differ diff --git a/marking_app/assets/images/marking_settings.png b/marking_app/assets/images/marking_settings.png new file mode 100644 index 0000000..093774a Binary files /dev/null and b/marking_app/assets/images/marking_settings.png differ diff --git a/marking_app/assets/images/not_data_bgm.png b/marking_app/assets/images/not_data_bgm.png new file mode 100644 index 0000000..7b15e94 Binary files /dev/null and b/marking_app/assets/images/not_data_bgm.png differ diff --git a/marking_app/assets/images/personal_bgi.png b/marking_app/assets/images/personal_bgi.png new file mode 100644 index 0000000..a83ea12 Binary files /dev/null and b/marking_app/assets/images/personal_bgi.png differ diff --git a/marking_app/assets/images/report_home_icon_burst.png b/marking_app/assets/images/report_home_icon_burst.png new file mode 100644 index 0000000..7715f37 Binary files /dev/null and b/marking_app/assets/images/report_home_icon_burst.png differ diff --git a/marking_app/assets/images/report_home_icon_subject.png b/marking_app/assets/images/report_home_icon_subject.png new file mode 100644 index 0000000..e5eef92 Binary files /dev/null and b/marking_app/assets/images/report_home_icon_subject.png differ diff --git a/marking_app/assets/images/report_home_top_img.png b/marking_app/assets/images/report_home_top_img.png new file mode 100644 index 0000000..ae19474 Binary files /dev/null and b/marking_app/assets/images/report_home_top_img.png differ diff --git a/marking_app/assets/images/report_level_bgm.png b/marking_app/assets/images/report_level_bgm.png new file mode 100644 index 0000000..e9c7338 Binary files /dev/null and b/marking_app/assets/images/report_level_bgm.png differ diff --git a/marking_app/assets/images/review_error.png b/marking_app/assets/images/review_error.png new file mode 100644 index 0000000..160fc8d Binary files /dev/null and b/marking_app/assets/images/review_error.png differ diff --git a/marking_app/assets/images/review_loding.png b/marking_app/assets/images/review_loding.png new file mode 100644 index 0000000..bdaf49e Binary files /dev/null and b/marking_app/assets/images/review_loding.png differ diff --git a/marking_app/assets/images/rise.png b/marking_app/assets/images/rise.png new file mode 100644 index 0000000..6538890 Binary files /dev/null and b/marking_app/assets/images/rise.png differ diff --git a/marking_app/assets/images/role_selection.png b/marking_app/assets/images/role_selection.png new file mode 100644 index 0000000..4fbdb17 Binary files /dev/null and b/marking_app/assets/images/role_selection.png differ diff --git a/marking_app/assets/images/test_paper_loading_failed.png b/marking_app/assets/images/test_paper_loading_failed.png new file mode 100644 index 0000000..65d323f Binary files /dev/null and b/marking_app/assets/images/test_paper_loading_failed.png differ diff --git a/marking_app/assets/images/upgrade_dialog_bgc.png b/marking_app/assets/images/upgrade_dialog_bgc.png new file mode 100644 index 0000000..497d624 Binary files /dev/null and b/marking_app/assets/images/upgrade_dialog_bgc.png differ diff --git a/marking_app/assets/images/vertical_screen_btn.png b/marking_app/assets/images/vertical_screen_btn.png new file mode 100644 index 0000000..bb3c388 Binary files /dev/null and b/marking_app/assets/images/vertical_screen_btn.png differ diff --git a/marking_app/ios/.gitignore b/marking_app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/marking_app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/marking_app/ios/Flutter/AppFrameworkInfo.plist b/marking_app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/marking_app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/marking_app/ios/Flutter/Debug.xcconfig b/marking_app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/marking_app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/marking_app/ios/Flutter/Release.xcconfig b/marking_app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/marking_app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/marking_app/ios/Podfile b/marking_app/ios/Podfile new file mode 100644 index 0000000..256af1a --- /dev/null +++ b/marking_app/ios/Podfile @@ -0,0 +1,42 @@ +# Uncomment this line to define a global platform for your project +# source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' +platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/marking_app/ios/Runner.xcodeproj/project.pbxproj b/marking_app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..353ac32 --- /dev/null +++ b/marking_app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,567 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0BD030502994F44800EB7527 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0BD0304F2994F44800EB7527 /* Launch Screen.storyboard */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + EE3BAAE20F3BEC99D310EC6B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AE64B664D96A59005B338D3 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0BD0304F2994F44800EB7527 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AE64B664D96A59005B338D3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C10C2C5ACC7897B6B2214829 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + C6B2A9330D892EC648722E1F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + ED6D5F149381C3F07E8271D9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EE3BAAE20F3BEC99D310EC6B /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 050DAC6E1AD50AF47A1F4114 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9AE64B664D96A59005B338D3 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + C207720910912611749C3350 /* Pods */, + 050DAC6E1AD50AF47A1F4114 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 0BD0304F2994F44800EB7527 /* Launch Screen.storyboard */, + ); + path = Runner; + sourceTree = ""; + }; + C207720910912611749C3350 /* Pods */ = { + isa = PBXGroup; + children = ( + C10C2C5ACC7897B6B2214829 /* Pods-Runner.debug.xcconfig */, + C6B2A9330D892EC648722E1F /* Pods-Runner.release.xcconfig */, + ED6D5F149381C3F07E8271D9 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1C9AEEDAA5EE4DF0C315D220 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3BB2ED9F63943D7AA67A4542 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 0BD030502994F44800EB7527 /* Launch Screen.storyboard in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1C9AEEDAA5EE4DF0C315D220 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 3BB2ED9F63943D7AA67A4542 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 14; + DEVELOPMENT_TEAM = CYDU583KN6; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.92; + PRODUCT_BUNDLE_IDENTIFIER = com.example.markingApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 14; + DEVELOPMENT_TEAM = CYDU583KN6; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.92; + PRODUCT_BUNDLE_IDENTIFIER = com.example.markingApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 14; + DEVELOPMENT_TEAM = CYDU583KN6; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.92; + PRODUCT_BUNDLE_IDENTIFIER = com.example.markingApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/marking_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/marking_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/marking_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/marking_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/marking_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/marking_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/marking_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/marking_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/marking_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/marking_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/marking_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/marking_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/ios/Runner.xcworkspace/contents.xcworkspacedata b/marking_app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/marking_app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/marking_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/marking_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/marking_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/marking_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/marking_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/marking_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/marking_app/ios/Runner/AppDelegate.swift b/marking_app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..1ae4b2e --- /dev/null +++ b/marking_app/ios/Runner/AppDelegate.swift @@ -0,0 +1,14 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + Thread.sleep(forTimeInterval: 2) + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..728e6c3 --- /dev/null +++ b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "icon-20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "icon-20@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "icon-29.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "icon-29@2x.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "icon-29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "icon-40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "icon-40@3x.png", + "scale": "3x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "icon-60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "icon-60@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "icon-20-ipad.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "icon-20@2x-ipad.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "icon-29-ipad.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "icon-29@2x-ipad.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "icon-40.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "icon-40@2x.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "icon-76.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "icon-76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "icon-83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "icon-1024.png", + "scale": "1x" + } + ], + "info": { + "version": 1, + "author": "icon.wuruihong.com" + } +} \ No newline at end of file diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 0000000..2d30d56 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png new file mode 100644 index 0000000..6752d55 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png new file mode 100644 index 0000000..2ac12a6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png new file mode 100644 index 0000000..2ac12a6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png new file mode 100644 index 0000000..6e173e4 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png new file mode 100644 index 0000000..8b589c6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png new file mode 100644 index 0000000..8b589c6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png new file mode 100644 index 0000000..25de1d7 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png new file mode 100644 index 0000000..25de1d7 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png new file mode 100644 index 0000000..559e028 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png new file mode 100644 index 0000000..2ac12a6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png new file mode 100644 index 0000000..7acf82a Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png new file mode 100644 index 0000000..34fc19b Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png new file mode 100644 index 0000000..34fc19b Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png new file mode 100644 index 0000000..1d955a6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png new file mode 100644 index 0000000..00bbb44 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png new file mode 100644 index 0000000..91d0196 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png new file mode 100644 index 0000000..112f8a6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/Contents.json b/marking_app/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/marking_app/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1125x2436.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1125x2436.png new file mode 100644 index 0000000..60cd0f6 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1125x2436.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1242x2208.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1242x2208.png new file mode 100644 index 0000000..119c566 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1242x2208.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1242x2688.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1242x2688.png new file mode 100644 index 0000000..4f56032 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1242x2688.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1792X828.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1792X828.png new file mode 100644 index 0000000..8ed7873 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/1792X828.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2208x1242.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2208x1242.png new file mode 100644 index 0000000..8042333 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2208x1242.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2436x1125.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2436x1125.png new file mode 100644 index 0000000..38c55f1 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2436x1125.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2688x1242.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2688x1242.png new file mode 100644 index 0000000..20677db Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/2688x1242.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/301697019082_.pic.jpg b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/301697019082_.pic.jpg new file mode 100644 index 0000000..a0b0f00 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/301697019082_.pic.jpg differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/311697019082_.pic.jpg b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/311697019082_.pic.jpg new file mode 100644 index 0000000..b509312 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/311697019082_.pic.jpg differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/320x480.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/320x480.png new file mode 100644 index 0000000..a1e808b Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/320x480.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/321697019083_.pic.jpg b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/321697019083_.pic.jpg new file mode 100644 index 0000000..c6d6508 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/321697019083_.pic.jpg differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/331697019083_.pic.jpg b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/331697019083_.pic.jpg new file mode 100644 index 0000000..4bb4795 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/331697019083_.pic.jpg differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/341697019083_.pic.jpg b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/341697019083_.pic.jpg new file mode 100644 index 0000000..a8447f3 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/341697019083_.pic.jpg differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/351697019083_.pic.jpg b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/351697019083_.pic.jpg new file mode 100644 index 0000000..fd64385 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/351697019083_.pic.jpg differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x1136 1.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x1136 1.png new file mode 100644 index 0000000..6842710 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x1136 1.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x1136.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x1136.png new file mode 100644 index 0000000..7ff8715 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x1136.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x960 1.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x960 1.png new file mode 100644 index 0000000..c9062e8 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x960 1.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x960.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x960.png new file mode 100644 index 0000000..de84333 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/640x960.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/750x1334.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/750x1334.png new file mode 100644 index 0000000..a4d47a8 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/750x1334.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/828x1792.png b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/828x1792.png new file mode 100644 index 0000000..94ad6ab Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/828x1792.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/Contents.json b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000..a148e93 --- /dev/null +++ b/marking_app/ios/Runner/Assets.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,182 @@ +{ + "images" : [ + { + "extent" : "full-screen", + "filename" : "1242x2688.png", + "idiom" : "iphone", + "minimum-system-version" : "12.0", + "orientation" : "portrait", + "scale" : "3x", + "subtype" : "2688h" + }, + { + "extent" : "full-screen", + "filename" : "2688x1242.png", + "idiom" : "iphone", + "minimum-system-version" : "12.0", + "orientation" : "landscape", + "scale" : "3x", + "subtype" : "2688h" + }, + { + "extent" : "full-screen", + "filename" : "828x1792.png", + "idiom" : "iphone", + "minimum-system-version" : "12.0", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "1792h" + }, + { + "extent" : "full-screen", + "filename" : "1792X828.png", + "idiom" : "iphone", + "minimum-system-version" : "12.0", + "orientation" : "landscape", + "scale" : "2x", + "subtype" : "1792h" + }, + { + "extent" : "full-screen", + "filename" : "321697019083_.pic.jpg", + "idiom" : "ipad", + "minimum-system-version" : "12.0", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "2388h" + }, + { + "extent" : "full-screen", + "filename" : "351697019083_.pic.jpg", + "idiom" : "ipad", + "minimum-system-version" : "12.0", + "orientation" : "landscape", + "scale" : "2x", + "subtype" : "2388h" + }, + { + "extent" : "full-screen", + "filename" : "1125x2436.png", + "idiom" : "iphone", + "minimum-system-version" : "11.0", + "orientation" : "portrait", + "scale" : "3x", + "subtype" : "2436h" + }, + { + "extent" : "full-screen", + "filename" : "2436x1125.png", + "idiom" : "iphone", + "minimum-system-version" : "11.0", + "orientation" : "landscape", + "scale" : "3x", + "subtype" : "2436h" + }, + { + "extent" : "full-screen", + "filename" : "311697019082_.pic.jpg", + "idiom" : "ipad", + "minimum-system-version" : "10.0", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "2224h" + }, + { + "extent" : "full-screen", + "filename" : "341697019083_.pic.jpg", + "idiom" : "ipad", + "minimum-system-version" : "10.0", + "orientation" : "landscape", + "scale" : "2x", + "subtype" : "2224h" + }, + { + "extent" : "full-screen", + "filename" : "331697019083_.pic.jpg", + "idiom" : "ipad", + "minimum-system-version" : "9.0", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "1366h" + }, + { + "extent" : "full-screen", + "filename" : "301697019082_.pic.jpg", + "idiom" : "ipad", + "minimum-system-version" : "9.0", + "orientation" : "landscape", + "scale" : "2x", + "subtype" : "1366h" + }, + { + "extent" : "full-screen", + "filename" : "1242x2208.png", + "idiom" : "iphone", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "3x", + "subtype" : "736h" + }, + { + "extent" : "full-screen", + "filename" : "2208x1242.png", + "idiom" : "iphone", + "minimum-system-version" : "8.0", + "orientation" : "landscape", + "scale" : "3x", + "subtype" : "736h" + }, + { + "extent" : "full-screen", + "filename" : "750x1334.png", + "idiom" : "iphone", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "667h" + }, + { + "extent" : "full-screen", + "filename" : "640x960.png", + "idiom" : "iphone", + "minimum-system-version" : "7.0", + "orientation" : "portrait", + "scale" : "2x" + }, + { + "extent" : "full-screen", + "filename" : "640x1136.png", + "idiom" : "iphone", + "minimum-system-version" : "7.0", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "retina4" + }, + { + "extent" : "full-screen", + "filename" : "320x480.png", + "idiom" : "iphone", + "orientation" : "portrait", + "scale" : "1x" + }, + { + "extent" : "full-screen", + "filename" : "640x960 1.png", + "idiom" : "iphone", + "orientation" : "portrait", + "scale" : "2x" + }, + { + "extent" : "full-screen", + "filename" : "640x1136 1.png", + "idiom" : "iphone", + "orientation" : "portrait", + "scale" : "2x", + "subtype" : "retina4" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/1242x2208.png b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/1242x2208.png new file mode 100644 index 0000000..119c566 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/1242x2208.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/640x1136 1.png b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/640x1136 1.png new file mode 100644 index 0000000..6842710 Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/640x1136 1.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/750x1334.png b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/750x1334.png new file mode 100644 index 0000000..3a9befa Binary files /dev/null and b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/750x1334.png differ diff --git a/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/Contents.json b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/Contents.json new file mode 100644 index 0000000..f9d8994 --- /dev/null +++ b/marking_app/ios/Runner/Assets.xcassets/laubimage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "640x1136 1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "750x1334.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "1242x2208.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/marking_app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/marking_app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/marking_app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/ios/Runner/Base.lproj/Main.storyboard b/marking_app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..4188780 --- /dev/null +++ b/marking_app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/ios/Runner/Info.plist b/marking_app/ios/Runner/Info.plist new file mode 100644 index 0000000..747360c --- /dev/null +++ b/marking_app/ios/Runner/Info.plist @@ -0,0 +1,71 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + 学而有道 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + marking_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSApplicationQueriesSchemes + + sms + tel + + LSRequiresIPhoneOS + + NSCameraUsageDescription + Used to demonstrate image picker plugin + NSMicrophoneUsageDescription + Used to capture audio for image picker plugin + NSPhotoLibraryUsageDescription + Used to demonstrate image picker plugin + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + Launch Screen.storyboard + UIMainStoryboardFile + Main + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/marking_app/ios/Runner/Launch Screen.storyboard b/marking_app/ios/Runner/Launch Screen.storyboard new file mode 100644 index 0000000..9bc26f7 --- /dev/null +++ b/marking_app/ios/Runner/Launch Screen.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/ios/Runner/Runner-Bridging-Header.h b/marking_app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/marking_app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/marking_app/lib/common/config/request_config.dart b/marking_app/lib/common/config/request_config.dart new file mode 100644 index 0000000..cb14e94 --- /dev/null +++ b/marking_app/lib/common/config/request_config.dart @@ -0,0 +1,74 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 11:28:23 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-14 11:17:20 + * @FilePath: \marking_app\lib\config\RequestConfig.dart + * @Description: 请求工具类 + */ +import 'package:marking_app/common/model/common/base_page.dart'; + +class RequestConfig { + /* 测试地址 + static const devBaseUrl = "http://192.168.2.9:6600"; // 测试环境 ==> 基本请求接口 + // static const devBaseUrl = "http://192.168.2.8:6700"; // 测试环境 ==> 基本请求接口 + static const devLoginBaseUrl = "http://192.168.2.9:6400"; // 基本请求接口 + static const devBaseUrlOfReport = "http://192.168.2.9:4000"; // 获取报告接口*/ + + static const devBaseUrl = "https://mk-api.23544.com/mb-cli"; // 基本请求 + static const devLoginBaseUrl = "https://mk-api.23544.com"; // 登录接口 + static const devBaseUrlOfReport = "https://dc-api.23544.com"; // 获取报告接口 + + /* 正式地址 */ + static const proBaseUrl = "https://mk-api.23544.com/mb-cli"; // 基本请求 + static const proLoginBaseUrl = "https://mk-api.23544.com"; // 登录接口 + static const proBaseUrlOfReport = "https://dc-api.23544.com"; // 获取报告接口 + static const proBaseUrlOfHomework = "https://mk-hw.23544.com/hw"; // 获取作业接口 + + static const hwProxyKeywords = "/hw"; // 作业代理条件关键字 + + // https://mk-api.23544.com/hw/hw/api/Task/answer + // http://192.168.2.9:6400/hw/api/Task/answer + + static RequestConfig? _instance; + String baseUrl; + String loginBaseUrl; + String baseUrlOfReport; + + static const connectTimeout = 8000; // 连接超时 + static const receiveTimeout = 8000; // 接收超时 + + static const bool requestDataPrinting = true; // 打印返回数据 + static const bool printSwitch = true; // 打印返回数据 + + static const successCode = 200; // 返回成功code + static final BasePage basePage = BasePage(1, 10); // 分页参数 + + // 私有化构造函数,防止外部直接实例化 +// 私有化构造函数,防止外部直接实例化 + RequestConfig._({required this.baseUrl, required this.baseUrlOfReport, required this.loginBaseUrl}); + + factory RequestConfig() { + if (_instance == null) { + late String newBaseUrl; + late String newBaseUrlOfReport; + late String newLoginBaseUrl; + + if (bool.fromEnvironment('dart.vm.product')) { + // 正式环境下的代码 + + newLoginBaseUrl = proLoginBaseUrl; + newBaseUrl = proBaseUrl; + newBaseUrlOfReport = proBaseUrlOfReport; + } else { + // 在生产环境下执行的代码 + newBaseUrl = devBaseUrl; + newBaseUrlOfReport = devBaseUrlOfReport; + newLoginBaseUrl = devLoginBaseUrl; + } + _instance = + RequestConfig._(baseUrl: newBaseUrl, baseUrlOfReport: newBaseUrlOfReport, loginBaseUrl: newLoginBaseUrl); + } + return _instance!; + } +} diff --git a/marking_app/lib/common/mixin/common.dart b/marking_app/lib/common/mixin/common.dart new file mode 100644 index 0000000..def6f47 --- /dev/null +++ b/marking_app/lib/common/mixin/common.dart @@ -0,0 +1,56 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-19 15:34:22 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 11:13:01 + * @FilePath: \marking_app\lib\utils\easy_refresh\ss.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; +import 'package:marking_app/utils/request/rest_dio.dart'; +import 'package:marking_app/utils/the_global.dart'; + +mixin CommonMixin { + // 获取DIO + Future getDio() async => await RestDio().getDio(); + + // 获取 CLIENT + Future getClient() async { + Dio dio = await getDio(); + return RestClient(dio, baseUrl: RequestConfig().baseUrl); + } + + // 获取 CLIENT + Future getClientReport() async { + Dio dio = await getDio(); + return RestClientReport(dio, baseUrl: RequestConfig().baseUrlOfReport); + } + + void setTimeOut(int seconds, call) => Future.delayed(Duration(seconds: seconds), call); + + // 执行 context + void getGlobalKey(Function(BuildContext context) call) { + Future.delayed(const Duration(seconds: 0)).then((onValue) { + BuildContext context = TheGlobal.navigatorKey.currentState!.overlay!.context; + call(context); + }); + } + + // 异常处理信息 + void returnExceptionPrompt(context, err) { + if (err is DioError) { + ExceptionHandle.exceptionPrompt(context, err.error); + } + } + + void toLoginPage(BuildContext context) { + RouterManager.router.navigateTo(context, RouterManager.loginPath, clearStack: true, transition: getTransition()); + } +} diff --git a/marking_app/lib/common/model/common/base_page.dart b/marking_app/lib/common/model/common/base_page.dart new file mode 100644 index 0000000..375cd1a --- /dev/null +++ b/marking_app/lib/common/model/common/base_page.dart @@ -0,0 +1,31 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-19 16:17:38 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 16:22:41 + * @FilePath: \marking_app\lib\common\model\common\base_page.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'base_page.g.dart'; + + +@JsonSerializable() + class BasePage extends Object { + + @JsonKey(name: 'Page') + int page; + + @JsonKey(name: 'Limit') + int limit; + + BasePage(this.page,this.limit,); + + factory BasePage.fromJson(Map srcJson) => _$BasePageFromJson(srcJson); + + Map toJson() => _$BasePageToJson(this); + +} + + diff --git a/marking_app/lib/common/model/common/base_page_data.dart b/marking_app/lib/common/model/common/base_page_data.dart new file mode 100644 index 0000000..3fde8be --- /dev/null +++ b/marking_app/lib/common/model/common/base_page_data.dart @@ -0,0 +1,29 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-18 17:44:15 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 16:19:15 + * @FilePath: \marking_app\lib\common\model\common\base_page.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'base_page_data.g.dart'; + +@JsonSerializable(genericArgumentFactories: true, fieldRename: FieldRename.snake) + class BasePageData extends Object { + + @JsonKey(name: 'items') + List items; + + @JsonKey(name: 'total') + int total; + + BasePageData(this.items,this.total,); + + + factory BasePageData.fromJson( Map json, T Function(dynamic json) fromJsonT) => _$BasePageDataFromJson(json, fromJsonT); + Map toJson(Object Function(T value) toJsonT) => _$BasePageDataToJson(this, toJsonT); +} + + diff --git a/marking_app/lib/common/model/common/base_page_data_report.dart b/marking_app/lib/common/model/common/base_page_data_report.dart new file mode 100644 index 0000000..69177b6 --- /dev/null +++ b/marking_app/lib/common/model/common/base_page_data_report.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'base_page_data_report.g.dart'; + + +@JsonSerializable(genericArgumentFactories: true, fieldRename: FieldRename.snake) + class BasePageDataReport extends Object { + + @JsonKey(name: 'Data') + List items; + + @JsonKey(name: 'Total') + int total; + + BasePageDataReport(this.items,this.total,); + + factory BasePageDataReport.fromJson( Map json, T Function(dynamic json) fromJsonT) => _$BasePageDataReportFromJson(json, fromJsonT); + Map toJson(Object Function(T value) toJsonT) => _$BasePageDataReportToJson(this, toJsonT); +} \ No newline at end of file diff --git a/marking_app/lib/common/model/common/base_page_report.dart b/marking_app/lib/common/model/common/base_page_report.dart new file mode 100644 index 0000000..ca139cf --- /dev/null +++ b/marking_app/lib/common/model/common/base_page_report.dart @@ -0,0 +1,31 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-19 16:17:38 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 16:22:41 + * @FilePath: \marking_app\lib\common\model\common\base_page.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'base_page_report.g.dart'; + + +@JsonSerializable() + class BasePageReport extends Object { + + @JsonKey(name: 'PageIndex') + int page; + + @JsonKey(name: 'PageSize') + int limit; + + BasePageReport(this.page,this.limit,); + + factory BasePageReport.fromJson(Map srcJson) => _$BasePageReportFromJson(srcJson); + + Map toJson() => _$BasePageReportToJson(this); + +} + + diff --git a/marking_app/lib/common/model/common/base_structure_result.dart b/marking_app/lib/common/model/common/base_structure_result.dart new file mode 100644 index 0000000..bccaded --- /dev/null +++ b/marking_app/lib/common/model/common/base_structure_result.dart @@ -0,0 +1,38 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 18:30:11 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-14 10:20:21 + * @FilePath: \marking_app\lib\common\model\common\base_structure_result.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/config/request_config.dart'; + +part 'base_structure_result.g.dart'; + +@JsonSerializable(genericArgumentFactories: true, fieldRename: FieldRename.snake) +class BaseStructureResult extends Object { + @JsonKey(name: 'success') + bool success; + + @JsonKey(name: 'code') + int code; + + @JsonKey(name: 'message') + String? message; + + @JsonKey(name: 'data') + T? data; + + BaseStructureResult( + this.code, + this.message, + this.data, { + this.success = false, + }) { + success = (code == RequestConfig.successCode); + } + factory BaseStructureResult.fromJson(Map json, T Function(dynamic json) fromJsonT) => _$BaseStructureResultFromJson(json, fromJsonT); + Map toJson(Object Function(T value) toJsonT) => _$BaseStructureResultToJson(this, toJsonT); +} diff --git a/marking_app/lib/common/model/common/base_structure_result_report.dart b/marking_app/lib/common/model/common/base_structure_result_report.dart new file mode 100644 index 0000000..b6ca314 --- /dev/null +++ b/marking_app/lib/common/model/common/base_structure_result_report.dart @@ -0,0 +1,38 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 18:30:11 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-14 10:20:21 + * @FilePath: \marking_app\lib\common\model\common\base_structure_result.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/config/request_config.dart'; + +part 'base_structure_result_report.g.dart'; + +@JsonSerializable(genericArgumentFactories: true, fieldRename: FieldRename.snake) +class BaseStructureResultReport extends Object { + @JsonKey(name: 'success') + bool success; + + @JsonKey(name: 'Code') + int code; + + @JsonKey(name: 'Message') + String? message; + + @JsonKey(name: 'Data') + T? data; + + BaseStructureResultReport( + this.code, + this.message, + this.data, { + this.success = false, + }) { + success = (code == RequestConfig.successCode); + } + factory BaseStructureResultReport.fromJson(Map json, T Function(dynamic json) fromJsonT) => _$BaseStructureResultReportFromJson(json, fromJsonT); + Map toJson(Object Function(T value) toJsonT) => _$BaseStructureResultReportToJson(this, toJsonT); +} diff --git a/marking_app/lib/common/model/common/upload_img_secret_key.dart b/marking_app/lib/common/model/common/upload_img_secret_key.dart new file mode 100644 index 0000000..19465ec --- /dev/null +++ b/marking_app/lib/common/model/common/upload_img_secret_key.dart @@ -0,0 +1,57 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'upload_img_secret_key.g.dart'; + + +@JsonSerializable() + class UploadImgSecretKey extends Object { + + @JsonKey(name: 'account') + String? account; + + @JsonKey(name: 'bucketName') + String? bucketName; + + @JsonKey(name: 'endPoint') + int? endPoint; + + @JsonKey(name: 'ip') + String? ip; + + @JsonKey(name: 'password') + String? password; + + @JsonKey(name: 'imgSecretKey') + bool? imgSecretKey; + + // 是否打开工具 + bool annotationSwitch; + + UploadImgSecretKey({this.account,this.bucketName,this.endPoint,this.ip,this.password,this.imgSecretKey,this.annotationSwitch = false}); + + factory UploadImgSecretKey.fromJson(Map srcJson) => _$UploadImgSecretKeyFromJson(srcJson); + + Map toJson() => _$UploadImgSecretKeyToJson(this); + +} + + +/// 返回结果文件实体类 +@JsonSerializable() +class FileResult extends Object{ + String? path; // 本地相对地址 + String? url; // 图片上传返回url地址 + dynamic? otherParam; + String myObject; // 我的项目名称 + bool success; // 是否成功 + + + FileResult({required this.myObject, this.url, this.success = false,this.otherParam}); + + + + factory FileResult.fromJson(Map srcJson) => _$FileResultFromJson(srcJson); + + Map toJson() => _$FileResultToJson(this); +} + diff --git a/marking_app/lib/common/model/enum/KeyboardType.dart b/marking_app/lib/common/model/enum/KeyboardType.dart new file mode 100644 index 0000000..440a658 --- /dev/null +++ b/marking_app/lib/common/model/enum/KeyboardType.dart @@ -0,0 +1,19 @@ +// 键盘类型 +enum KeyboardType { + INPUT_TYPE, // 输入型 + RIGHT_SELECTION, // 右侧选择型 + BOTTOM_SELECTION // 底部选择型 +} + +// 键盘排序规则 +enum SortKeyboard { + INVERTED_ORDER, // 倒序排列 + FULL_AND_AERO_TOP, // 顺序排列 + FULL_AND_AERO_TOP_INVERTED_ORDER, // 满分零分置顶倒序排列 +} + +// 屏幕方向 +enum ScreenDirection { + HORIZONTAL_SCREEN, // 横屏 + VERTICAL_SCREEN // 竖屏 +} diff --git a/marking_app/lib/common/model/enum/job_enums.dart b/marking_app/lib/common/model/enum/job_enums.dart new file mode 100644 index 0000000..aea8cd1 --- /dev/null +++ b/marking_app/lib/common/model/enum/job_enums.dart @@ -0,0 +1,6 @@ +// 平台类型 +enum ReviewSwitching { + TO_PREVIOUS_QUESTION, // 上一题 + TO_NEXT_QUESTION, // 下一题 + CURRENT_QUESTION, // 当前题 +} diff --git a/marking_app/lib/common/model/enum/marking_list_type.dart b/marking_app/lib/common/model/enum/marking_list_type.dart new file mode 100644 index 0000000..9a5fa0b --- /dev/null +++ b/marking_app/lib/common/model/enum/marking_list_type.dart @@ -0,0 +1,6 @@ +/// 阅卷集合类型 +enum MarkingListType { + NORMAL, // 正常 + ARBITRATE, // 仲裁 + EXCEPTIONAL, // 异常 +} diff --git a/marking_app/lib/common/model/enum/platform.dart b/marking_app/lib/common/model/enum/platform.dart new file mode 100644 index 0000000..d3fb735 --- /dev/null +++ b/marking_app/lib/common/model/enum/platform.dart @@ -0,0 +1,5 @@ +// 平台类型 +enum Platform { + MARKING, // 阅卷 + REPORT, // 报告 +} \ No newline at end of file diff --git a/marking_app/lib/common/model/enum/reportUserIdentity.dart b/marking_app/lib/common/model/enum/reportUserIdentity.dart new file mode 100644 index 0000000..23cda20 --- /dev/null +++ b/marking_app/lib/common/model/enum/reportUserIdentity.dart @@ -0,0 +1,16 @@ +// 屏幕方向 +enum ReportUserIdentity{ + // { + // 教委 = 1, + // 校级 = 2, + // 年级 = 3, + // 班级 = 4, + // 教师 = 5, + // } + OCCUPY_POSITION, // 占位 + STATE_EDUCATION_COMMISSION, // 国家教育委员会 + SCHOOL_LEVEL, // 校级 + GRADE, // 年级 + CLASS, // 班级 + TEACHER, // 单科 +} \ No newline at end of file diff --git a/marking_app/lib/common/model/enum/subject.dart b/marking_app/lib/common/model/enum/subject.dart new file mode 100644 index 0000000..6bc847d --- /dev/null +++ b/marking_app/lib/common/model/enum/subject.dart @@ -0,0 +1,44 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-18 17:12:53 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 21:29:49 + * @FilePath: \marking_app\lib\common\model\enum\subject.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +enum Subject { + other, // 其他未定义 + chineseLanguage, // 语文 + mathematics, // 数学 + english, // 英语 + physical, // 物理 + chemical, // 化学 + biological, // 生物 + political, // 政治 + history, // 历史 + geographic // 地理 +} + +const Map subjectMap = { + Subject.chineseLanguage: '语文', + Subject.mathematics: '数学', + Subject.english: '英语', + Subject.physical: '物理', + Subject.chemical: '化学', + Subject.biological: '生物', + Subject.political: '政治', + Subject.history: '历史', + Subject.geographic: '地理' +}; + +String getSubjectEnumName(int index) { + String res = ''; + try { + Subject theCurnt = Subject.values[index]; + res = subjectMap[theCurnt] ?? res; + return res; + } catch (e) { + return ''; + } + +} diff --git a/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart b/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart new file mode 100644 index 0000000..89e53ca --- /dev/null +++ b/marking_app/lib/common/model/event_bus/bottom_annotation_switch_cleanall.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'bottom_annotation_switch_cleanall.g.dart'; + +@JsonSerializable() +class BottomAnnotationSwitchCleanall extends Object { + @JsonKey(name: 'cleanAll') + bool cleanAll; + + @JsonKey(name: 'previousStep') + bool previousStep; + + @JsonKey(name: 'uploadImage') + bool uploadImage; + + BottomAnnotationSwitchCleanall({this.cleanAll = false, this.previousStep = false, this.uploadImage = false}); + + factory BottomAnnotationSwitchCleanall.fromJson(Map srcJson) => + _$BottomAnnotationSwitchCleanallFromJson(srcJson); + + Map toJson() => _$BottomAnnotationSwitchCleanallToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/home_marking_statistics.dart b/marking_app/lib/common/model/event_bus/home_marking_statistics.dart new file mode 100644 index 0000000..9f59e29 --- /dev/null +++ b/marking_app/lib/common/model/event_bus/home_marking_statistics.dart @@ -0,0 +1 @@ +class HomeMarkingStatistics {} diff --git a/marking_app/lib/common/model/event_bus/job_currenttab_finish_addtion.dart b/marking_app/lib/common/model/event_bus/job_currenttab_finish_addtion.dart new file mode 100644 index 0000000..dd03fff --- /dev/null +++ b/marking_app/lib/common/model/event_bus/job_currenttab_finish_addtion.dart @@ -0,0 +1,13 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_currenttab_finish_addtion.g.dart'; + +@JsonSerializable() +class JobCurrenttabFinishAddtion extends Object { + JobCurrenttabFinishAddtion(); + + factory JobCurrenttabFinishAddtion.fromJson(Map srcJson) => + _$JobCurrenttabFinishAddtionFromJson(srcJson); + + Map toJson() => _$JobCurrenttabFinishAddtionToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/job_large_question_scoring_bus.dart b/marking_app/lib/common/model/event_bus/job_large_question_scoring_bus.dart new file mode 100644 index 0000000..815ed0a --- /dev/null +++ b/marking_app/lib/common/model/event_bus/job_large_question_scoring_bus.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_large_question_scoring_bus.g.dart'; + +@JsonSerializable() +class JobLargeQuestionScoringBus extends Object { + @JsonKey(name: 'ratingScore') + double? ratingScore; + + JobLargeQuestionScoringBus({this.ratingScore}); + + factory JobLargeQuestionScoringBus.fromJson(Map srcJson) => + _$JobLargeQuestionScoringBusFromJson(srcJson); + + Map toJson() => _$JobLargeQuestionScoringBusToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/job_question_switch_bus.dart b/marking_app/lib/common/model/event_bus/job_question_switch_bus.dart new file mode 100644 index 0000000..1b2cce0 --- /dev/null +++ b/marking_app/lib/common/model/event_bus/job_question_switch_bus.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/enum/job_enums.dart'; + +part 'job_question_switch_bus.g.dart'; + +@JsonSerializable() +class JobQuestionSwitchBus extends Object { + @JsonKey(name: 'switchAction') + ReviewSwitching switchAction; + + JobQuestionSwitchBus({this.switchAction = ReviewSwitching.TO_NEXT_QUESTION}); + + factory JobQuestionSwitchBus.fromJson(Map srcJson) => _$JobQuestionSwitchBusFromJson(srcJson); + + Map toJson() => _$JobQuestionSwitchBusToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/job_submit_bus.dart b/marking_app/lib/common/model/event_bus/job_submit_bus.dart new file mode 100644 index 0000000..b102021 --- /dev/null +++ b/marking_app/lib/common/model/event_bus/job_submit_bus.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_submit_bus.g.dart'; + +@JsonSerializable() +class JobSubmitBus extends Object { + @JsonKey(name: 'taskSubjectiveDetailId') + int taskSubjectiveDetailId; + + @JsonKey(name: 'score') + double score; + + @JsonKey(name: 'commentImageUrl', includeIfNull: true) + String? commentImageUrl; + + JobSubmitBus({this.commentImageUrl, required this.taskSubjectiveDetailId, required this.score}); + + factory JobSubmitBus.fromJson(Map srcJson) => _$JobSubmitBusFromJson(srcJson); + + Map toJson() => _$JobSubmitBusToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/jobs/job_do_papers_student_bus.dart b/marking_app/lib/common/model/event_bus/jobs/job_do_papers_student_bus.dart new file mode 100644 index 0000000..b181e37 --- /dev/null +++ b/marking_app/lib/common/model/event_bus/jobs/job_do_papers_student_bus.dart @@ -0,0 +1,27 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_do_papers_student_bus.g.dart'; + +@JsonSerializable() +class JobDoPapersStudentBus extends Object { + @JsonKey(name: 'studentId') + int studentId; + + @JsonKey(name: 'studentName') + String studentName; + + int pageIndex; + + bool isFirst; + + JobDoPapersStudentBus( + this.studentId, + this.studentName, + this.pageIndex, + this.isFirst, + ); + + factory JobDoPapersStudentBus.fromJson(Map srcJson) => _$JobDoPapersStudentBusFromJson(srcJson); + + Map toJson() => _$JobDoPapersStudentBusToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/jobs/job_do_papers_switch_operation_bus.dart b/marking_app/lib/common/model/event_bus/jobs/job_do_papers_switch_operation_bus.dart new file mode 100644 index 0000000..48e40c1 --- /dev/null +++ b/marking_app/lib/common/model/event_bus/jobs/job_do_papers_switch_operation_bus.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_do_papers_switch_operation_bus.g.dart'; + +@JsonSerializable() +class JobDoPapersSwitchOperationBus extends Object { + @JsonKey(name: 'flag') + bool flag; + + JobDoPapersSwitchOperationBus( + this.flag, + ); + + factory JobDoPapersSwitchOperationBus.fromJson(Map srcJson) => + _$JobDoPapersSwitchOperationBusFromJson(srcJson); + + Map toJson() => _$JobDoPapersSwitchOperationBusToJson(this); +} diff --git a/marking_app/lib/common/model/event_bus/marking_statistics_bus.dart b/marking_app/lib/common/model/event_bus/marking_statistics_bus.dart new file mode 100644 index 0000000..18ef796 --- /dev/null +++ b/marking_app/lib/common/model/event_bus/marking_statistics_bus.dart @@ -0,0 +1,5 @@ +// 阅卷列表统计数据更新 +class MarkingStatisticsBus {} + +// 阅卷列表统计数据更新 +class MarkingStatisticsMainListBus {} diff --git a/marking_app/lib/common/model/job/gesture_recording.dart b/marking_app/lib/common/model/job/gesture_recording.dart new file mode 100644 index 0000000..f140744 --- /dev/null +++ b/marking_app/lib/common/model/job/gesture_recording.dart @@ -0,0 +1,21 @@ +import 'dart:ui'; + +/** + * 手势记录 + */ +class GestureRecording { + bool eraser; // 是否是橡皮擦 + + int? usageTime; // 用时 + + Offset? data; // 位置记录 可能为null + + bool annotationSwitch; + + GestureRecording({ + required this.eraser, + required this.annotationSwitch, + this.data, + this.usageTime, + }); +} diff --git a/marking_app/lib/common/model/job/job_collect_params.dart b/marking_app/lib/common/model/job/job_collect_params.dart new file mode 100644 index 0000000..3ec0fa6 --- /dev/null +++ b/marking_app/lib/common/model/job/job_collect_params.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_collect_params.g.dart'; + +@JsonSerializable() +class JobCollectParams extends Object { + @JsonKey(name: 'taskId') + int taskId; + + @JsonKey(name: 'studentId') + int studentId; + + @JsonKey(name: 'isCollect') + bool isCollect; + + JobCollectParams( + this.taskId, + this.studentId, + this.isCollect, + ); + + factory JobCollectParams.fromJson(Map srcJson) => _$JobCollectParamsFromJson(srcJson); + + Map toJson() => _$JobCollectParamsToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_concerned_with_student.dart b/marking_app/lib/common/model/job/job_concerned_with_student.dart new file mode 100644 index 0000000..13822b0 --- /dev/null +++ b/marking_app/lib/common/model/job/job_concerned_with_student.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_concerned_with_student.g.dart'; + +@JsonSerializable() +class JobConcernedWithStudent extends Object { + @JsonKey(name: 'studentId') + int studentId; + + @JsonKey(name: 'studentName') + String studentName; + + JobConcernedWithStudent( + this.studentId, + this.studentName, + ); + + factory JobConcernedWithStudent.fromJson(Map srcJson) => _$JobConcernedWithStudentFromJson(srcJson); + + Map toJson() => _$JobConcernedWithStudentToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_concerned_with_student_params.dart b/marking_app/lib/common/model/job/job_concerned_with_student_params.dart new file mode 100644 index 0000000..e65a297 --- /dev/null +++ b/marking_app/lib/common/model/job/job_concerned_with_student_params.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_concerned_with_student_params.g.dart'; + +@JsonSerializable() +class JobConcernedWithStudentParams extends Object { + @JsonKey(name: 'taskIds') + List taskIds; + + @JsonKey(name: 'isCommit') + bool? isCommit; + + JobConcernedWithStudentParams(this.taskIds, {this.isCommit}); + + factory JobConcernedWithStudentParams.fromJson(Map srcJson) => + _$JobConcernedWithStudentParamsFromJson(srcJson); + + Map toJson() => _$JobConcernedWithStudentParamsToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_note_taking_trajectory.dart b/marking_app/lib/common/model/job/job_note_taking_trajectory.dart new file mode 100644 index 0000000..5aec683 --- /dev/null +++ b/marking_app/lib/common/model/job/job_note_taking_trajectory.dart @@ -0,0 +1,51 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_note_taking_trajectory.g.dart'; + +@JsonSerializable() +class JobNoteTakingTrajectory extends Object { + @JsonKey(name: 'lattices') + List lattices; + + @JsonKey(name: 'paperPicture') + String paperPicture; + + @JsonKey(name: 'resetTime') + int resetTime; + + JobNoteTakingTrajectory( + this.lattices, + this.paperPicture, + this.resetTime, + ); + + factory JobNoteTakingTrajectory.fromJson(Map srcJson) => _$JobNoteTakingTrajectoryFromJson(srcJson); + + Map toJson() => _$JobNoteTakingTrajectoryToJson(this); +} + +@JsonSerializable() +class Lattices extends Object { + @JsonKey(name: 'stroke') + int stroke; + + @JsonKey(name: 'x') + double x; + + @JsonKey(name: 'y') + double y; + + @JsonKey(name: 'time') + double time; + + Lattices( + this.stroke, + this.x, + this.y, + this.time, + ); + + factory Lattices.fromJson(Map srcJson) => _$LatticesFromJson(srcJson); + + Map toJson() => _$LatticesToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_page_tab.dart b/marking_app/lib/common/model/job/job_page_tab.dart new file mode 100644 index 0000000..04fb2be --- /dev/null +++ b/marking_app/lib/common/model/job/job_page_tab.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_page_tab.g.dart'; + +@JsonSerializable() +class JobPageTab extends Object { + @JsonKey(name: 'pageIndex') + int pageIndex; + + @JsonKey(name: 'total') + int total; + + @JsonKey(name: 'finishCount') + int finishCount; + + JobPageTab( + this.pageIndex, + this.total, + this.finishCount, + ); + + factory JobPageTab.fromJson(Map srcJson) => _$JobPageTabFromJson(srcJson); + + Map toJson() => _$JobPageTabToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_review_submission.dart b/marking_app/lib/common/model/job/job_review_submission.dart new file mode 100644 index 0000000..e54f2ae --- /dev/null +++ b/marking_app/lib/common/model/job/job_review_submission.dart @@ -0,0 +1,47 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_review_submission.g.dart'; + +@JsonSerializable(includeIfNull: false) +class JobReviewSubmission extends Object { + @JsonKey(name: 'paperId') + int paperId; + + @JsonKey(name: 'score') + int? score; + + @JsonKey(name: 'commentImageUrl') + String? commentImageUrl; + + @JsonKey(name: 'questions') + List questions; + + JobReviewSubmission({ + this.commentImageUrl, + required this.score, + required this.paperId, + required this.questions, + }); + + factory JobReviewSubmission.fromJson(Map srcJson) => _$JobReviewSubmissionFromJson(srcJson); + + Map toJson() => _$JobReviewSubmissionToJson(this); +} + +@JsonSerializable() +class JobReviewQuestions extends Object { + @JsonKey(name: 'questionNo') + String questionNo; + + @JsonKey(name: 'score') // 0:错 1:半对 2: 对 + int? score; + + JobReviewQuestions({ + required this.questionNo, + required this.score, + }); + + factory JobReviewQuestions.fromJson(Map srcJson) => _$JobReviewQuestionsFromJson(srcJson); + + Map toJson() => _$JobReviewQuestionsToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_task_item.dart b/marking_app/lib/common/model/job/job_task_item.dart new file mode 100644 index 0000000..6d817c9 --- /dev/null +++ b/marking_app/lib/common/model/job/job_task_item.dart @@ -0,0 +1,168 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_task_item.g.dart'; + +@JsonSerializable() +class JobTaskItem extends Object { + @JsonKey(name: 'id') + int id; + + @JsonKey(name: 'title') + String title; + + @JsonKey(name: 'subjectName') + String subjectName; + + @JsonKey(name: 'genderName') + String genderName; + + @JsonKey(name: 'isFinish') + bool isFinish; + + @JsonKey(name: 'studentCount') + int studentCount; + + @JsonKey(name: 'commitStudentCount') + int commitStudentCount; + + @JsonKey(name: 'totalCount') + int totalCount; + + @JsonKey(name: 'finishCount') + int finishCount; + + @JsonKey(name: 'objectivePrecision') + double objectivePrecision; + + @JsonKey(name: 'subjectivePrecision') + double subjectivePrecision; + + @JsonKey(name: 'precision') + double precision; + + @JsonKey(name: 'markingTasks') + List markingTasks; + + @JsonKey(name: 'createTime') + String createTime; + + @JsonKey(name: 'progressPercentage') + double progressPercentage; // 进度百分比 + + JobTaskItem( + this.id, + this.title, + this.subjectName, + this.genderName, + this.isFinish, + this.studentCount, + this.commitStudentCount, + this.totalCount, + this.finishCount, + this.objectivePrecision, + this.subjectivePrecision, + this.precision, + this.markingTasks, + this.createTime, { + this.progressPercentage = 0, + }) { + try { + progressPercentage = (finishCount / totalCount) * 100; + if (progressPercentage.isNaN) { + progressPercentage = 0; + } else { + progressPercentage = double.parse(progressPercentage.toStringAsFixed(2)); + } + } catch (e) { + progressPercentage = 0; + } + } + + factory JobTaskItem.fromJson(Map srcJson) => _$JobTaskItemFromJson(srcJson); + + Map toJson() => _$JobTaskItemToJson(this); +} + +@JsonSerializable() +class MarkingTasks extends Object { + @JsonKey(name: 'id') + int id; + + @JsonKey(name: 'className') + String className; + + @JsonKey(name: 'teacherName') + String teacherName; + + @JsonKey(name: 'isFinish') + bool isFinish; + + @JsonKey(name: 'finishTime') + String? finishTime; + + @JsonKey(name: 'studentCount') + int studentCount; + + @JsonKey(name: 'commitStudentCount') + int commitStudentCount; + + @JsonKey(name: 'totalCount') + int totalCount; + + @JsonKey(name: 'finishCount') + int finishCount; + + @JsonKey(name: 'objectivePrecision') // 客观题正确率 + double objectivePrecision; + + @JsonKey(name: 'subjectivePrecision') // 主观题正确率 + double subjectivePrecision; + + @JsonKey(name: 'precision') // 综合正确率 + double precision; + + @JsonKey(name: 'canMarking') + bool canMarking; + + /** 前端自定义字段 */ + @JsonKey(name: 'progressPercentage') + double progressPercentage; // 进度百分比 + + /** 前端自定义字段 */ + @JsonKey(name: 'canGoReview') + bool canGoReview; // 是否能批阅 + + MarkingTasks( + this.id, + this.className, + this.teacherName, + this.isFinish, + this.studentCount, + this.commitStudentCount, + this.totalCount, + this.finishCount, + this.objectivePrecision, + this.subjectivePrecision, + this.precision, + this.canMarking, { + this.progressPercentage = 0, + this.canGoReview = true, + this.finishTime, + }) { + try { + progressPercentage = (finishCount / totalCount) * 100; + if (progressPercentage.isNaN) + progressPercentage = 0; + else + progressPercentage = double.parse(progressPercentage.toStringAsFixed(2)); + } catch (e) { + progressPercentage = 0; + } + + canGoReview = totalCount > 0 && canMarking && !isFinish; + } + + factory MarkingTasks.fromJson(Map srcJson) => _$MarkingTasksFromJson(srcJson); + + Map toJson() => _$MarkingTasksToJson(this); +} diff --git a/marking_app/lib/common/model/job/marking_text_question_job.dart b/marking_app/lib/common/model/job/marking_text_question_job.dart new file mode 100644 index 0000000..8638ad6 --- /dev/null +++ b/marking_app/lib/common/model/job/marking_text_question_job.dart @@ -0,0 +1,98 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_job.g.dart'; + +@JsonSerializable() +class MarkingTextQuestionJob extends Object { + @JsonKey(name: 'paperId') + int paperId; + + int? nextPageIndex; // 下一个页面 + int? previousPageIndex; // 上一 + + @JsonKey(name: 'pageIndex') + int pageIndex; + + @JsonKey(name: 'taskId') + int taskId; + + @JsonKey(name: 'schoolName') + String schoolName; + + @JsonKey(name: 'className') + String className; + + @JsonKey(name: 'studentId') + int studentId; + + @JsonKey(name: 'studentName') + String studentName; + + @JsonKey(name: 'isFinish') + bool isFinish; + + @JsonKey(name: 'current') + int current; + + @JsonKey(name: 'prevId') + int prevId; + + @JsonKey(name: 'nextId') + int nextId; + + @JsonKey(name: 'questions') + List questions; + + @JsonKey(name: 'imageUrl') + String imageUrl; + + @JsonKey(name: 'commentImageUrl') + String? commentImageUrl; + + bool isFirst; + + String? lastModifyTime; + + MarkingTextQuestionJob( + this.paperId, + this.pageIndex, + this.taskId, + this.schoolName, + this.className, + this.studentId, + this.studentName, + this.isFinish, + this.current, + this.prevId, + this.nextId, + this.questions, + this.imageUrl, + this.commentImageUrl, + this.lastModifyTime, + this.isFirst, + ) { + if (lastModifyTime != null) imageUrl = imageUrl + '?$lastModifyTime'; + } + + factory MarkingTextQuestionJob.fromJson(Map srcJson) => _$MarkingTextQuestionJobFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionJobToJson(this); +} + +@JsonSerializable() +class Questions extends Object { + @JsonKey(name: 'questionNo') + String questionNo; + + @JsonKey(name: 'answer') + String answer; + + @JsonKey(name: 'score') + double? score; + + Questions(this.questionNo, this.answer, [this.score]); + + factory Questions.fromJson(Map srcJson) => _$QuestionsFromJson(srcJson); + + Map toJson() => _$QuestionsToJson(this); +} diff --git a/marking_app/lib/common/model/job/marking_text_question_job_params.dart b/marking_app/lib/common/model/job/marking_text_question_job_params.dart new file mode 100644 index 0000000..df61d94 --- /dev/null +++ b/marking_app/lib/common/model/job/marking_text_question_job_params.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_job_params.g.dart'; + +@JsonSerializable() +class MarkingTextQuestionJobParams extends Object { + @JsonKey(name: 'taskSubjectiveDetailId') + int taskSubjectiveDetailId; + + @JsonKey(name: 'score') + double score; + + @JsonKey(name: 'commentImageUrl', includeIfNull: true) + String? commentImageUrl; + + MarkingTextQuestionJobParams({this.commentImageUrl, required this.taskSubjectiveDetailId, required this.score}); + + factory MarkingTextQuestionJobParams.fromJson(Map srcJson) => + _$MarkingTextQuestionJobParamsFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionJobParamsToJson(this); +} diff --git a/marking_app/lib/common/model/job/marking_text_question_job_tab_params.dart b/marking_app/lib/common/model/job/marking_text_question_job_tab_params.dart new file mode 100644 index 0000000..5aad5b9 --- /dev/null +++ b/marking_app/lib/common/model/job/marking_text_question_job_tab_params.dart @@ -0,0 +1,34 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_job_tab_params.g.dart'; + +@JsonSerializable(includeIfNull: false) +class MarkingTextQuestionJobTabParams extends Object { + @JsonKey(name: 'taskId') + int taskId; + + @JsonKey(name: 'pageIndex') + int pageIndex; + int? nextPageIndex; // 下一个页面 + int? previousPageIndex; // 上一个页面 + + @JsonKey(name: 'paperId') + int? paperId; + + @JsonKey(name: 'studentId') + int? studentId; + + MarkingTextQuestionJobTabParams({ + required this.taskId, + required this.pageIndex, + this.paperId, + this.studentId, + this.nextPageIndex, + this.previousPageIndex, + }); + + factory MarkingTextQuestionJobTabParams.fromJson(Map srcJson) => + _$MarkingTextQuestionJobTabParamsFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionJobTabParamsToJson(this); +} diff --git a/marking_app/lib/common/model/job/review_again_list_params.dart b/marking_app/lib/common/model/job/review_again_list_params.dart new file mode 100644 index 0000000..4d7e742 --- /dev/null +++ b/marking_app/lib/common/model/job/review_again_list_params.dart @@ -0,0 +1,41 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../common/base_page.dart'; + +part 'review_again_list_params.g.dart'; + +@JsonSerializable() +class ReviewAgainListParams extends BasePage { + @JsonKey(name: 'TaskId') + int taskId; + + @JsonKey(name: 'QuestionNo') + String? questionNo; + + @JsonKey(name: 'IsFinish') + bool isFinish; + + @JsonKey(name: 'SortName') + String? sortName; + + @JsonKey(name: 'Order') + String? order; // asc desc + + @JsonKey(name: 'keyword') + String? keyword; + + ReviewAgainListParams( + {required this.taskId, required this.questionNo, this.isFinish = true, required page, required limit}) + : super(page, limit); + + factory ReviewAgainListParams.fromJson(Map srcJson) => _$ReviewAgainListParamsFromJson(srcJson); + + Map toJson() => _$ReviewAgainListParamsToJson(this); + + /// 条件赛选 + void setConditionalFiltering({String? keyword, String? sortName, String? order}) { + this.keyword = keyword; + this.sortName = sortName; + this.order = order; + } +} diff --git a/marking_app/lib/common/model/job/test_questions_image_info.dart b/marking_app/lib/common/model/job/test_questions_image_info.dart new file mode 100644 index 0000000..fed08ac --- /dev/null +++ b/marking_app/lib/common/model/job/test_questions_image_info.dart @@ -0,0 +1,66 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'test_questions_image_info.g.dart'; + +@JsonSerializable() +class TestQuestionsImageInfo extends Object { + @JsonKey(name: 'url') + String url; + + @JsonKey(name: 'boxWidth') + double boxWidth; + + @JsonKey(name: 'boxHeight') + double boxHeight; + + @JsonKey(name: 'width') + double width; + + @JsonKey(name: 'height') + double height; + + @JsonKey(name: 'pixelRatio') // 像素比 + double pixelRatio; + + @JsonKey(name: 'scale') + double? scale; + + @JsonKey(name: 'scaleHeight') + double? scaleHeight; + + @JsonKey(name: 'scaleWidth') + double? scaleWidth; + + double? imageHeightOffsetStart; + + double? imageHeightOffsetend; + + TestQuestionsImageInfo( + {required this.width, + required this.height, + required this.url, + required this.boxWidth, + required this.boxHeight, + this.pixelRatio = 1}) { + // print('图片宽度:$width'); + // print('图片高度:$height'); + + // print('容器宽度:$boxWidth'); + // print('容器高度:$boxHeight'); + + pixelRatio = width / boxWidth; + + scale = boxWidth / width; + scaleHeight = scale! * height; + scaleWidth = scale! * width; + + if (scaleHeight != null) { + imageHeightOffsetStart = (boxHeight - scaleHeight!) / 2; + imageHeightOffsetend = imageHeightOffsetStart! + scaleHeight!; + } + } + + factory TestQuestionsImageInfo.fromJson(Map srcJson) => _$TestQuestionsImageInfoFromJson(srcJson); + + Map toJson() => _$TestQuestionsImageInfoToJson(this); +} diff --git a/marking_app/lib/common/model/job/upload_file_interface_config.dart b/marking_app/lib/common/model/job/upload_file_interface_config.dart new file mode 100644 index 0000000..c6b365f --- /dev/null +++ b/marking_app/lib/common/model/job/upload_file_interface_config.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'upload_file_interface_config.g.dart'; + +@JsonSerializable() +class UploadFileInterfaceConfig extends Object { + @JsonKey(name: 'uploadMethod') + String uploadMethod; + + @JsonKey(name: 'uploadUri') + String? uploadUri; + + @JsonKey(name: 'downloadUri') + String downloadUri; + + UploadFileInterfaceConfig( + this.uploadMethod, + this.uploadUri, + this.downloadUri, + ); + + factory UploadFileInterfaceConfig.fromJson(Map srcJson) => + _$UploadFileInterfaceConfigFromJson(srcJson); + + Map toJson() => _$UploadFileInterfaceConfigToJson(this); +} diff --git a/marking_app/lib/common/model/job/upload_file_interface_config_params.dart b/marking_app/lib/common/model/job/upload_file_interface_config_params.dart new file mode 100644 index 0000000..7fc2885 --- /dev/null +++ b/marking_app/lib/common/model/job/upload_file_interface_config_params.dart @@ -0,0 +1,30 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'upload_file_interface_config_params.g.dart'; + +@JsonSerializable() +class UploadFileInterfaceConfigParams extends Object { + @JsonKey(name: 'fileName') + String fileName; + + @JsonKey(name: 'fileMd5') + String fileMd5; + + @JsonKey(name: 'contentLength') + int contentLength; + + @JsonKey(name: 'objectName', includeIfNull: false) + String? objectName; + + UploadFileInterfaceConfigParams({ + required this.fileName, + required this.fileMd5, + required this.contentLength, + this.objectName, + }); + + factory UploadFileInterfaceConfigParams.fromJson(Map srcJson) => + _$UploadFileInterfaceConfigParamsFromJson(srcJson); + + Map toJson() => _$UploadFileInterfaceConfigParamsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/annotation_graffiti_switch.dart b/marking_app/lib/common/model/marking/annotation_graffiti_switch.dart new file mode 100644 index 0000000..04028f6 --- /dev/null +++ b/marking_app/lib/common/model/marking/annotation_graffiti_switch.dart @@ -0,0 +1,43 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'annotation_graffiti_switch.g.dart'; + +// 审批涂鸦开关类 +@JsonSerializable() +class AnnotationGraffitiSwitch extends Object { + // 是否打开工具 + @JsonKey(name: 'annotationSwitch') + bool annotationSwitch; + + // 打开画笔 + @JsonKey(name: 'openBrush') + bool openBrush; + + // 打开橡皮擦 + @JsonKey(name: 'openEraser') + bool openEraser; + + // 清空全部 + @JsonKey(name: 'openClearAll') + bool openClearAll; + + // 放大镜 + @JsonKey(name: 'magnifier') + bool magnifier; + + // 笔记回显 + @JsonKey(name: 'trajectoryDisplay') + bool trajectoryDisplay; + + AnnotationGraffitiSwitch({ + this.annotationSwitch = false, + this.openBrush = false, + this.openEraser = false, + this.openClearAll = false, + this.magnifier = false, + this.trajectoryDisplay = false, + }); + factory AnnotationGraffitiSwitch.fromJson(Map srcJson) => + _$AnnotationGraffitiSwitchFromJson(srcJson); + Map toJson() => _$AnnotationGraffitiSwitchToJson(this); +} diff --git a/marking_app/lib/common/model/marking/current_review_task.dart b/marking_app/lib/common/model/marking/current_review_task.dart new file mode 100644 index 0000000..a0e195b --- /dev/null +++ b/marking_app/lib/common/model/marking/current_review_task.dart @@ -0,0 +1,32 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-09-27 14:45:27 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 16:13:57 + * @FilePath: \marking_app\lib\common\model\marking\current_review_task.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'current_review_task.g.dart'; + + +@JsonSerializable() + class CurrentReviewTask extends Object { + + @JsonKey(name: 'taskId') + int? taskId; + + // 当前任务状态 (true、需要刷新 2、不需要刷新) + @JsonKey(name: 'refresh') + bool refresh; + + CurrentReviewTask({this.taskId,this.refresh=false}); + + factory CurrentReviewTask.fromJson(Map srcJson) => _$CurrentReviewTaskFromJson(srcJson); + + Map toJson() => _$CurrentReviewTaskToJson(this); + +} + + diff --git a/marking_app/lib/common/model/marking/do_marking_keyboard_model.dart b/marking_app/lib/common/model/marking/do_marking_keyboard_model.dart new file mode 100644 index 0000000..679f801 --- /dev/null +++ b/marking_app/lib/common/model/marking/do_marking_keyboard_model.dart @@ -0,0 +1,228 @@ +// import 'package:json_annotation/json_annotation.dart'; +// import 'package:marking_app/common/model/enum/KeyboardType.dart'; + +// part 'do_marking_keyboard_model.g.dart'; + +// // 键盘操作模型 +// @JsonSerializable() +// class DoMarkingKeyboardModel extends Object { +// // 当前选中键盘 +// KeyboardType keyboard; + +// // 屏幕方向 +// ScreenDirection screenDirection; + +// // 引导页展示 +// bool guidePageDisplay; + +// // 是否关闭键盘 +// bool open; + +// // 自动提交并前往下一题 (输入型键盘无法自动提交只回自动前往下一题) +// bool autoSubmitToNextQuestion; + +// // 键盘排序方式(输入型键盘为,无排序规则) +// SortKeyboard? sort; + +// // 阅卷分值步长集合(每个试卷 每种试题分值间隔不同) key:试卷号-题号 val:分值步长 +// Map? scoreIntervalMap; + +// // 是否有辅助键盘 +// bool auxiliaryKeyboard; + +// // 打开辅助键盘 +// bool openAuxiliary; + +// // 是否隐藏试题ID +// bool hideQuestionId; + +// // 常用打分项 +// @JsonKey(name: 'commonScores', fromJson: MarkingCommonScoreItems.fromJson, includeIfNull: false) +// MarkingCommonScoreItems? commonScores; + +// // 默认输入型键盘 +// DoMarkingKeyboardModel({ +// this.hideQuestionId = false, +// this.screenDirection = ScreenDirection.HORIZONTAL_SCREEN, +// this.openAuxiliary = true, +// this.auxiliaryKeyboard = true, +// this.keyboard = KeyboardType.RIGHT_SELECTION, +// this.guidePageDisplay = true, +// this.open = true, +// this.autoSubmitToNextQuestion = true, +// this.sort = SortKeyboard.FULL_AND_AERO_TOP, +// this.scoreIntervalMap, +// this.commonScores, +// }) { +// if (keyboard == KeyboardType.INPUT_TYPE) { +// // 输入型键盘无排序规则 +// sort = null; +// } else { +// sort ??= SortKeyboard.FULL_AND_AERO_TOP; +// } +// if (keyboard == KeyboardType.BOTTOM_SELECTION) { +// auxiliaryKeyboard = false; +// } +// if (keyboard == KeyboardType.RIGHT_SELECTION) { +// open = true; +// } +// } +// factory DoMarkingKeyboardModel.fromJson(Map srcJson) => _$DoMarkingKeyboardModelFromJson(srcJson); +// Map toJson() => _$DoMarkingKeyboardModelToJson(this); + +// /// 设置步长 +// /// @param {int} markingUserId 当场考试任务ID +// /// @param {String} questionNum 题号 +// /// @param {double?} val 步长值(val为null就清空当前步长,非null和0就是设置步长) +// void setScoreStepSize(int markingUserId, String questionNum, double defaultNum, double val) { +// if (val == defaultNum) return; +// String theKey = '$markingUserId-$questionNum'; +// // val = val==0?null:val; +// // if(val==null){ +// // // 清空 +// // scoreIntervalMap!.remove(theKey); +// // }else{ +// // // 设置值 +// // scoreIntervalMap ??= {}; +// // scoreIntervalMap!['$markingUserId-$questionNum'] = val; +// // } +// // 设置值 +// scoreIntervalMap ??= {}; +// scoreIntervalMap!['$markingUserId-$questionNum'] = val; +// } + +// /// 或者试题步长 +// /// @param {int} markingUserId 试卷ID +// /// @param {String} questionNum 题号 +// /// return 没有设置就返回0,设置了返回对应的值 +// double getScoreStepSize(int markingUserId, String questionNum, double defaultNum) { +// double? val = scoreIntervalMap?['$markingUserId-$questionNum']; +// return val ?? defaultNum; +// } +// } + +// @JsonSerializable(includeIfNull: false) +// class MarkingCommonScoreItems extends Object { +// @JsonKey(name: 'id') +// int id; + +// @JsonKey(name: 'questionNum') +// String questionNum; + +// @JsonKey(name: 'score') +// List score; + +// MarkingCommonScoreItems({ +// required this.id, +// required this.questionNum, +// required this.score, +// }); + +// factory MarkingCommonScoreItems.fromJson(Map srcJson) => _$MarkingCommonScoreItemsFromJson(srcJson); + +// Map toJson() => _$MarkingCommonScoreItemsToJson(this); +// } + +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/marking_common_score_items.dart'; + +part 'do_marking_keyboard_model.g.dart'; + +// 键盘操作模型 +@JsonSerializable(includeIfNull: false, explicitToJson: true, checked: true) +class DoMarkingKeyboardModel extends Object { + // 当前选中键盘 + KeyboardType keyboard; + + // 屏幕方向 + ScreenDirection screenDirection; + + // 引导页展示 + bool guidePageDisplay; + + // 是否关闭键盘 + bool open; + + // 自动提交并前往下一题 (输入型键盘无法自动提交只回自动前往下一题) + bool autoSubmitToNextQuestion; + + // 键盘排序方式(输入型键盘为,无排序规则) + SortKeyboard? sort; + + // 阅卷分值步长集合(每个试卷 每种试题分值间隔不同) key:试卷号-题号 val:分值步长 + Map? scoreIntervalMap; + + // 是否有辅助键盘 + bool auxiliaryKeyboard; + + // 打开辅助键盘 + bool openAuxiliary; + + // 是否隐藏试题ID + bool hideQuestionId; + + // 常用打分项 + @JsonKey(name: 'commonScores', includeIfNull: false) + MarkingCommonScoreItems? commonScores; + + // 默认输入型键盘 + DoMarkingKeyboardModel({ + this.hideQuestionId = false, + this.screenDirection = ScreenDirection.HORIZONTAL_SCREEN, + this.openAuxiliary = true, + this.auxiliaryKeyboard = true, + this.keyboard = KeyboardType.RIGHT_SELECTION, + this.guidePageDisplay = true, + this.open = true, + this.autoSubmitToNextQuestion = true, + this.sort = SortKeyboard.FULL_AND_AERO_TOP, + this.scoreIntervalMap, + this.commonScores, + }) { + if (keyboard == KeyboardType.INPUT_TYPE) { + // 输入型键盘无排序规则 + sort = null; + } else { + sort ??= SortKeyboard.FULL_AND_AERO_TOP; + } + if (keyboard == KeyboardType.BOTTOM_SELECTION) { + auxiliaryKeyboard = false; + } + if (keyboard == KeyboardType.RIGHT_SELECTION) { + open = true; + } + } + factory DoMarkingKeyboardModel.fromJson(Map srcJson) => _$DoMarkingKeyboardModelFromJson(srcJson); + Map toJson() => _$DoMarkingKeyboardModelToJson(this); + + /// 设置步长 + /// @param {int} markingUserId 当场考试任务ID + /// @param {String} questionNum 题号 + /// @param {double?} val 步长值(val为null就清空当前步长,非null和0就是设置步长) + void setScoreStepSize(int markingUserId, String questionNum, double defaultNum, double val) { + if (val == defaultNum) return; + String theKey = '$markingUserId-$questionNum'; + // val = val==0?null:val; + // if(val==null){ + // // 清空 + // scoreIntervalMap!.remove(theKey); + // }else{ + // // 设置值 + // scoreIntervalMap ??= {}; + // scoreIntervalMap!['$markingUserId-$questionNum'] = val; + // } + // 设置值 + scoreIntervalMap ??= {}; + scoreIntervalMap!['$markingUserId-$questionNum'] = val; + } + + /// 或者试题步长 + /// @param {int} markingUserId 试卷ID + /// @param {String} questionNum 题号 + /// return 没有设置就返回0,设置了返回对应的值 + double getScoreStepSize(int markingUserId, String questionNum, double defaultNum) { + double? val = scoreIntervalMap?['$markingUserId-$questionNum']; + return val ?? defaultNum; + } +} diff --git a/marking_app/lib/common/model/marking/keyboard_assist_event.dart b/marking_app/lib/common/model/marking/keyboard_assist_event.dart new file mode 100644 index 0000000..d564097 --- /dev/null +++ b/marking_app/lib/common/model/marking/keyboard_assist_event.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'keyboard_assist_event.g.dart'; + +// 键盘辅助类 +@JsonSerializable() +class KeyboardAssistEvent extends Object { + + bool openAuxiliary; // 打开辅助键盘 + + KeyboardAssistEvent({this.openAuxiliary = false}); + + factory KeyboardAssistEvent.fromJson(Map srcJson) => _$KeyboardAssistEventFromJson(srcJson); + + Map toJson() => _$KeyboardAssistEventToJson(this); + +} + + diff --git a/marking_app/lib/common/model/marking/keyboard_assist_event.g.dart b/marking_app/lib/common/model/marking/keyboard_assist_event.g.dart new file mode 100644 index 0000000..13cca58 --- /dev/null +++ b/marking_app/lib/common/model/marking/keyboard_assist_event.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'keyboard_assist_event.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +KeyboardAssistEvent _$KeyboardAssistEventFromJson(Map json) => + KeyboardAssistEvent( + openAuxiliary: json['openAuxiliary'] as bool? ?? false, + ); + +Map _$KeyboardAssistEventToJson( + KeyboardAssistEvent instance) => + { + 'openAuxiliary': instance.openAuxiliary, + }; diff --git a/marking_app/lib/common/model/marking/marking_abnormal_res.dart b/marking_app/lib/common/model/marking/marking_abnormal_res.dart new file mode 100644 index 0000000..11da7f1 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_abnormal_res.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_abnormal_res.g.dart'; + +@JsonSerializable() +class MarkingAbnormalRes extends Object { + @JsonKey(name: 'errorsTypeDic') + Map errorsTypeDic; + + MarkingAbnormalRes(this.errorsTypeDic); + + factory MarkingAbnormalRes.fromJson(Map srcJson) => _$MarkingAbnormalResFromJson(srcJson); + + Map toJson() => _$MarkingAbnormalResToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_common_score_items.dart b/marking_app/lib/common/model/marking/marking_common_score_items.dart new file mode 100644 index 0000000..fe7fe39 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_common_score_items.dart @@ -0,0 +1,51 @@ +// import 'package:json_annotation/json_annotation.dart'; + +// part 'marking_common_score_items.g.dart'; + +// @JsonSerializable(includeIfNull: false) +// class MarkingCommonScoreItems extends Object { +// @JsonKey(name: 'id') +// int id; + +// @JsonKey(name: 'questionNum') +// String questionNum; + +// @JsonKey(name: 'score') +// List score; + +// MarkingCommonScoreItems({ +// required this.id, +// required this.questionNum, +// required this.score, +// }); + +// factory MarkingCommonScoreItems.fromJson(Map srcJson) => _$MarkingCommonScoreItemsFromJson(srcJson); + +// Map toJson() => _$MarkingCommonScoreItemsToJson(this); +// } + +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_common_score_items.g.dart'; + +@JsonSerializable(includeIfNull: false) +class MarkingCommonScoreItems extends Object { + @JsonKey(name: 'id') + int id; + + @JsonKey(name: 'questionNum') + String questionNum; + + @JsonKey(name: 'score') + List score; + + MarkingCommonScoreItems({ + required this.id, + required this.questionNum, + required this.score, + }); + + factory MarkingCommonScoreItems.fromJson(Map srcJson) => _$MarkingCommonScoreItemsFromJson(srcJson); + + Map toJson() => _$MarkingCommonScoreItemsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_history_zoom_info.dart b/marking_app/lib/common/model/marking/marking_history_zoom_info.dart new file mode 100644 index 0000000..0bacc02 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_history_zoom_info.dart @@ -0,0 +1,33 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_history_zoom_info.g.dart'; + +@JsonSerializable() +class MarkingHistoryZoomInfo extends Object { + @JsonKey(name: 'markingUserId') + int markingUserId; + + @JsonKey(name: 'questionNum') + String questionNum; + + @JsonKey(name: 'scale') + double scale; + + @JsonKey(name: 'positionY') + double positionY; + + @JsonKey(name: 'positionX') + double positionX; + + MarkingHistoryZoomInfo({ + required this.markingUserId, + required this.questionNum, + required this.scale, + required this.positionY, + required this.positionX, + }); + + factory MarkingHistoryZoomInfo.fromJson(Map srcJson) => _$MarkingHistoryZoomInfoFromJson(srcJson); + + Map toJson() => _$MarkingHistoryZoomInfoToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_history_zoom_info.g.dart b/marking_app/lib/common/model/marking/marking_history_zoom_info.g.dart new file mode 100644 index 0000000..07a8b6a --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_history_zoom_info.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'marking_history_zoom_info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MarkingHistoryZoomInfo _$MarkingHistoryZoomInfoFromJson( + Map json) => + MarkingHistoryZoomInfo( + markingUserId: json['markingUserId'] as int, + questionNum: json['questionNum'] as String, + scale: (json['scale'] as num).toDouble(), + positionY: (json['positionY'] as num).toDouble(), + positionX: (json['positionX'] as num).toDouble(), + ); + +Map _$MarkingHistoryZoomInfoToJson( + MarkingHistoryZoomInfo instance) => + { + 'markingUserId': instance.markingUserId, + 'questionNum': instance.questionNum, + 'scale': instance.scale, + 'positionY': instance.positionY, + 'positionX': instance.positionX, + }; diff --git a/marking_app/lib/common/model/marking/marking_item.dart b/marking_app/lib/common/model/marking/marking_item.dart new file mode 100644 index 0000000..e106ae4 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_item.dart @@ -0,0 +1,59 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-18 13:55:30 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-20 21:02:36 + * @FilePath: \marking_app\lib\common\model\marking\markingItem.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/utils/index.dart'; + +part 'marking_item.g.dart'; + +@JsonSerializable(checked: true) +class MarkingItem extends Object { + @JsonKey(name: 'markingUserId') + int markingUserId; + + @JsonKey(name: 'examSubjectId') + int examSubjectId; + + @JsonKey(name: 'isSchoolAssign') + bool isSchoolAssign; + + @JsonKey(name: 'examName') + String examName; + + @JsonKey(name: 'subjectId') + int subjectId; + + @JsonKey(name: 'finishCount') + int finishCount; + + @JsonKey(name: 'totalCount') + int totalCount; + + @JsonKey(name: 'isFinish') + bool isFinish; + + // 完成比例 + @JsonKey(name: 'completionRate') + double completionRate; + + @JsonKey(name: 'completionRateStr') + String completionRateStr; + + MarkingItem(this.examSubjectId, this.isFinish, this.markingUserId, this.examName, this.subjectId, this.finishCount, + this.totalCount, + {this.isSchoolAssign = false, this.completionRate = 0, this.completionRateStr = '0.0'}) { + if (totalCount != 0) { + completionRate = finishCount / totalCount; + completionRateStr = doubleToStringAsFixed((completionRate * 100)); + } + } + + factory MarkingItem.fromJson(Map srcJson) => _$MarkingItemFromJson(srcJson); + + Map toJson() => _$MarkingItemToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_keyboard_sliding_position.dart b/marking_app/lib/common/model/marking/marking_keyboard_sliding_position.dart new file mode 100644 index 0000000..b950270 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_keyboard_sliding_position.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_keyboard_sliding_position.g.dart'; + +@JsonSerializable() +class MarkingKeyboardSlidingPosition extends Object { + @JsonKey(name: 'id') + int id; + + @JsonKey(name: 'questionNum') + String questionNum; + + @JsonKey(name: 'position') + double position; + + @JsonKey(name: 'isVertical') + bool isVertical; // 是否是横行键盘 + + MarkingKeyboardSlidingPosition( + {required this.id, required this.questionNum, required this.position, this.isVertical = false}); + + factory MarkingKeyboardSlidingPosition.fromJson(Map srcJson) => + _$MarkingKeyboardSlidingPositionFromJson(srcJson); + + Map toJson() => _$MarkingKeyboardSlidingPositionToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_list_params.dart b/marking_app/lib/common/model/marking/marking_list_params.dart new file mode 100644 index 0000000..da455b5 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_list_params.dart @@ -0,0 +1,43 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-18 12:58:28 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 16:27:05 + * @FilePath: \marking_app\lib\common\model\marking\marking_list_params.dart + * @Description: 阅卷列表请求参数类 + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/common/base_page.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; + +part 'marking_list_params.g.dart'; + +@JsonSerializable() +class MarkingListParams extends BasePage { + @JsonKey(name: 'isFinish') + bool isFinish; + + // 阅卷类型 + @JsonKey(name: 'PageType') + int pageType; + + MarkingListParams({ + required this.isFinish, + required this.pageType, + required page, + required limit, + }) : super(page, limit); + + factory MarkingListParams.fromJson(Map srcJson) => _$MarkingListParamsFromJson(srcJson); + + @override + Map toJson() => _$MarkingListParamsToJson(this); + + setPagetype(MarkingListType typeEnum) { + this.pageType = typeEnum.index; + } + + MarkingListType getPagetype() { + return MarkingListType.values[pageType]; + } +} diff --git a/marking_app/lib/common/model/marking/marking_statistics.dart b/marking_app/lib/common/model/marking/marking_statistics.dart new file mode 100644 index 0000000..6b5166d --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_statistics.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_statistics.g.dart'; + +@JsonSerializable() +class MarkingStatistics extends Object { + @JsonKey(name: 'total') + int total; + + @JsonKey(name: 'navId') + int navld; + + MarkingStatistics( + this.total, + this.navld, + ); + + factory MarkingStatistics.fromJson(Map srcJson) => _$MarkingStatisticsFromJson(srcJson); + + Map toJson() => _$MarkingStatisticsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_submit_result.dart b/marking_app/lib/common/model/marking/marking_submit_result.dart new file mode 100644 index 0000000..177562a --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_submit_result.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/marking/progress_of_marking.dart'; + +part 'marking_submit_result.g.dart'; + + +@JsonSerializable() + class MarkingSubmitResult extends Object { + + @JsonKey(name: 'marking') + bool marking; + + @JsonKey(name: 'markingProgress') + ProgressOfMarking markingProgress; + + MarkingSubmitResult(this.marking,this.markingProgress,); + + factory MarkingSubmitResult.fromJson(Map srcJson) => _$MarkingSubmitResultFromJson(srcJson); + + Map toJson() => _$MarkingSubmitResultToJson(this); + +} + + + diff --git a/marking_app/lib/common/model/marking/marking_tag_single_params.dart b/marking_app/lib/common/model/marking/marking_tag_single_params.dart new file mode 100644 index 0000000..ae59327 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_tag_single_params.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_tag_single_params.g.dart'; + +@JsonSerializable() +class MarkingTagSingleParams extends Object { + @JsonKey(name: 'markingUserId') + int markingUserId; + + @JsonKey(name: 'questionNum') + String questionNum; + + @JsonKey(name: 'isExcess') + bool isExcess; + + MarkingTagSingleParams({required this.markingUserId,required this.questionNum,required this.isExcess}); + + factory MarkingTagSingleParams.fromJson(Map srcJson) => _$MarkingTagSingleParamsFromJson(srcJson); + + Map toJson() => _$MarkingTagSingleParamsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_text_question.dart b/marking_app/lib/common/model/marking/marking_text_question.dart new file mode 100644 index 0000000..f206ab9 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_text_question.dart @@ -0,0 +1,294 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 15:15:29 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 11:48:00 + * @FilePath: \marking_app\lib\common\model\marking\marking_text_question.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:convert'; + +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/marking/progress_of_marking.dart'; +import 'package:marking_app/utils/image/gallery_example_item_model.dart'; + +part 'marking_text_question.g.dart'; + +@JsonSerializable(checked: true) +class MarkingTextQuestion extends Object { + @JsonKey(name: 'id') + int id; + + @JsonKey(name: 'isException') + bool isException; + + @JsonKey(name: 'score') + double? score; + + @JsonKey(name: 'totalScore') + double totalScore; + + @JsonKey(name: 'isFinish') + bool isFinish; + + @JsonKey(name: 'questionNum') + String questionNum; + + // 分值间隔 前端自定义 + @JsonKey(name: 'scoreInterval') + double scoreInterval; + + // 学生试题图片 + @JsonKey(name: 'studentAnswerList') + List studentAnswerList; + + // 子题(小题) + @JsonKey(name: 'subQuestionDetailList') + List subQuestionDetailList; + + // 当前试题位置 + // @JsonKey(name: 'questionIndex') + // int questionIndex; + + // @JsonKey(name: 'totalCount') + // int totalCount; + + // // 前端自定义 + // @JsonKey(name: 'currentIndex') + // int currentIndex; + + @JsonKey(name: 'lastOne') + bool lastOne; + + @JsonKey(name: 'nextId') + int nextId; + + @JsonKey(name: 'prevId') + int prevId; + + @JsonKey(name: 'paperNum') + String? paperNum; + + // 当前ID (用于获取下一道天或者上一道题) + // @JsonKey(name: 'markingUserDetailId') + // int markingUserDetailId; + + // 自定义 是否评分 ,默认false + @JsonKey(name: 'completeRating') + bool completeRating; + + // 整卷图片 目前后端没有返回字段 + @JsonKey(name: 'originalPaperAbsoluteUrl') + List originalPaperAbsoluteUrl; + + // 阅卷进度 + @JsonKey(name: 'markingProgress') + ProgressOfMarking? markingProgress; + + // 批注图片集合 + @JsonKey(name: 'commentImageUrl') + List commentImageUrl; + + // 原卷数组 前端自定义 + List papersUrl; + + // 原卷地址 前端自定义 + @JsonKey(name: 'papersUrlStr') + List? papersUrlStr; + + // 批注图片 预提交集合 + @JsonKey(name: 'commentImageUrlTodo') + List? commentImageUrlTodo; + + // 批注图片Map + @JsonKey(name: 'commentImageUrlMap') + Map commentImageUrlMap; + + // 仲裁历史得分 + @JsonKey(name: 'historicalScore') + String? historicalScore; + + @JsonKey(name: 'exceptionInfo') + ExceptionInfo? exceptionInfo; + + // 仲裁历史得分 ==》 + @JsonKey(name: 'historicalScorings') + List? historicalScorings; + + MarkingTextQuestion(this.id, this.totalScore, this.isFinish, this.subQuestionDetailList, this.isException, + this.questionNum, this.score, this.studentAnswerList, + // this.questionIndex, + {required this.commentImageUrl, + required this.lastOne, + required this.nextId, + required this.prevId, + this.historicalScore, + this.scoreInterval = 1, + this.markingProgress, + this.completeRating = false, + this.originalPaperAbsoluteUrl = const [], + // this.totalCount = 0, + // this.currentIndex = 0, + this.papersUrl = const [], + this.commentImageUrlTodo, + this.commentImageUrlMap = const {}}) { + score = isFinish ? score : null; + completeRating = isFinish && score != null; + // totalCount = markingProgress?.totalCount ?? totalCount; + // currentIndex = markingProgress?.nowIndex ?? currentIndex; + // currentIndex = questionIndex; + commentImageUrlMap = Map(); + + if (commentImageUrl.isNotEmpty) { + for (var element in commentImageUrl) { + commentImageUrlMap[element] = '$element?${DateTime.now().millisecondsSinceEpoch}'; + } + } else { + for (var element in studentAnswerList) { + commentImageUrlMap[element] = element; + } + } + // if (historicalScore != null) { + // this.historicalScorings = (jsonDecode(historicalScore!) as List).map((e) { + // return HistoricalScoring.fromJson(e as Map); + // }).toList(); + // } + // TODO 删除 + // this.historicalScorings = [ + // HistoricalScoring.fromJson({'scorel': 1.5, 'name': 'wy', 'markingUserld': 121321231313}), + // HistoricalScoring.fromJson({'scorel': 5, 'name': '汪杨', 'markingUserld': 121321231313}) + // ]; + // print('这个是仲裁数据:${this.historicalScorings.toString()}'); + // exceptionInfo = ExceptionInfo.fromJson({'reason': "4325432", 'errorType': "其他", 'submitUserName': "陈川蓉"}); + } + + factory MarkingTextQuestion.fromJson(Map srcJson) => _$MarkingTextQuestionFromJson(srcJson); + + List getImageData() { + if (commentImageUrlTodo != null && commentImageUrlTodo!.isNotEmpty) { + return commentImageUrlTodo!; + } + + if (commentImageUrl.isNotEmpty) { + return commentImageUrl; + } + + return studentAnswerList; + } + + bool setImageList(String resImage, int imageIndex) { + try { + commentImageUrlTodo ??= getImageData().map((e) => e).toList(); + String oldImage = studentAnswerList[imageIndex]; + commentImageUrlMap[oldImage] = '$resImage?${DateTime.now().millisecondsSinceEpoch}'; + commentImageUrlTodo![imageIndex] = resImage; + return true; + } catch (e) { + return false; + } + } + + /// @param account 总数 + /// @param index 下标 + void setTotalCountAndCurrentIndex(int account, int index) { + // totalCount = account; + // currentIndex = index; + } +} + +// 子题(小题) +@JsonSerializable() +class SubQuestions extends Object { + // 总分 + @JsonKey(name: 'subQuestionScore') + double subQuestionScore; + + // 得分 + @JsonKey(name: 'subQuestionGotScore') + double? subQuestionGotScore; + + // 小题题号 + @JsonKey(name: 'subQuestionNum') + String subQuestionNum; + + // JS端判断是否得分 判断0分使用,dart 不需要(也可作为是否已经提交判断) + @JsonKey(name: 'isFinish') + bool isFinish; + + // 自定义 是否评分 ,默认false + @JsonKey(name: 'completeRating') + bool completeRating; + + SubQuestions( + {required this.subQuestionScore, + required this.subQuestionNum, + this.subQuestionGotScore, + required this.isFinish, + this.completeRating = false}) { + subQuestionGotScore = isFinish ? subQuestionGotScore : null; + completeRating = isFinish && subQuestionGotScore != null; + } + + factory SubQuestions.fromJson(Map srcJson) => _$SubQuestionsFromJson(srcJson); + Map toJson() => _$SubQuestionsToJson(this); +} + +@JsonSerializable(checked: true) +class HistoricalScoring extends Object { + @JsonKey(name: 'markingUserId') + int markingUserld; + + @JsonKey(name: 'name') + String name; + + @JsonKey(name: 'score') + double scorel; + + HistoricalScoring( + this.markingUserld, + this.name, + this.scorel, + ); + + factory HistoricalScoring.fromJson(Map srcJson) => _$HistoricalScoringFromJson(srcJson); + + Map toJson() => _$HistoricalScoringToJson(this); +} + +@JsonSerializable() +class ExceptionInfo extends Object { + @JsonKey(name: 'reason') + String? reason; + + @JsonKey(name: 'errorType') + String errorType; + + @JsonKey(name: 'submitUserName') + String submitUserName; + + @JsonKey(name: 'studentName') + String studentName; + + @JsonKey(name: 'studentExamNum') + String studentExamNum; + + @JsonKey(name: 'paperNum') + String paperNum; + + ExceptionInfo( + this.reason, + this.errorType, + this.submitUserName, + this.paperNum, + this.studentExamNum, + this.studentName, + ) { + if (reason?.length == 0) { + reason = null; + } + } + + factory ExceptionInfo.fromJson(Map srcJson) => _$ExceptionInfoFromJson(srcJson); + + Map toJson() => _$ExceptionInfoToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_text_question_params.dart b/marking_app/lib/common/model/marking/marking_text_question_params.dart new file mode 100644 index 0000000..36c7fbb --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_text_question_params.dart @@ -0,0 +1,42 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 15:19:54 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-27 12:26:15 + * @FilePath: \marking_app\lib\common\model\marking\marking_text_question_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_params.g.dart'; + +@JsonSerializable(includeIfNull: false) +class MarkingTextQuestionParams extends Object { + @JsonKey(name: 'markingUserId') + int markingUserId; + + @JsonKey(name: 'MarkingUserDetailId') + int? markingUserDetailId; + + @JsonKey(name: 'type') + int? type; + + @JsonKey(name: 'isReview') + bool isReview; + + @JsonKey(name: 'pageType') + int? pageType; + + MarkingTextQuestionParams({ + required this.markingUserId, + this.pageType, + this.markingUserDetailId, + this.type, + this.isReview = false, + }); + + factory MarkingTextQuestionParams.fromJson(Map srcJson) => + _$MarkingTextQuestionParamsFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionParamsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_text_question_tab.dart b/marking_app/lib/common/model/marking/marking_text_question_tab.dart new file mode 100644 index 0000000..209b900 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_text_question_tab.dart @@ -0,0 +1,99 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_tab.g.dart'; + +@JsonSerializable() +class MarkingTextQuestionTab extends Object { + @JsonKey(name: 'questionNum') + String questionNum; // 批次题号 + + @JsonKey(name: 'total') + int total; // 当前批次试题总数(领取的任务数量) + + // @JsonKey(name: 'excessAvgCount') + // int excessAvgCount; // 阅卷指标 + + @JsonKey(name: 'finishCount') + int finishCount; // 当前批次完成数量 + + @JsonKey(name: 'isExcess') + bool isExcess; // 是否是超量题 + + @JsonKey(name: 'isFinished') + bool isFinished; // 是否是超量题 + + @JsonKey(name: 'scoreInterval') + double? scoreInterval; // 自定义 ==》 当前TAG下对应步长 + + // @JsonKey(name: 'agreementExcess') + // bool? agreementExcess; // 自定义 ==》 是否同意批阅超量题 + + // 进度条 完成百分比 + @JsonKey(name: 'percent') + double percent; + + // @JsonKey(name: 'excessCountTotal') + // int? excessCountTotal; // 前端自定义 ==》 超量题总数量 + + MarkingTextQuestionTab({ + required this.questionNum, + required this.total, + required this.finishCount, + // this.excessCount = 0, + this.isExcess = false, + // this.excessAvgCount = 0, + this.scoreInterval, + // this.agreementExcess, + this.percent = 0.0, + this.isFinished = false, + }) { + initGradingProgress(); + } + + void initGradingProgress() { + try { + /** 取消字段不需要判断位置了 + if (isExcess) { + // 超量题 + dividendTotal = excessAvgCount; // 默认以指标数为被除数 + if (finishCount >= excessAvgCount) { + percent = 1; + return; + /** + // 完成数量大于指标 + if (agreementExcess == null || agreementExcess == false) { + // 没有同意继续批阅的试题返回1 + percent = 1; + return; + } + + if (excessCount > 0) { + // 题池中还有试题 + dividendTotal = total + excessCount; // 任务总量加题池剩余重量 作为被除数 + } else { + // 题池中没有试题 + if (finishCount == total) { + percent = 1; + return; + } + dividendTotal = total; + } */ + } + } */ + // print('这里是数据:${finishCount};${dividendTotal}'); + percent = isFinished || (isExcess && finishCount >= total) ? 1 : finishCount / total; + if (percent > 1) { + percent = 1; + } + // TODO 这里数据"percent"可能返回大于1 需要埋点 + } catch (e) { + percent = 0.0; + } + } + + setStepSize(double stepSize) => this.scoreInterval = stepSize; + + factory MarkingTextQuestionTab.fromJson(Map srcJson) => _$MarkingTextQuestionTabFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionTabToJson(this); +} diff --git a/marking_app/lib/common/model/marking/marking_text_question_tab_params.dart b/marking_app/lib/common/model/marking/marking_text_question_tab_params.dart new file mode 100644 index 0000000..dc14888 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_text_question_tab_params.dart @@ -0,0 +1,43 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 15:19:54 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-27 12:26:15 + * @FilePath: \marking_app\lib\common\model\marking\marking_text_question_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; + +part 'marking_text_question_tab_params.g.dart'; + +@JsonSerializable(includeIfNull: false) +class MarkingTextQuestionTabParams extends Object { + @JsonKey(name: 'markingUserId') + int markingUserId; + + @JsonKey(name: 'MarkingUserDetailId') + int? markingUserDetailId; + + @JsonKey(name: 'QuestionNum') + String? questionNum; + + MarkingTextQuestionTabParams({ + required this.markingUserId, + required this.questionNum, + this.markingUserDetailId, + }); + + factory MarkingTextQuestionTabParams.fromJson(Map srcJson) => + _$MarkingTextQuestionTabParamsFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionTabParamsToJson(this); + + MarkingTextQuestionTabParams setQuestionNum({required MarkingTextQuestionTab tab, String? locaQuestionNum}) { + this.questionNum = tab.questionNum; + if (locaQuestionNum != null && questionNum != locaQuestionNum) { + this.markingUserDetailId = 0; + } + return this; + } +} diff --git a/marking_app/lib/common/model/marking/marking_text_question_tab_step_size.dart b/marking_app/lib/common/model/marking/marking_text_question_tab_step_size.dart new file mode 100644 index 0000000..93cc5f6 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_text_question_tab_step_size.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_tab_step_size.g.dart'; + + +@JsonSerializable() + class MarkingTextQuestionTabStepSize extends Object { + + @JsonKey(name: 'questionNum') + String questionNum; // 批次题号 + + @JsonKey(name: 'scoreInterval') + double scoreInterval; // 超量题词数量 + + MarkingTextQuestionTabStepSize(this.questionNum,this.scoreInterval); + + factory MarkingTextQuestionTabStepSize.fromJson(Map srcJson) => _$MarkingTextQuestionTabStepSizeFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionTabStepSizeToJson(this); + + +} + + diff --git a/marking_app/lib/common/model/marking/marking_zoom.dart b/marking_app/lib/common/model/marking/marking_zoom.dart new file mode 100644 index 0000000..3efd7f4 --- /dev/null +++ b/marking_app/lib/common/model/marking/marking_zoom.dart @@ -0,0 +1,29 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_zoom.g.dart'; + +@JsonSerializable() +class MarkingZoom extends Object { + @JsonKey(name: 'questionNumber') + String questionNumber; + + @JsonKey(name: 'scale') + double scale; + + @JsonKey(name: 'dx') + double dx; + + @JsonKey(name: 'dy') + double dy; + + MarkingZoom({ + required this.scale, + required this.dx, + required this.dy, + required this.questionNumber, + }); + + factory MarkingZoom.fromJson(Map srcJson) => _$MarkingZoomFromJson(srcJson); + + Map toJson() => _$MarkingZoomToJson(this); +} diff --git a/marking_app/lib/common/model/marking/progress_of_marking.dart b/marking_app/lib/common/model/marking/progress_of_marking.dart new file mode 100644 index 0000000..0d30c25 --- /dev/null +++ b/marking_app/lib/common/model/marking/progress_of_marking.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'progress_of_marking.g.dart'; + +@JsonSerializable() +class ProgressOfMarking extends Object { + + @JsonKey(name: 'totalCount') + int totalCount; + + @JsonKey(name: 'nowIndex') + int nowIndex; + + + ProgressOfMarking(this.totalCount,this.nowIndex); + + factory ProgressOfMarking.fromJson(Map srcJson) => + _$ProgressOfMarkingFromJson(srcJson); + + Map toJson() => _$ProgressOfMarkingToJson(this); +} diff --git a/marking_app/lib/common/model/marking/rating_progress_model.dart b/marking_app/lib/common/model/marking/rating_progress_model.dart new file mode 100644 index 0000000..6727419 --- /dev/null +++ b/marking_app/lib/common/model/marking/rating_progress_model.dart @@ -0,0 +1,29 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'rating_progress_model.g.dart'; + +@JsonSerializable() +class RatingProgressModel extends Object { + @JsonKey(name: 'questionNum') + String questionNum; + + @JsonKey(name: 'avgScore') + double avgScore; + + @JsonKey(name: 'schedule') + double schedule; + + @JsonKey(name: 'myAvgScore') + double myAvgScore; + + RatingProgressModel( + this.questionNum, + this.avgScore, + this.schedule, + this.myAvgScore, + ); + + factory RatingProgressModel.fromJson(Map srcJson) => _$RatingProgressModelFromJson(srcJson); + + Map toJson() => _$RatingProgressModelToJson(this); +} diff --git a/marking_app/lib/common/model/marking/review_records_item.dart b/marking_app/lib/common/model/marking/review_records_item.dart new file mode 100644 index 0000000..df8836e --- /dev/null +++ b/marking_app/lib/common/model/marking/review_records_item.dart @@ -0,0 +1,33 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'review_records_item.g.dart'; + +@JsonSerializable() +class ReviewRecordsItem extends Object { + @JsonKey(name: 'markingUserDetailId') + int markingUserDetailId; + + @JsonKey(name: 'paperNum') + String paperNum; + + @JsonKey(name: 'finishTime') + String finishTime; + + @JsonKey(name: 'sort') + int sort; + + @JsonKey(name: 'score') + double score; + + ReviewRecordsItem( + this.markingUserDetailId, + this.paperNum, + this.finishTime, + this.sort, + this.score, + ); + + factory ReviewRecordsItem.fromJson(Map srcJson) => _$ReviewRecordsItemFromJson(srcJson); + + Map toJson() => _$ReviewRecordsItemToJson(this); +} diff --git a/marking_app/lib/common/model/marking/review_records_params.dart b/marking_app/lib/common/model/marking/review_records_params.dart new file mode 100644 index 0000000..8ab23bf --- /dev/null +++ b/marking_app/lib/common/model/marking/review_records_params.dart @@ -0,0 +1,20 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/common/base_page.dart'; + +part 'review_records_params.g.dart'; + +@JsonSerializable() +class ReviewRecordsParams extends BasePage { + @JsonKey(name: 'QuestionNum') + String questionNum; + + @JsonKey(name: 'markingUserId') + int markingUserId; + + ReviewRecordsParams({required page, required limit, required this.questionNum, required this.markingUserId}) + : super(page, limit); + + factory ReviewRecordsParams.fromJson(Map srcJson) => _$ReviewRecordsParamsFromJson(srcJson); + + Map toJson() => _$ReviewRecordsParamsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/submit_exam_abnormal_params.dart b/marking_app/lib/common/model/marking/submit_exam_abnormal_params.dart new file mode 100644 index 0000000..1077e70 --- /dev/null +++ b/marking_app/lib/common/model/marking/submit_exam_abnormal_params.dart @@ -0,0 +1,30 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'submit_exam_abnormal_params.g.dart'; + +@JsonSerializable() +class SubmitExamAbnormalParams extends Object { + @JsonKey(name: 'markingUserDetailId') + int markingUserDetailId; + + @JsonKey(name: 'markingUserId') + int markingUserId; + + @JsonKey(name: 'reason') + String reason; + + @JsonKey(name: 'errorType') + int errorType; + + SubmitExamAbnormalParams({ + required this.markingUserDetailId, + required this.markingUserId, + required this.reason, + required this.errorType, + }); + + factory SubmitExamAbnormalParams.fromJson(Map srcJson) => + _$SubmitExamAbnormalParamsFromJson(srcJson); + + Map toJson() => _$SubmitExamAbnormalParamsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/submit_exam_params.dart b/marking_app/lib/common/model/marking/submit_exam_params.dart new file mode 100644 index 0000000..1e3f3d3 --- /dev/null +++ b/marking_app/lib/common/model/marking/submit_exam_params.dart @@ -0,0 +1,76 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 15:32:18 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-22 15:36:53 + * @FilePath: \marking_app\lib\common\model\marking\submit_exam_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/marking/submit_exam_abnormal_params.dart'; +import 'package:marking_app/common/model/marking/submit_exam_small_params.dart'; + +part 'submit_exam_params.g.dart'; + +@JsonSerializable(includeIfNull: false, explicitToJson: true) +class SubmitExamParams extends Object { + // 任务ID + @JsonKey(name: 'markingUserId') + int markingUserId; + + // 当前试题ID + @JsonKey(name: 'markingUserDetailId') + int markingUserDetailId; + + @JsonKey(name: 'isReview') + bool isReview; + + // 是否异常 + @JsonKey(name: 'isException') + bool isException; + + // 得分 + @JsonKey(name: 'score') + double? score; + + @JsonKey(name: 'commentImageUrl') + String? commentImageUrl; + + // 试题 + @JsonKey(name: 'subQuestionDetailList') + List? subQuestionDetailList; + + @JsonKey(name: 'questionNum') + String? questionNum; + + @JsonKey(name: 'isExcess') + bool? isExcess; + + @JsonKey(name: 'excessContinue') + bool? excessContinue; + + @JsonKey(name: 'pageType') + int? pageType; + + // @JsonKey(name: 'errorRecord', fromJson: SubmitExamAbnormalParams.fromJson, includeIfNull: false) + // SubmitExamAbnormalParams? errorRecord; + + SubmitExamParams( + this.markingUserId, + this.markingUserDetailId, { + this.score, + this.subQuestionDetailList, + this.isException = false, + this.isReview = false, + this.commentImageUrl, + this.questionNum, + this.isExcess, + this.excessContinue = true, + this.pageType, + // this.errorRecord, + }); + + factory SubmitExamParams.fromJson(Map srcJson) => _$SubmitExamParamsFromJson(srcJson); + + Map toJson() => _$SubmitExamParamsToJson(this); +} diff --git a/marking_app/lib/common/model/marking/submit_exam_small_params.dart b/marking_app/lib/common/model/marking/submit_exam_small_params.dart new file mode 100644 index 0000000..1a851cb --- /dev/null +++ b/marking_app/lib/common/model/marking/submit_exam_small_params.dart @@ -0,0 +1,35 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 15:33:34 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-27 10:14:28 + * @FilePath: \marking_app\lib\common\model\marking\submit_exam_small_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'submit_exam_small_params.g.dart'; + + +@JsonSerializable() + class SubmitExamSmallParams extends Object { + + @JsonKey(name: 'subQuestionNum') + String subQuestionNum; + + @JsonKey(name: 'subQuestionScore') + double subQuestionScore; + + @JsonKey(name: 'subQuestionGotScore') + double subQuestionGotScore; + + @JsonKey(name: 'isFinish') + bool isFinish; + + SubmitExamSmallParams(this.subQuestionNum,this.subQuestionScore,this.subQuestionGotScore,this.isFinish); + + factory SubmitExamSmallParams.fromJson(Map srcJson) => _$SubmitExamSmallParamsFromJson(srcJson); + + Map toJson() => _$SubmitExamSmallParamsToJson(this); + +} \ No newline at end of file diff --git a/marking_app/lib/common/model/marking/switch_keyboard_to_reload_images.dart b/marking_app/lib/common/model/marking/switch_keyboard_to_reload_images.dart new file mode 100644 index 0000000..01f5640 --- /dev/null +++ b/marking_app/lib/common/model/marking/switch_keyboard_to_reload_images.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'switch_keyboard_to_reload_images.g.dart'; + +@JsonSerializable() +class SwitchKeyboardToReloadImages extends Object { + @JsonKey(name: 'reload ') + bool reload; + + SwitchKeyboardToReloadImages( + this.reload, + ); + + factory SwitchKeyboardToReloadImages.fromJson(Map srcJson) => + _$SwitchKeyboardToReloadImagesFromJson(srcJson); + + Map toJson() => _$SwitchKeyboardToReloadImagesToJson(this); +} diff --git a/marking_app/lib/common/model/progress/progress_item.dart b/marking_app/lib/common/model/progress/progress_item.dart new file mode 100644 index 0000000..535cc51 --- /dev/null +++ b/marking_app/lib/common/model/progress/progress_item.dart @@ -0,0 +1,56 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-20 15:06:38 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 18:59:16 + * @FilePath: \marking_app\lib\common\model\progress\progress_item.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'progress_item.g.dart'; + +@JsonSerializable() +class ProgressItem extends Object { + @JsonKey(name: 'teacherName') + String teacherName; + + @JsonKey(name: 'avgScore') + double avgScore; + + @JsonKey(name: 'schoolName') + String schoolName; + + @JsonKey(name: 'totalCount') + int totalCount; + + @JsonKey(name: 'finishCount') + int finishCount; + + // 完成比例 + @JsonKey(name: 'completionRate') + double completionRate; + + @JsonKey(name: 'completionRateStr') + String completionRateStr; + + ProgressItem( + this.teacherName, + this.finishCount, + this.totalCount, + this.avgScore, + this.schoolName, { + this.completionRate = 0, + this.completionRateStr = '0.0', + }) { + if (totalCount != 0) { + completionRate = finishCount / totalCount; + completionRateStr = (completionRate * 100).toStringAsFixed(2); + } + } + + factory ProgressItem.fromJson(Map srcJson) => + _$ProgressItemFromJson(srcJson); + + Map toJson() => _$ProgressItemToJson(this); +} diff --git a/marking_app/lib/common/model/progress/progress_page_params.dart b/marking_app/lib/common/model/progress/progress_page_params.dart new file mode 100644 index 0000000..5d917d0 --- /dev/null +++ b/marking_app/lib/common/model/progress/progress_page_params.dart @@ -0,0 +1,29 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-20 15:09:30 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-20 15:13:23 + * @FilePath: \marking_app\lib\common\model\progress\progress_page_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/common/base_page.dart'; + +part 'progress_page_params.g.dart'; + + +@JsonSerializable() + class ProgressPageParams extends BasePage { + + @JsonKey(name: 'examSubjectId') + int examSubjectId; + + ProgressPageParams({required this.examSubjectId,required page,required limit}):super(page,limit); + + factory ProgressPageParams.fromJson(Map srcJson) => _$ProgressPageParamsFromJson(srcJson); + + Map toJson() => _$ProgressPageParamsToJson(this); + +} + + diff --git a/marking_app/lib/common/model/progress/progress_statistics.dart b/marking_app/lib/common/model/progress/progress_statistics.dart new file mode 100644 index 0000000..464fb0f --- /dev/null +++ b/marking_app/lib/common/model/progress/progress_statistics.dart @@ -0,0 +1,57 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-20 15:20:11 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-26 16:42:09 + * @FilePath: \marking_app\lib\common\model\progress\progress_statistics.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'progress_statistics.g.dart'; + +@JsonSerializable() +class ProgressStatistics extends Object { + + @JsonKey(name: 'markingTotal') + int markingTotal; + + @JsonKey(name: 'markingFinishedCount') + int markingFinishedCount; + + @JsonKey(name: 'markingUnfinishedCount') + int markingUnfinishedCount; + + @JsonKey(name: 'overallAverage') + double overallAverage; + + @JsonKey(name: 'myAverage') + double myAverage; + + // 完成比例 + @JsonKey(name: 'completionRate') + double completionRate; + + @JsonKey(name: 'completionRateStr') + String completionRateStr; + + ProgressStatistics( + this.markingUnfinishedCount, + this.markingFinishedCount, + this.myAverage, + this.markingTotal, + this.overallAverage, { + this.completionRate = 0, + this.completionRateStr = '0.0', + }) { + if (markingTotal != 0) { + completionRate = markingFinishedCount / markingTotal; + completionRateStr = (completionRate * 100).toStringAsFixed(2); + } + } + + factory ProgressStatistics.fromJson(Map srcJson) => + _$ProgressStatisticsFromJson(srcJson); + + Map toJson() => _$ProgressStatisticsToJson(this); +} diff --git a/marking_app/lib/common/model/report/marked_item.dart b/marking_app/lib/common/model/report/marked_item.dart new file mode 100644 index 0000000..95393f5 --- /dev/null +++ b/marking_app/lib/common/model/report/marked_item.dart @@ -0,0 +1,39 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marked_item.g.dart'; + +@JsonSerializable() +class MarkedItem extends Object { + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'UserId') + int userId; + + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'Type') + int type; + + @JsonKey(name: 'TypeName') + String typeName; + + @JsonKey(name: 'Subjects') + String subjects; + + @JsonKey(name: 'StartTime') + String startTime; + + @JsonKey(name: 'Status') + int status; + + @JsonKey(name: 'StatusName') + String statusName; + + MarkedItem(this.id, this.userId, this.name, this.type, this.typeName, this.subjects, this.startTime, this.status, this.statusName); + + factory MarkedItem.fromJson(Map srcJson) => _$MarkedItemFromJson(srcJson); + + Map toJson() => _$MarkedItemToJson(this); +} diff --git a/marking_app/lib/common/model/report/marked_item_params.dart b/marking_app/lib/common/model/report/marked_item_params.dart new file mode 100644 index 0000000..3d29ab3 --- /dev/null +++ b/marking_app/lib/common/model/report/marked_item_params.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/common/base_page_report.dart'; + +part 'marked_item_params.g.dart'; + +@JsonSerializable() +class MarkedItemParams extends BasePageReport { + + // 角色 + @JsonKey(name: 'PositionId') + int roleId; + + MarkedItemParams(super.page, super.limit, {required this.roleId}); + + factory MarkedItemParams.fromJson(Map srcJson) => _$MarkedItemParamsFromJson(srcJson); + + Map toJson() => _$MarkedItemParamsToJson(this); +} diff --git a/marking_app/lib/common/model/report/radar_chart_model.dart b/marking_app/lib/common/model/report/radar_chart_model.dart new file mode 100644 index 0000000..be46b73 --- /dev/null +++ b/marking_app/lib/common/model/report/radar_chart_model.dart @@ -0,0 +1,63 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'radar_chart_model.g.dart'; + +@JsonSerializable() +class RadarChartModel extends Object { + RadarChartModel(); + + factory RadarChartModel.fromJson(Map srcJson) => _$RadarChartModelFromJson(srcJson); + + Map toJson() => _$RadarChartModelToJson(this); +} + + +@JsonSerializable() +class RawDataSet extends Object { + @JsonKey(name: 'name') + String name; + + @JsonKey(name: 'value') + List value; + + @JsonKey(name: 'itemStyle') + ItemStyle? itemStyle; + + RawDataSet({required this.name, required this.value,this.itemStyle}); + + factory RawDataSet.fromJson(Map srcJson) => _$RawDataSetFromJson(srcJson); + + Map toJson() => _$RawDataSetToJson(this); +} + + + +@JsonSerializable() + class ItemStyle extends Object { + + @JsonKey(name: 'color') + String color; + + ItemStyle({required this.color}); + + factory ItemStyle.fromJson(Map srcJson) => _$ItemStyleFromJson(srcJson); + + Map toJson() => _$ItemStyleToJson(this); + +} + + +@JsonSerializable() +class RawTitle extends Object { + @JsonKey(name: 'name') + String name; + + @JsonKey(name: 'max') + double max; + + RawTitle({required this.name, required this.max}); + + factory RawTitle.fromJson(Map srcJson) => _$RawTitleFromJson(srcJson); + + Map toJson() => _$RawTitleToJson(this); +} diff --git a/marking_app/lib/common/model/report/report_for_class_teacher_model.dart b/marking_app/lib/common/model/report/report_for_class_teacher_model.dart new file mode 100644 index 0000000..e650513 --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_class_teacher_model.dart @@ -0,0 +1,263 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/report/report_for_subject_teacher_model.dart'; +import 'package:marking_app/common/model/report/report_histogram_model.dart'; + +part 'report_for_class_teacher_model.g.dart'; + + +@JsonSerializable(checked: true) + class ReportForClassTeacherModel extends Object { + + @JsonKey(name: 'BaseInfo') + BaseInfo baseInfo; + + @JsonKey(name: 'ExamTagDistributionList') + List examTagDistributionList; + + @JsonKey(name: 'AvgComparison') + AvgComparison avgComparison; + + @JsonKey(name: 'YearLevel') + YearLevel yearLevel; + + @JsonKey(name: 'OverallLevel') + OverallLevel overallLevel; + + @JsonKey(name: 'StudentInfo') + StudentInfo studentInfo; + + @JsonKey(name: 'SubjectRadarMap') + SubjectRadarMap subjectRadarMap; + + List averageModels; + + ReportForClassTeacherModel(this.baseInfo,this.examTagDistributionList,this.avgComparison,this.yearLevel,this.overallLevel,this.studentInfo,this.subjectRadarMap,{this.averageModels=const []}); + + factory ReportForClassTeacherModel.fromJson(Map srcJson) => _$ReportForClassTeacherModelFromJson(srcJson); + + Map toJson() => _$ReportForClassTeacherModelToJson(this); + +} + + +@JsonSerializable(checked: true) + class BaseInfo extends Object { + + @JsonKey(name: 'ExamName') + String examName; + + @JsonKey(name: 'ExamType') + String examType; + + @JsonKey(name: 'Grade') + String grade; + + @JsonKey(name: 'Subjects') + List subjects; + + @JsonKey(name: 'TotalScore') + double totalScore; + + @JsonKey(name: 'Average') + double average; + + @JsonKey(name: 'MaxScore') + double maxScore; + + @JsonKey(name: 'Excellent') + String excellent; + + @JsonKey(name: 'Qualified') + String qualified; + + @JsonKey(name: 'ExamTime') + String examTime; + + BaseInfo(this.examName,this.examType,this.grade,this.subjects,this.totalScore,this.average,this.maxScore,this.excellent,this.qualified,this.examTime,); + + factory BaseInfo.fromJson(Map srcJson) => _$BaseInfoFromJson(srcJson); + + Map toJson() => _$BaseInfoToJson(this); + +} + + +@JsonSerializable(checked: true) + class Subjects extends Object { + + @JsonKey(name: 'SubjectId') + int subjectId; + + @JsonKey(name: 'SubjectName') + String subjectName; + + Subjects(this.subjectId,this.subjectName,); + + factory Subjects.fromJson(Map srcJson) => _$SubjectsFromJson(srcJson); + + Map toJson() => _$SubjectsToJson(this); + +} + + + + +@JsonSerializable(checked: true) + class ExamTagDistributionList extends Object { + + @JsonKey(name: 'TagId') + int tagId; + + @JsonKey(name: 'TagName') + String tagName; + + @JsonKey(name: 'Number') + int number; + + @JsonKey(name: 'NumberRate') + String numberRate; + + ExamTagDistributionList(this.tagId,this.tagName,this.number,this.numberRate,); + + factory ExamTagDistributionList.fromJson(Map srcJson) => _$ExamTagDistributionListFromJson(srcJson); + + Map toJson() => _$ExamTagDistributionListToJson(this); + +} + + +@JsonSerializable(checked: true) + class AvgComparison extends Object { + + @JsonKey(name: 'AvgScore') + double avgScore; + + @JsonKey(name: 'AvgComparisonList') + List avgComparisonList; + + AvgComparison(this.avgScore,this.avgComparisonList,); + + factory AvgComparison.fromJson(Map srcJson) => _$AvgComparisonFromJson(srcJson); + + Map toJson() => _$AvgComparisonToJson(this); + +} + + +@JsonSerializable(checked: true) + class AvgComparisonList extends Object { + + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'Score') + double score; + + AvgComparisonList(this.id,this.name,this.score,); + + factory AvgComparisonList.fromJson(Map srcJson) => _$AvgComparisonListFromJson(srcJson); + + Map toJson() => _$AvgComparisonListToJson(this); + +} + + +@JsonSerializable(checked: true) + class YearLevel extends Object { + + @JsonKey(name: 'Actor') + int actor; + + @JsonKey(name: 'Ranking') + int ranking; + + YearLevel(this.actor,this.ranking,); + + factory YearLevel.fromJson(Map srcJson) => _$YearLevelFromJson(srcJson); + + Map toJson() => _$YearLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class OverallLevel extends Object { + + @JsonKey(name: 'Actor') + int actor; + + @JsonKey(name: 'Ranking') + int ranking; + + OverallLevel(this.actor,this.ranking,); + + factory OverallLevel.fromJson(Map srcJson) => _$OverallLevelFromJson(srcJson); + + Map toJson() => _$OverallLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class StudentInfo extends Object { + + @JsonKey(name: 'Heads') + List heads; + + @JsonKey(name: 'Data') + List> data; + + StudentInfo(this.heads,this.data,); + + factory StudentInfo.fromJson(Map srcJson) => _$StudentInfoFromJson(srcJson); + + Map toJson() => _$StudentInfoToJson(this); + +} + + +@JsonSerializable(checked: true) + class SubjectRadarMap extends Object { + + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'ExamSubjects') + List examSubjects; + + SubjectRadarMap(this.id,this.name,this.examSubjects,); + + factory SubjectRadarMap.fromJson(Map srcJson) => _$SubjectRadarMapFromJson(srcJson); + + Map toJson() => _$SubjectRadarMapToJson(this); + +} + + +@JsonSerializable(checked: true) + class ExamSubjects extends Object { + + @JsonKey(name: 'Subject') + String subject; + + @JsonKey(name: 'Score') + double score; + + @JsonKey(name: 'TotalScore') + double totalScore; + + ExamSubjects(this.subject,this.score,this.totalScore,); + + factory ExamSubjects.fromJson(Map srcJson) => _$ExamSubjectsFromJson(srcJson); + + Map toJson() => _$ExamSubjectsToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_for_class_teacher_params.dart b/marking_app/lib/common/model/report/report_for_class_teacher_params.dart new file mode 100644 index 0000000..61a2437 --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_class_teacher_params.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_class_teacher_params.g.dart'; + + +@JsonSerializable() + class ReportForClassTeacherParams extends Object { + + @JsonKey(name: 'ExamId') + int examId; + + + @JsonKey(name: 'CompareAll') + bool compareAll; + + ReportForClassTeacherParams({required this.examId,required this.compareAll}); + + factory ReportForClassTeacherParams.fromJson(Map srcJson) => _$ReportForClassTeacherParamsFromJson(srcJson); + + Map toJson() => _$ReportForClassTeacherParamsToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_for_marking_pagerdetail_model.dart b/marking_app/lib/common/model/report/report_for_marking_pagerdetail_model.dart new file mode 100644 index 0000000..597f16e --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_marking_pagerdetail_model.dart @@ -0,0 +1,20 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_marking_pagerdetail_model.g.dart'; + + +@JsonSerializable() + class ReportForMarkingPagerdetailModel extends Object { + + @JsonKey(name: 'MarkingPagerImgs') + List markingPagerImgs; + + ReportForMarkingPagerdetailModel(this.markingPagerImgs); + + factory ReportForMarkingPagerdetailModel.fromJson(Map srcJson) => _$ReportForMarkingPagerdetailModelFromJson(srcJson); + + Map toJson() => _$ReportForMarkingPagerdetailModelToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_for_marking_pagerdetail_params.dart b/marking_app/lib/common/model/report/report_for_marking_pagerdetail_params.dart new file mode 100644 index 0000000..3918541 --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_marking_pagerdetail_params.dart @@ -0,0 +1,29 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_marking_pagerdetail_params.g.dart'; + +@JsonSerializable() +class ReportForMarkingPagerdetailParams extends Object { + @JsonKey(name: 'ExamSubjectSchoolId') + int examSubjectSchoolId; + + @JsonKey(name: 'ExamSubjectId') + int examSubjectId; + + @JsonKey(name: 'ExamInfoId') + int examInfoId; + + @JsonKey(name: 'StudentExamNum') + String studentExamNum; + + ReportForMarkingPagerdetailParams({ + required this.examSubjectSchoolId, + required this.examSubjectId, + required this.examInfoId, + required this.studentExamNum, + }); + + factory ReportForMarkingPagerdetailParams.fromJson(Map srcJson) => _$ReportForMarkingPagerdetailParamsFromJson(srcJson); + + Map toJson() => _$ReportForMarkingPagerdetailParamsToJson(this); +} diff --git a/marking_app/lib/common/model/report/report_for_subject_student_model.dart b/marking_app/lib/common/model/report/report_for_subject_student_model.dart new file mode 100644 index 0000000..fb7e44a --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_subject_student_model.dart @@ -0,0 +1,417 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_subject_student_model.g.dart'; + + +@JsonSerializable(checked: true) + class ReportForSubjectStudentModel extends Object { + + @JsonKey(name: 'BaseInfo') + BaseInfo baseInfo; + + @JsonKey(name: 'ClassLevel') + ClassLevel classLevel; + + @JsonKey(name: 'YearLevel') + YearLevel yearLevel; + + @JsonKey(name: 'OverallLevel') + OverallLevel overallLevel; + + @JsonKey(name: 'ScoreTrends') + List scoreTrends; + + @JsonKey(name: 'RankingTrends') + List rankingTrends; + + @JsonKey(name: 'TagAnalysis') + List tagAnalysis; + + @JsonKey(name: 'KnowledgeAnalysisList') + List knowledgeAnalysisList; + + @JsonKey(name: 'MethodAnalysisList') + List methodAnalysisList; + + @JsonKey(name: 'AnswerDetails') + List answerDetails; + + ReportForSubjectStudentModel(this.baseInfo,this.classLevel,this.yearLevel,this.overallLevel,this.scoreTrends,this.rankingTrends,this.tagAnalysis,this.knowledgeAnalysisList,this.methodAnalysisList,this.answerDetails,); + + factory ReportForSubjectStudentModel.fromJson(Map srcJson) => _$ReportForSubjectStudentModelFromJson(srcJson); + + Map toJson() => _$ReportForSubjectStudentModelToJson(this); + +} + + +@JsonSerializable(checked: true) + class BaseInfo extends Object { + + @JsonKey(name: 'Subject') + Subject subject; + + @JsonKey(name: 'TotalScore') + double totalScore; + + @JsonKey(name: 'Score') + double score; + + @JsonKey(name: 'Rating') + String rating; + + @JsonKey(name: 'StartTime') + String startTime; + + BaseInfo(this.subject,this.totalScore,this.score,this.rating,this.startTime,); + + factory BaseInfo.fromJson(Map srcJson) => _$BaseInfoFromJson(srcJson); + + Map toJson() => _$BaseInfoToJson(this); + +} + + +@JsonSerializable(checked: true) + class Subject extends Object { + + @JsonKey(name: 'SubjectId') + int subjectId; + + @JsonKey(name: 'SubjectName') + String subjectName; + + Subject(this.subjectId,this.subjectName,); + + factory Subject.fromJson(Map srcJson) => _$SubjectFromJson(srcJson); + + Map toJson() => _$SubjectToJson(this); + +} + + +@JsonSerializable(checked: true) + class ClassLevel extends Object { + + @JsonKey(name: 'Actor') + int actor; + + @JsonKey(name: 'Ranking') + int ranking; + + @JsonKey(name: 'Lack') + int lack; + + @JsonKey(name: 'Exceed') + String exceed; + + @JsonKey(name: 'Average') + double average; + + @JsonKey(name: 'Max') + double max; + + ClassLevel(this.actor,this.ranking,this.lack,this.exceed,this.average,this.max,); + + factory ClassLevel.fromJson(Map srcJson) => _$ClassLevelFromJson(srcJson); + + Map toJson() => _$ClassLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class YearLevel extends Object { + + @JsonKey(name: 'Actor') + int actor; + + @JsonKey(name: 'Ranking') + int ranking; + + @JsonKey(name: 'Lack') + int lack; + + @JsonKey(name: 'Exceed') + String exceed; + + @JsonKey(name: 'Average') + double average; + + @JsonKey(name: 'Max') + double max; + + YearLevel(this.actor,this.ranking,this.lack,this.exceed,this.average,this.max,); + + factory YearLevel.fromJson(Map srcJson) => _$YearLevelFromJson(srcJson); + + Map toJson() => _$YearLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class OverallLevel extends Object { + + @JsonKey(name: 'Actor') + int actor; + + @JsonKey(name: 'Ranking') + int ranking; + + @JsonKey(name: 'Lack') + int lack; + + @JsonKey(name: 'Exceed') + String exceed; + + @JsonKey(name: 'Average') + double average; + + @JsonKey(name: 'Max') + double max; + + OverallLevel(this.actor,this.ranking,this.lack,this.exceed,this.average,this.max,); + + factory OverallLevel.fromJson(Map srcJson) => _$OverallLevelFromJson(srcJson); + + Map toJson() => _$OverallLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class ScoreTrends extends Object { + + @JsonKey(name: 'ExamName') + String examName; + + @JsonKey(name: 'ExamTime') + String examTime; + + @JsonKey(name: 'UserScore') + double userScore; + + @JsonKey(name: 'ClassScore') + double classScore; + + @JsonKey(name: 'GradeScore') + double gradeScore; + + @JsonKey(name: 'AllScore') + double allScore; + + ScoreTrends(this.examName,this.examTime,this.userScore,this.classScore,this.gradeScore,this.allScore,); + + factory ScoreTrends.fromJson(Map srcJson) => _$ScoreTrendsFromJson(srcJson); + + Map toJson() => _$ScoreTrendsToJson(this); + +} + + +@JsonSerializable(checked: true) + class RankingTrends extends Object { + + @JsonKey(name: 'ClassRanking') + double classRanking; + + @JsonKey(name: 'GradeRanking') + double gradeRanking; + + @JsonKey(name: 'TotalRanking') + double totalRanking; + + @JsonKey(name: 'ExamName') + String examName; + + @JsonKey(name: 'ExamTime') + String examTime; + + RankingTrends(this.classRanking,this.gradeRanking,this.totalRanking,this.examName,this.examTime,); + + factory RankingTrends.fromJson(Map srcJson) => _$RankingTrendsFromJson(srcJson); + + Map toJson() => _$RankingTrendsToJson(this); + +} + + +@JsonSerializable(checked: true) + class TagAnalysis extends Object { + + @JsonKey(name: 'QuestionLevel') + int questionLevel; + + @JsonKey(name: 'QuestionLevelTitle') + String questionLevelTitle; + + @JsonKey(name: 'QuestionNums') + String questionNums; + + @JsonKey(name: 'CorrectRate') + String correctRate; + + @JsonKey(name: 'ScoringRate') + String scoringRate; + + TagAnalysis(this.questionLevel,this.questionLevelTitle,this.questionNums,this.correctRate,this.scoringRate,); + + factory TagAnalysis.fromJson(Map srcJson) => _$TagAnalysisFromJson(srcJson); + + Map toJson() => _$TagAnalysisToJson(this); + +} + + +@JsonSerializable(checked: true) + class KnowledgeAnalysisList extends Object { + + @JsonKey(name: 'Questions') + List questions; + + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'QuestionNums') + String questionNums; + + @JsonKey(name: 'AllCorrectRate') + String allCorrectRate; + + @JsonKey(name: 'ClassCorrectRate') + String classCorrectRate; + + @JsonKey(name: 'GradeCorrectRate') + String gradeCorrectRate; + + @JsonKey(name: 'UserCorrectRate') + String userCorrectRate; + + KnowledgeAnalysisList(this.questions,this.name,this.id,this.questionNums,this.allCorrectRate,this.classCorrectRate,this.gradeCorrectRate,this.userCorrectRate,); + + factory KnowledgeAnalysisList.fromJson(Map srcJson) => _$KnowledgeAnalysisListFromJson(srcJson); + + Map toJson() => _$KnowledgeAnalysisListToJson(this); + +} + + +@JsonSerializable(checked: true) + class Questions extends Object { + + @JsonKey(name: 'QuestionNum') + String questionNum; + + @JsonKey(name: 'QuestionId') + int questionId; + + Questions(this.questionNum,this.questionId,); + + factory Questions.fromJson(Map srcJson) => _$QuestionsFromJson(srcJson); + + Map toJson() => _$QuestionsToJson(this); + +} + + +@JsonSerializable(checked: true) + class MethodAnalysisList extends Object { + + @JsonKey(name: 'Questions') + List questions; + + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'QuestionNums') + String questionNums; + + @JsonKey(name: 'AllCorrectRate') + String allCorrectRate; + + @JsonKey(name: 'ClassCorrectRate') + String classCorrectRate; + + @JsonKey(name: 'GradeCorrectRate') + String gradeCorrectRate; + + @JsonKey(name: 'UserCorrectRate') + String userCorrectRate; + + MethodAnalysisList(this.questions,this.name,this.id,this.questionNums,this.allCorrectRate,this.classCorrectRate,this.gradeCorrectRate,this.userCorrectRate,); + + factory MethodAnalysisList.fromJson(Map srcJson) => _$MethodAnalysisListFromJson(srcJson); + + Map toJson() => _$MethodAnalysisListToJson(this); + +} + + + +@JsonSerializable(checked: true) + class AnswerDetails extends Object { + + @JsonKey(name: 'No') + String no; + + @JsonKey(name: 'Title') + String title; + + @JsonKey(name: 'Type') + String type; + + @JsonKey(name: 'Answer') + String answer; + + @JsonKey(name: 'YouAnswer') + String youAnswer; + + @JsonKey(name: 'QuestionId') + int questionId; + + @JsonKey(name: 'AnswerParse') + String answerParse; + + @JsonKey(name: 'QuestionChoiceOptions') + List? questionChoiceOptions; + + AnswerDetails(this.no,this.title,this.type,this.answer,this.youAnswer,this.questionId,this.answerParse,this.questionChoiceOptions,); + + factory AnswerDetails.fromJson(Map srcJson) => _$AnswerDetailsFromJson(srcJson); + + Map toJson() => _$AnswerDetailsToJson(this); + +} + + +@JsonSerializable(checked: true) + class QuestionChoiceOptions extends Object { + + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'QuestionId') + int questionId; + + @JsonKey(name: 'OptionValue') + String optionValue; + + @JsonKey(name: 'OptionContent') + String optionContent; + + QuestionChoiceOptions(this.id,this.questionId,this.optionValue,this.optionContent,); + + factory QuestionChoiceOptions.fromJson(Map srcJson) => _$QuestionChoiceOptionsFromJson(srcJson); + + Map toJson() => _$QuestionChoiceOptionsToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_for_subject_student_params.dart b/marking_app/lib/common/model/report/report_for_subject_student_params.dart new file mode 100644 index 0000000..2d1d530 --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_subject_student_params.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_subject_student_params.g.dart'; + +@JsonSerializable(checked: true) +class ReportForSubjectStudentParams extends Object { + @JsonKey(name: 'ExamId') + int examId; + + @JsonKey(name: 'UserId') + int userId; + + @JsonKey(name: 'Subject') + int subject; + + ReportForSubjectStudentParams({required this.examId, required this.userId, required this.subject}); + + factory ReportForSubjectStudentParams.fromJson(Map srcJson) => _$ReportForSubjectStudentParamsFromJson(srcJson); + + Map toJson() => _$ReportForSubjectStudentParamsToJson(this); +} diff --git a/marking_app/lib/common/model/report/report_for_subject_teacher_model.dart b/marking_app/lib/common/model/report/report_for_subject_teacher_model.dart new file mode 100644 index 0000000..9ab1b86 --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_subject_teacher_model.dart @@ -0,0 +1,327 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_subject_teacher_model.g.dart'; + + +@JsonSerializable(checked: true) + class ReportForSubjectTeacherModel extends Object { + + @JsonKey(name: 'BaseInfo') + BaseInfo baseInfo; + + @JsonKey(name: 'GradeLevel') + GradeLevel gradeLevel; + + @JsonKey(name: 'AllLevel') + AllLevel allLevel; + + @JsonKey(name: 'StudentRankInfoTopList') + List studentRankInfoTopList; // 大幅进步 + + @JsonKey(name: 'StudentRankInfoFallList') + List studentRankInfoFallList; // 大幅退步 + + @JsonKey(name: 'StudentResultInfoTopList') + List studentResultInfoTopList; // 单个学生 + + @JsonKey(name: 'StudentResultInfoFallList') + List studentResultInfoFallList; + + @JsonKey(name: 'QuestionList') + List questionList; + + @JsonKey(name: 'KnowledgeAnalysisList') + List knowledgeAnalysisList; + + @JsonKey(name: 'MethodAnalysisList') + List methodAnalysisList; + + @JsonKey(name: 'StudentDetailList') + List studentDetailList; + + @JsonKey(name: 'QuestionTagsAnalyseList') + List questionTagsAnalyseList; + + ReportForSubjectTeacherModel(this.baseInfo,this.gradeLevel,this.allLevel,this.studentRankInfoTopList,this.studentRankInfoFallList,this.studentResultInfoTopList,this.studentResultInfoFallList,this.questionList,this.knowledgeAnalysisList,this.methodAnalysisList,this.studentDetailList,this.questionTagsAnalyseList,); + + factory ReportForSubjectTeacherModel.fromJson(Map srcJson) => _$ReportForSubjectTeacherModelFromJson(srcJson); + + Map toJson() => _$ReportForSubjectTeacherModelToJson(this); + +} + + +@JsonSerializable(checked: true) + class BaseInfo extends Object { + + @JsonKey(name: 'ExamName') + String examName; + + @JsonKey(name: 'ExamTypeName') + String examTypeName; + + @JsonKey(name: 'Grade') + String grade; + + @JsonKey(name: 'Subjects') + String subjects; + + @JsonKey(name: 'ExamTime') + String examTime; + + @JsonKey(name: 'TotalScore') + double totalScore; + + @JsonKey(name: 'AvgScore') + double avgScore; + + @JsonKey(name: 'MaxScore') + double maxScore; + + @JsonKey(name: 'GoodRate') + String goodRate; + + @JsonKey(name: 'GoodRateDesc') + String goodRateDesc; + + @JsonKey(name: 'PassRate') + String passRate; + + @JsonKey(name: 'PassRateDesc') + String passRateDesc; + + BaseInfo(this.examName,this.examTypeName,this.grade,this.subjects,this.examTime,this.totalScore,this.avgScore,this.maxScore,this.goodRate,this.goodRateDesc,this.passRate,this.passRateDesc,); + + factory BaseInfo.fromJson(Map srcJson) => _$BaseInfoFromJson(srcJson); + + Map toJson() => _$BaseInfoToJson(this); + +} + + +@JsonSerializable(checked: true) + class GradeLevel extends Object { + + @JsonKey(name: 'JoinClassCount') + int joinClassCount; + + @JsonKey(name: 'Ranking') + int ranking; + + GradeLevel(this.joinClassCount,this.ranking,); + + factory GradeLevel.fromJson(Map srcJson) => _$GradeLevelFromJson(srcJson); + + Map toJson() => _$GradeLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class AllLevel extends Object { + + @JsonKey(name: 'JoinClassCount') + int joinClassCount; + + @JsonKey(name: 'Ranking') + int ranking; + + AllLevel(this.joinClassCount,this.ranking,); + + factory AllLevel.fromJson(Map srcJson) => _$AllLevelFromJson(srcJson); + + Map toJson() => _$AllLevelToJson(this); + +} + + +@JsonSerializable(checked: true) + class StudentRankInfoTopList extends Object { + + @JsonKey(name: 'ExamNo') + String examNo; + + @JsonKey(name: 'StudentName') + String studentName; + + @JsonKey(name: 'Ranking') + int ranking; + + @JsonKey(name: 'RankChange') + int rankChange; + + StudentRankInfoTopList(this.examNo,this.studentName,this.ranking,this.rankChange,); + + factory StudentRankInfoTopList.fromJson(Map srcJson) => _$StudentRankInfoTopListFromJson(srcJson); + + Map toJson() => _$StudentRankInfoTopListToJson(this); + +} + + +@JsonSerializable(checked: true) + class StudentRankInfoFallList extends Object { + + @JsonKey(name: 'ExamNo') + String examNo; + + @JsonKey(name: 'StudentName') + String studentName; + + @JsonKey(name: 'Ranking') + int ranking; + + @JsonKey(name: 'RankChange') + int rankChange; + + StudentRankInfoFallList(this.examNo,this.studentName,this.ranking,this.rankChange,); + + factory StudentRankInfoFallList.fromJson(Map srcJson) => _$StudentRankInfoFallListFromJson(srcJson); + + Map toJson() => _$StudentRankInfoFallListToJson(this); + +} + + +@JsonSerializable(checked: true) + class StudentResultInfoTopList extends Object { + + @JsonKey(name: 'StudentName') + String studentName; + + @JsonKey(name: 'Result') + String result; + + StudentResultInfoTopList(this.studentName,this.result,); + + factory StudentResultInfoTopList.fromJson(Map srcJson) => _$StudentResultInfoTopListFromJson(srcJson); + + Map toJson() => _$StudentResultInfoTopListToJson(this); + +} + + +@JsonSerializable(checked: true) + class StudentResultInfoFallList extends Object { + + @JsonKey(name: 'StudentName') + String studentName; + + @JsonKey(name: 'Result') + String result; + + StudentResultInfoFallList(this.studentName,this.result,); + + factory StudentResultInfoFallList.fromJson(Map srcJson) => _$StudentResultInfoFallListFromJson(srcJson); + + Map toJson() => _$StudentResultInfoFallListToJson(this); + +} + + +@JsonSerializable(checked: true) + class QuestionList extends Object { + + @JsonKey(name: 'QuestionId') + int questionId; + + @JsonKey(name: 'QuestionNum') + String questionNum; + + @JsonKey(name: 'KownledgeIds') + String kownledgeIds; + + @JsonKey(name: 'KownledgeName') + String kownledgeName; + + @JsonKey(name: 'MethodIds') + String methodIds; + + @JsonKey(name: 'MethodName') + String methodName; + + @JsonKey(name: 'CorrectRate') + String correctRate; + + @JsonKey(name: 'CorrectAnswerDetailList') + List correctAnswerDetailList; + + @JsonKey(name: 'AnswerDetailList') + List answerDetailList; + + QuestionList(this.questionId,this.questionNum,this.kownledgeIds,this.kownledgeName,this.methodIds,this.methodName,this.correctRate,this.correctAnswerDetailList,this.answerDetailList,); + + factory QuestionList.fromJson(Map srcJson) => _$QuestionListFromJson(srcJson); + + Map toJson() => _$QuestionListToJson(this); + +} + + + + +@JsonSerializable(checked: true) + class AnswerDetailModel extends Object { + + @JsonKey(name: 'Option') + String option; + + @JsonKey(name: 'CheckCount') + int checkCount; + + AnswerDetailModel(this.checkCount,this.option,); + + factory AnswerDetailModel.fromJson(Map srcJson) => _$AnswerDetailModelFromJson(srcJson); + + Map toJson() => _$AnswerDetailModelToJson(this); + +} + + + + +@JsonSerializable(checked: true) + class StudentDetailList extends Object { + + @JsonKey(name: 'UserId') + int userId; + + @JsonKey(name: 'StudentExamNum') + String studentExamNum; + + @JsonKey(name: 'ExamSubjectSchoolId') + int examSubjectSchoolId; + + @JsonKey(name: 'ExamSubjectId') + int examSubjectId; + + @JsonKey(name: 'SubjectId') + int subjectId; + + @JsonKey(name: 'ExamId') + int examId; + + @JsonKey(name: 'ExamInfoId') + int examInfoId; + + @JsonKey(name: 'StudentName') + String studentName; + + @JsonKey(name: 'Score') + String score; + + @JsonKey(name: 'Ranking') + int ranking; + + @JsonKey(name: 'Step') + String step; + + StudentDetailList(this.userId,this.studentExamNum,this.examSubjectSchoolId,this.examSubjectId,this.subjectId,this.examId,this.examInfoId,this.studentName,this.score,this.ranking,this.step,); + + factory StudentDetailList.fromJson(Map srcJson) => _$StudentDetailListFromJson(srcJson); + + Map toJson() => _$StudentDetailListToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_for_subject_teacher_params.dart b/marking_app/lib/common/model/report/report_for_subject_teacher_params.dart new file mode 100644 index 0000000..9b36d8f --- /dev/null +++ b/marking_app/lib/common/model/report/report_for_subject_teacher_params.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_for_subject_teacher_params.g.dart'; + + +@JsonSerializable() + class ReportForSubjectTeacherParams extends Object { + + @JsonKey(name: 'ExamId') + int examId; + + @JsonKey(name: 'ClassId') + int classId; + + @JsonKey(name: 'Subject') + int subject; + + ReportForSubjectTeacherParams({required this.examId,required this.classId,required this.subject}); + + factory ReportForSubjectTeacherParams.fromJson(Map srcJson) => _$ReportForSubjectTeacherParamsFromJson(srcJson); + + Map toJson() => _$ReportForSubjectTeacherParamsToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_histogram_model.dart b/marking_app/lib/common/model/report/report_histogram_model.dart new file mode 100644 index 0000000..8bfb483 --- /dev/null +++ b/marking_app/lib/common/model/report/report_histogram_model.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'report_histogram_model.g.dart'; + + +@JsonSerializable(checked: true) + class ReportHistogramModel extends Object { + + @JsonKey(name: 'name') + String name; + + @JsonKey(name: 'val') + double val; + + ReportHistogramModel({required this.name,required this.val}); + + factory ReportHistogramModel.fromJson(Map srcJson) => _$ReportHistogramModelFromJson(srcJson); + + Map toJson() => _$ReportHistogramModelToJson(this); + +} + + diff --git a/marking_app/lib/common/model/report/report_home_model.dart b/marking_app/lib/common/model/report/report_home_model.dart new file mode 100644 index 0000000..ce2cb8d --- /dev/null +++ b/marking_app/lib/common/model/report/report_home_model.dart @@ -0,0 +1,146 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/utils/index.dart'; + +part 'report_home_model.g.dart'; + +@JsonSerializable(checked: true) +class ReportHomeModel extends Object { + @JsonKey(name: 'ExamData') + ExamData examData; + + @JsonKey(name: 'ExamSubjectDatas') + List examSubjectDatas; + + ReportHomeModel( + this.examData, + this.examSubjectDatas, + ); + + factory ReportHomeModel.fromJson(Map srcJson) => _$ReportHomeModelFromJson(srcJson); + + Map toJson() => _$ReportHomeModelToJson(this); +} + +@JsonSerializable() +class ExamData extends Object { + @JsonKey(name: 'ExamId') + int examId; + + @JsonKey(name: 'ExamName') + String examName; + + @JsonKey(name: 'ExamType') + int examType; + + @JsonKey(name: 'ExamTypeName') + String examTypeName; + + @JsonKey(name: 'Grade') + String grade; + + @JsonKey(name: 'Subjects') + String subjects; + + @JsonKey(name: 'AverageScore') + double averageScore; + + @JsonKey(name: 'TotalScore') + double totalScore; + + @JsonKey(name: 'TagComparisonList') + List tagComparisonList; + + @JsonKey(name: 'StartTime') + String startTime; + + ExamData( + this.examId, + this.examName, + this.examType, + this.examTypeName, + this.grade, + this.subjects, + this.averageScore, + this.totalScore, + this.tagComparisonList, + this.startTime, + ); + + factory ExamData.fromJson(Map srcJson) => _$ExamDataFromJson(srcJson); + + Map toJson() => _$ExamDataToJson(this); +} + +@JsonSerializable() +class TagComparisonList extends Object { + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'Count') + double count; + + TagComparisonList( + this.name, + this.count, + ); + + factory TagComparisonList.fromJson(Map srcJson) => _$TagComparisonListFromJson(srcJson); + + Map toJson() => _$TagComparisonListToJson(this); +} + +@JsonSerializable() +class ExamSubjectDatas extends Object { + @JsonKey(name: 'SubjectId') + int subjectId; + + @JsonKey(name: 'SubjectName') + String subjectName; + + @JsonKey(name: 'AverageScore') + double averageScore; + + @JsonKey(name: 'TotalScore') + double totalScore; + + @JsonKey(name: 'PassRate') + double passRate; + + @JsonKey(name: 'passPercent') + double passPercent; + + @JsonKey(name: 'averagePercent') + double averagePercent; + + @JsonKey(name: 'AverageScoreStr') + String averageScoreStr; + + @JsonKey(name: 'TotalScoreStr') + String totalScoreStr; + + @JsonKey(name: 'PassRateStr') + String passRateStr; + + ExamSubjectDatas( + this.subjectId, + this.subjectName, + this.averageScore, + this.totalScore, + this.passRate, { + this.averageScoreStr = '', + this.totalScoreStr = '', + this.passRateStr = '', + this.passPercent = 0, + this.averagePercent = 0, + }) { + averageScoreStr = doubleToStringAsFixed(averageScore); + totalScoreStr = doubleToStringAsFixed(totalScore); + passRateStr = doubleToStringAsFixed(passRate); + passPercent = passRate/100; + averagePercent = averageScore/totalScore; + } + + factory ExamSubjectDatas.fromJson(Map srcJson) => _$ExamSubjectDatasFromJson(srcJson); + + Map toJson() => _$ExamSubjectDatasToJson(this); +} diff --git a/marking_app/lib/common/model/review/additional_conditions_for_review.dart b/marking_app/lib/common/model/review/additional_conditions_for_review.dart new file mode 100644 index 0000000..d1202db --- /dev/null +++ b/marking_app/lib/common/model/review/additional_conditions_for_review.dart @@ -0,0 +1,102 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'additional_conditions_for_review.g.dart'; + +// 回评外加条件 +@JsonSerializable() +class AdditionalConditionsForReview extends Object { + // 排序规则集合 + static List collationArr = [ + CollationItem( + title: '评阅时间:近>远', type: 1, field: OrderField.TIME, rule: RuleEnum.ASC), + CollationItem( + title: '评阅时间:远>近', type: 2, field: OrderField.TIME, rule: RuleEnum.DESC), + CollationItem( + title: '评阅分值:高>低', type: 3, field: OrderField.SCORE, rule: RuleEnum.DESC), + CollationItem( + title: '评阅分值:低>高', type: 4, field: OrderField.SCORE, rule: RuleEnum.ASC), + ]; + + @JsonKey(name: 'questionId') + String? questionId; + + @JsonKey(name: 'markingTimeOrderType') + int? markingTimeOrderType; // 评阅时间排序方式 0:从远到近 1:从近到远 + + @JsonKey(name: 'scoreOrderType') + int? scoreOrderType; // 分数排序方式 0:从低到高 1:从高到低 + + @JsonKey(name: 'orderItem') + CollationItem? orderItem; // 选中排序规则 + + Future cleanCollation() async { + orderItem = null; + markingTimeOrderType = null; + scoreOrderType = null; + questionId = null; + } + + // 同步实例化实例数据 + void synchronizationInstance(CollationItem? item, String? testId) { + orderItem = item; + questionId = testId; + + if (item != null) { + RuleEnum rule = item.rule; + OrderField field = item.field; + + switch (field) { + case OrderField.TIME: + // 评阅时间排序方式 0:从远到近 1:从近到远 + markingTimeOrderType = RuleEnum.DESC == rule ? 0 : 1; + break; + case OrderField.SCORE: + // 分数排序方式 0:从低到高 1:从高到低 + scoreOrderType = RuleEnum.DESC == rule ? 1 : 0; + break; + } + } + } + + AdditionalConditionsForReview( + {this.questionId, + this.markingTimeOrderType, + this.scoreOrderType, + this.orderItem}); + + factory AdditionalConditionsForReview.fromJson( + Map srcJson) => + _$AdditionalConditionsForReviewFromJson(srcJson); + + Map toJson() => _$AdditionalConditionsForReviewToJson(this); +} + +// 排序方式实体ITEM +@JsonSerializable() +class CollationItem extends Object { + String title; + OrderField field; + RuleEnum rule; + int type; + + CollationItem({ + required this.title, + required this.field, + required this.rule, + required this.type, + }); + + factory CollationItem.fromJson(Map srcJson) => + _$CollationItemFromJson(srcJson); + + Map toJson() => _$CollationItemToJson(this); +} + +// 排序字段枚举 +enum OrderField { TIME, SCORE } + +// 排序方式枚举 +enum RuleEnum { DESC, ASC } + +// 条件确定回调 +typedef CollationCallback = Future Function( + {required bool reset, CollationItem? item, String? testId}); diff --git a/marking_app/lib/common/model/review/review_item.dart b/marking_app/lib/common/model/review/review_item.dart new file mode 100644 index 0000000..a70f768 --- /dev/null +++ b/marking_app/lib/common/model/review/review_item.dart @@ -0,0 +1,57 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-21 14:03:51 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 18:10:43 + * @FilePath: \marking_app\lib\common\model\review\review_item.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'review_item.g.dart'; + +@JsonSerializable(checked: true) +class ReviewItem extends Object { + @JsonKey(name: 'markingUserDetailId') + String markingUserDetailId; + + @JsonKey(name: 'score') + double score; + + @JsonKey(name: 'finishedTime') + String finishedTime; + + @JsonKey(name: 'isException') + bool isException; + + @JsonKey(name: 'isFinish') + bool isFinish; + + @JsonKey(name: 'studentAnswerList') + List studentAnswerList; + + @JsonKey(name: 'questionNum') + String questionNum; + + @JsonKey(name: 'answer') + String? answer; + + @JsonKey(name: 'questionNo') + String? questionNo; + + @JsonKey(name: 'studentName') + String? studentName; + + ReviewItem(this.markingUserDetailId, this.questionNum, this.score, this.finishedTime, this.isFinish, this.answer, + this.questionNo, this.studentName, + {this.isException = false, this.studentAnswerList = const []}) { + if (finishedTime.length == 19) { + finishedTime = finishedTime.substring(0, 16); + } + if (answer != null) studentAnswerList = [answer!]; + } + + factory ReviewItem.fromJson(Map srcJson) => _$ReviewItemFromJson(srcJson); + + Map toJson() => _$ReviewItemToJson(this); +} diff --git a/marking_app/lib/common/model/review/review_page_params.dart b/marking_app/lib/common/model/review/review_page_params.dart new file mode 100644 index 0000000..9d60b79 --- /dev/null +++ b/marking_app/lib/common/model/review/review_page_params.dart @@ -0,0 +1,42 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-21 14:04:32 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-21 14:13:20 + * @FilePath: \marking_app\lib\common\model\review\review_page_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/common/base_page.dart'; + +part 'review_page_params.g.dart'; + +@JsonSerializable(includeIfNull: false) + class ReviewPageParams extends BasePage { + + @JsonKey(name: 'qNum') + String? qNum; + + @JsonKey(name: 'IsException') + bool isException; + + @JsonKey(name: 'markingTimeOrderType') + int? markingTimeOrderType; + + @JsonKey(name: 'scoreOrderType') + int? scoreOrderType; + + @JsonKey(name: 'markingUserDetailId') + int? markingUserDetailId; + + ReviewPageParams({required this.qNum,required this.isException,this.scoreOrderType,this.markingTimeOrderType,this.markingUserDetailId,required page,required limit}):super(page,limit); + + factory ReviewPageParams.fromJson(Map srcJson) => _$ReviewPageParamsFromJson(srcJson); + + Map toJson() => _$ReviewPageParamsToJson(this); + +} + + + + diff --git a/marking_app/lib/common/model/review/review_tab.dart b/marking_app/lib/common/model/review/review_tab.dart new file mode 100644 index 0000000..16eee4e --- /dev/null +++ b/marking_app/lib/common/model/review/review_tab.dart @@ -0,0 +1,30 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-20 20:40:20 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-20 20:40:26 + * @FilePath: \marking_app\lib\common\model\review\review_tab.dart + * @Description: 回评页面的tab + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'review_tab.g.dart'; + +@JsonSerializable() + class ReviewTab extends Object { + + @JsonKey(name: 'questionCount') + int questionCount; + + @JsonKey(name: 'questionNum') + String questionNum; + + ReviewTab(this.questionCount,this.questionNum,); + + factory ReviewTab.fromJson(Map srcJson) => _$ReviewTabFromJson(srcJson); + + Map toJson() => _$ReviewTabToJson(this); + +} + + diff --git a/marking_app/lib/common/model/sys/system_version.dart b/marking_app/lib/common/model/sys/system_version.dart new file mode 100644 index 0000000..0fbe6d5 --- /dev/null +++ b/marking_app/lib/common/model/sys/system_version.dart @@ -0,0 +1,30 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-08-01 10:02:40 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 18:07:50 + * @FilePath: \marking_app\lib\common\model\sys\system_version.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'system_version.g.dart'; + + +@JsonSerializable() + class SystemVersion extends Object{ + + @JsonKey(name: 'version') + String version; + + @JsonKey(name: 'apkUrl') + String apkUrl; + + @JsonKey(name: 'description') + String? description; + + SystemVersion(this.version,this.apkUrl,this.description); + + factory SystemVersion.fromJson(Map srcJson) => _$SystemVersionFromJson(srcJson); + +} \ No newline at end of file diff --git a/marking_app/lib/common/model/user/user_info.dart b/marking_app/lib/common/model/user/user_info.dart new file mode 100644 index 0000000..4135909 --- /dev/null +++ b/marking_app/lib/common/model/user/user_info.dart @@ -0,0 +1,51 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 10:44:46 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 20:17:48 + * @FilePath: \marking_app\lib\common\model\user\user_info.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'user_info.g.dart'; + + +@JsonSerializable(checked: true) + class UserInfo extends Object { + + @JsonKey(name: 'id') + String id; + + @JsonKey(name: 'userName') + String userName; + + @JsonKey(name: 'loginName') + String loginName; + + @JsonKey(name: 'subjectIds') + List subjectIds; + + @JsonKey(name: 'schoolId') + int schoolId; + + @JsonKey(name: 'schoolName') + String schoolName; + + + @JsonKey(name: 'avatar') + String avatar; + + @JsonKey(name: 'positionNames') + List positionNames; + + + UserInfo(this.id,this.userName,this.loginName,this.subjectIds,this.schoolId,this.schoolName,this.avatar,this.positionNames); + + factory UserInfo.fromJson(Map srcJson) => _$UserInfoFromJson(srcJson); + + Map toJson() => _$UserInfoToJson(this); + +} + + diff --git a/marking_app/lib/common/model/user/user_info_report.dart b/marking_app/lib/common/model/user/user_info_report.dart new file mode 100644 index 0000000..48bd28a --- /dev/null +++ b/marking_app/lib/common/model/user/user_info_report.dart @@ -0,0 +1,86 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/model/enum/reportUserIdentity.dart'; +import 'package:marking_app/common/model/report/marked_item.dart'; + +part 'user_info_report.g.dart'; + +@JsonSerializable() +class UserInfoReport extends Object { + @JsonKey(name: 'Positions') + List positions; + + @JsonKey(name: 'positionIndex', defaultValue: 0) + int positionIndex; + + @JsonKey(name: 'normal') + bool normal; + + UserInfoReport({required this.positions, required this.positionIndex, this.normal = false}); + + factory UserInfoReport.fromJson(Map srcJson) => _$UserInfoReportFromJson(srcJson); + + Map toJson() => _$UserInfoReportToJson(this); +} + +@JsonSerializable() +class Positions extends Object { + @JsonKey(name: 'Id') + int id; + + @JsonKey(name: 'Name') + String name; + + @JsonKey(name: 'SchoolId') + int schoolId; + + @JsonKey(name: 'SchoolName') + String schoolName; + + @JsonKey(name: 'GraduationYear') + int graduationYear; + + @JsonKey(name: 'Grade') + String grade; + + @JsonKey(name: 'ClassId') + int classId; + + @JsonKey(name: 'ClassName') + String? className; + + @JsonKey(name: 'SubjectId') + int subjectId; + + @JsonKey(name: 'SubjectName') + String? subjectName; + + @JsonKey(name: 'PositionType') + int positionType; + + // { + // 教委 = 1, + // 校级 = 2, + // 年级 = 3, + // 班级 = 4, + // 教师 = 5, + // } + @JsonKey(name: 'PositionLevel') + int positionLevel; + + @JsonKey(name: 'Status') + int status; + + @JsonKey(name: 'markExams') + List? markExams; + + @JsonKey(name: 'identityEnum') + ReportUserIdentity identityEnum; + + Positions(this.id, this.name, this.schoolId, this.schoolName, this.graduationYear, this.grade, this.classId, this.className, this.subjectId, this.subjectName, this.positionType, this.positionLevel, this.status, {this.markExams, this.identityEnum = ReportUserIdentity.OCCUPY_POSITION}) { + this.identityEnum = ReportUserIdentity.values[positionLevel]; + } + + factory Positions.fromJson(Map srcJson) => _$PositionsFromJson(srcJson); + + Map toJson() => _$PositionsToJson(this); +} diff --git a/marking_app/lib/common/model/user/user_login.dart b/marking_app/lib/common/model/user/user_login.dart new file mode 100644 index 0000000..7e60083 --- /dev/null +++ b/marking_app/lib/common/model/user/user_login.dart @@ -0,0 +1,32 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 14:50:51 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-13 17:22:16 + * @FilePath: \marking_app\lib\common\model\UserLogin.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'user_login.g.dart'; + + +@JsonSerializable(checked: true) + class UserLogin extends Object { + + @JsonKey(name: 'access_token') + String accessToken; + + @JsonKey(name: 'expires_in') + int expiresIn; + + @JsonKey(name: 'token_type') + String tokenType; + + UserLogin(this.accessToken,this.expiresIn,this.tokenType,); + + factory UserLogin.fromJson(Map srcJson) => _$UserLoginFromJson(srcJson); + + Map toJson() => _$UserLoginToJson(this); + +} \ No newline at end of file diff --git a/marking_app/lib/common/model/user/user_login_params.dart b/marking_app/lib/common/model/user/user_login_params.dart new file mode 100644 index 0000000..1f92752 --- /dev/null +++ b/marking_app/lib/common/model/user/user_login_params.dart @@ -0,0 +1,29 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 17:17:53 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-13 17:23:42 + * @FilePath: \marking_app\lib\common\model\user\user_login_params.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:json_annotation/json_annotation.dart'; + +part 'user_login_params.g.dart'; + + +@JsonSerializable() + class UserLoginParams extends Object { + + @JsonKey(name: 'loginName') + String loginName; + + @JsonKey(name: 'password') + String password; + + UserLoginParams(this.loginName,this.password,); + + factory UserLoginParams.fromJson(Map srcJson) => _$UserLoginParamsFromJson(srcJson); + + Map toJson() => _$UserLoginParamsToJson(this); + +} \ No newline at end of file diff --git a/marking_app/lib/components/DrawerHead.dart b/marking_app/lib/components/DrawerHead.dart new file mode 100644 index 0000000..9e83b6a --- /dev/null +++ b/marking_app/lib/components/DrawerHead.dart @@ -0,0 +1,78 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 18:12:17 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-22 18:13:35 + * @FilePath: \marking_app\lib\components\DrawerHead.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:flutter/material.dart'; + +class DrawerHead extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Drawer( // 重要的Drawer组件 + child: ListView( // Flutter 可滚动组件 + padding: EdgeInsets.zero, // padding为0 + children: [ + UserAccountsDrawerHeader( + // UserAccountsDrawerHeader 可以设置用户头像、用户名、Email 等信息,显示一个符合纸墨设计规范的 drawer header。 + // 标题 + accountName: Text('Jobsofferings', + style: TextStyle(fontWeight: FontWeight.bold)), + // 副标题 + accountEmail: Text('https://juejin.im/user/5eaee21f5188256da0323bf9'), + // Emails + currentAccountPicture: CircleAvatar( + // 使用网络加载图像 + backgroundImage: NetworkImage( + 'https://images.cnblogs.com/cnblogs_com/JobsOfferings/1363202/o_preview.jpg'), + ), + // 圆角头像 + decoration: BoxDecoration( + color: Colors.yellow[400], + image: DecorationImage( + image: NetworkImage( + 'http://pic.netbian.com/uploads/allimg/190510/221228-15574975489aa1.jpg'), + fit: BoxFit.cover, // 一种图像的布局方式 + colorFilter: ColorFilter.mode( + Colors.grey, + BlendMode.hardLight))), + // BoxDecoration 用于制作背景 + ), + // ListTile是下方的几个可点按List + ListTile( + // List标题 + title: Text('details', textAlign: TextAlign.right), + trailing: Icon( + Icons.favorite, // Icon种类 + color: Colors.black12, // Icon颜色 + size: 22.0, // Icon大小 + ), + // 点按时间,这里可以做你想做的事情,如跳转、判断等等 + // 此处博主只使用了 Navigator.pop(context) 来手动关闭Drawer + onTap: () => Navigator.pop(context), + ), + ListTile( + title: Text('Favorite', textAlign: TextAlign.right), + trailing: Icon( + Icons.favorite, + color: Colors.black12, + size: 22.0, + ), + onTap: () => Navigator.pop(context), + ), + ListTile( + title: Text('Settings', textAlign: TextAlign.right), + trailing: Icon( + Icons.favorite, + color: Colors.black12, + size: 22.0, + ), + onTap: () => Navigator.pop(context), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/marking_app/lib/components/PictureOverview.dart b/marking_app/lib/components/PictureOverview.dart new file mode 100644 index 0000000..545f4c5 --- /dev/null +++ b/marking_app/lib/components/PictureOverview.dart @@ -0,0 +1,956 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-23 11:55:16 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 17:01:15 + * @FilePath: \marking_app\lib\components\PictureOverview.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:async'; +import 'dart:io'; +import 'package:crypto/crypto.dart' as crypto; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; +import 'package:marking_app/common/model/job/test_questions_image_info.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config_params.dart'; +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/common/model/marking/marking_history_zoom_info.dart'; +import 'package:marking_app/common/model/marking/switch_keyboard_to_reload_images.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart'; +import 'package:marking_app/pages/marking/hooks/use_zoom_image_history.dart'; +import 'package:marking_app/pages/marking/provider/zoom_height_provider.dart'; +import 'package:marking_app/pages/marking/provider/zoom_history_provider.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/provider/upload_file_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:path_provider/path_provider.dart'; +import 'dart:ui' as ui; + +import 'package:uuid/uuid.dart'; +import 'package:zoom_widget/zoom_widget.dart'; +// import 'package:zoom_widget/zoom_widget.dart'; +part 'PictureOverview.g.dart'; + +typedef PageChanged = void Function(int index); + +//这里就是关键的代码,定义一个key +final GlobalKey pictureOverviewKey = GlobalKey(); + +class PictureOverview extends StatefulHookConsumerWidget { + final double imageScale; + final Offset? imagePosition; + + final String questionNum; + final int markingUserId; + + final bool homework; + final List imageItems; //图片列表 + final bool annotationsFlag; + final String testQuestionNumber; + final Map commentImageMap; + + const PictureOverview({ + required this.imageItems, + required this.annotationsFlag, + required this.commentImageMap, + required this.testQuestionNumber, + required this.questionNum, + required this.markingUserId, + this.homework = false, + this.imageScale = 1, + this.imagePosition, + Key? key, + }) : super(key: key); + + @override + PictureOverviewState createState() => PictureOverviewState(); +} + +class PictureOverviewState extends ConsumerState with CommonMixin { + final GlobalKey theglobalKey = GlobalKey(); + final GlobalKey<_ExamPaperDrawingState> examPaperDrawingKey = GlobalKey<_ExamPaperDrawingState>(); + final GlobalKey zoomGlobalKey = GlobalKey(); // zoom + double? initScale; + Offset? zoomOffset; + + ImageStreamListener? _imageStreamListener; + TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 + ImageStream? _imageStream; + + final int currentIndex = 0; + late AnnotationGraffitiSwitch graffitiSwitch; + late RemoveListener _annotationsListener; // 批注关闭监听 + File? temFile; // 批注临时数据 + + @override + void initState() { + super.initState(); + FastData.getInstance().getMarkingZoomInfo().then((value) { + if (value == null) return; + bool flag = value.questionNum == widget.questionNum && value.markingUserId == widget.markingUserId; + if (flag) { + if (value.positionX != 0 && value.positionY != 0) { + zoomOffset = Offset(value.positionX, value.positionY); + } + if (value.scale < 1) { + initScale = value.scale; + Future.delayed(Duration(seconds: 5), () => ref.read(zoomHistoryProvider.notifier).setState(initScale!)); + } + } + }); + Future.delayed(Duration.zero, () { + ref.read(zoomHeightProvider.notifier).setState(0.0); + }); + _annotationsListener = ref.read(annotationGraffitiSwitchProvider.notifier).addListener((state) { + graffitiSwitch = state; + toUpState(setState, () {}, mounted); + }); + } + + @override + void dispose() { + super.dispose(); + _annotationsListener(); + try { + _imageStream?.removeListener(_imageStreamListener!); + if (temFile != null) { + bool flieExist = temFile!.existsSync(); + if (flieExist) temFile!.delete(); + } + } catch (e) {} + } + + Future saveImage() async { + try { + ToastUtils.showLoading(); + if (temFile == null) { + if (examPaperDrawingKey.currentState?.pointsPureData.isEmpty ?? true) return null; + // 没有图片就上传图片 + RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; + if (boundary == null) return null; + double dpr = MediaQuery.of(context).devicePixelRatio; + // double dpr = ui.window.devicePixelRatio; + ui.Image image = await boundary.toImage(pixelRatio: dpr); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + /// 等于null 已经是异常了 + return null; + } + Directory appDocDir = await getApplicationDocumentsDirectory(); + List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + await file.writeAsBytes(bytes); // 将ByteData写入文件 + temFile = file; + } + + crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes()); + + RestClient _client = await getClient(); + BaseStructureResult resUploadConfig = + await _client.getUploadFile(UploadFileInterfaceConfigParams( + fileName: '1.png', + fileMd5: fileMd5.toString(), + contentLength: temFile!.lengthSync(), + )); + if (!resUploadConfig.success || resUploadConfig.data == null) { + ToastUtils.getFluttertoast(msg: '获取图片上传配置失败', context: context); + + return null; + } + if (resUploadConfig.data!.uploadUri == null) { + return FileResult(myObject: '', success: true, url: resUploadConfig.data!.downloadUri); + } + FileResult? resFile = + await ref.read(uploadFileProvider.notifier).getUploadFileConfig(resUploadConfig.data!, temFile!); + // FileResult? resFile = await ref + // .read(uploadFileProvider.notifier) + // .uploadFile(temFile!.path, widget.imageItems[currentIndex], ref.read(userProvider).id.toString()); + if (resFile != null && resFile.success) { + resFile.otherParam = currentIndex; + return resFile; + } + } catch (e) { + toPrint(val: e.toString()); + } finally { + // ToastUtils.dismiss(); + } + return null; + } + + // 缩放组件 ==> 位置更新 + void onPositionUpdate(Offset position) async { + MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); + double? scale = historyZoomInfo?.scale ?? 1; + MarkingHistoryZoomInfo info = MarkingHistoryZoomInfo( + scale: scale, + positionX: position.dx, + positionY: position.dy, + questionNum: widget.questionNum, + markingUserId: widget.markingUserId, + ); + FastData.getInstance().setMarkingZoomInfo(info); + } + + // 缩放组件 ==> 缩放监听 + void onScaleUpdate(double scale, double scale1) async { + print('这是第一个scale:$scale'); + print('这是第二个noScale:$scale1'); + // MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); + MarkingHistoryZoomInfo? historyZoomInfo = await FastData.getInstance().getMarkingZoomInfo(); + double positionX = historyZoomInfo?.positionX ?? 0; + double positionY = historyZoomInfo?.positionY ?? 0; + MarkingHistoryZoomInfo info = MarkingHistoryZoomInfo( + scale: scale1, + positionX: positionX, + positionY: positionY, + questionNum: widget.questionNum, + markingUserId: widget.markingUserId, + ); + FastData.getInstance().setMarkingZoomInfo(info); + } + + @override + Widget build(BuildContext context) { + DoMarkingKeyboardModel _model = ref.watch(markingKeyboardProvider); + return Container( + width: double.infinity, + height: double.infinity, + alignment: Alignment.center, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + double containerWidth = constraints.maxWidth; + double containerHeight = constraints.maxHeight; + + return $LocalAndNetworkSwitch( + zoomGlobalKey: zoomGlobalKey, + containerWidth: containerWidth, + containerHeight: containerHeight, + imagePosition: widget.imagePosition ?? Offset(0, 0), + imageScale: widget.imageScale, + homework: widget.homework, + theglobalKey: theglobalKey, + graffitiSwitch: graffitiSwitch, + drawFlag: widget.annotationsFlag, + examGlobalKey: examPaperDrawingKey, + testQuestionNumber: widget.testQuestionNumber, + imageUrl: widget.commentImageMap[widget.imageItems[currentIndex]]!, + updateTempFileCall: (File? file) { + temFile = file; + print('更新需要上传的文件'); + }, + imageBuilder: (context, imageProvider) { + Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); + if (imagInfoModel == null || + (imagInfoModel?.boxHeight != containerHeight || imagInfoModel?.boxWidth != containerWidth)) { + if (_imageStreamListener != null) _imageStream?.removeListener(_imageStreamListener!); + _imageStreamListener = ImageStreamListener((ImageInfo info, bool _) { + imagInfoModel = TestQuestionsImageInfo( + // 获取图片的宽高 + boxHeight: containerHeight, + boxWidth: containerWidth, + url: widget.commentImageMap[widget.imageItems[currentIndex]]!, + height: info.image.height.toDouble(), + width: info.image.width.toDouble(), + ); + printJson(imagInfoModel!.toJson()); + Future.delayed(Duration.zero, () { + ref.read(zoomHeightProvider.notifier).setState(imagInfoModel?.scaleHeight ?? 0.0); + }); + }); + _imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(_imageStreamListener!); + } + + // return imageWidget; + return Zoom( + // initTotalZoomOut: true, + child: imageWidget, + maxZoomWidth: containerWidth, + canvasColor: Colors.transparent, + backgroundColor: Colors.transparent, + maxZoomHeight: imagInfoModel?.scaleHeight, + initScale: initScale ?? 1, + initPosition: zoomOffset, + // initPosition: , + // onPositionUpdate: onPositionUpdate, + onScaleUpdate: onScaleUpdate, + // zoomSensibility: 0.5, + ); + }, + ); + }, + )); + } +} + +// 试卷绘制 +class ExamPaperDrawing extends StatefulHookConsumerWidget { + String imgUrl; + bool homework; + BoxDecoration? decoration; + AnnotationGraffitiSwitch graffitiSwitch; + List? points; + List? pointsPureData; + ValueNotifier> imageLoaded; + + GlobalKey globalKey; + // Function(String) imageCall; + ExamPaperDrawing({ + required this.imgUrl, + required this.homework, + required this.points, + required this.pointsPureData, + required this.graffitiSwitch, + required this.globalKey, + required this.imageLoaded, + this.decoration, + Key? key, + }) : super(key: key); + + @override + _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); +} + +class _ExamPaperDrawingState extends ConsumerState + with EventBusMixin { + late Future _future; // 考试试卷 + + // 用于记录手指位置的变量 + late List points; + late List pointsPureData; + // 用于记录绘图结果的变量 + bool _isEraserPressed = false; // 橡皮擦按下 + Offset? _eraserPosition; // 按下位置 + Offset? globalPosition = null; // 是否正在绘制 + + Future loadImage(String url) async { + try { + Map map = widget.imageLoaded.value; + ui.Image? image = map[url]; + if (image != null) { + return image; + } + final httpClient = HttpClient(); + final request = await httpClient.getUrl(Uri.parse(url)); + final response = await request.close(); + final bytes = await consolidateHttpClientResponseBytes(response); + final codec = await ui.instantiateImageCodec(bytes); + final frame = await codec.getNextFrame(); + ui.Image theImage = frame.image; + map[url] = theImage; + return theImage; + } catch (e) { + print('请求图片报错:${e.toString()}'); + } + return null; + } + + // void _onPointerDown(DragDownDetails details) { + // if (widget.graffitiSwitch.openEraser) { + // _eraserPosition = (context.findRenderObject() as RenderBox).globalToLocal(details.globalPosition); + // _isEraserPressed = true; + // toUpState(setState, ()=>{}, mounted); + // } + // } + + @override + void initState() { + points = widget.points ?? []; + pointsPureData = widget.pointsPureData ?? []; + print('图片地址:${widget.imgUrl}'); + _future = loadImage(widget.imgUrl); + // 事件总线监听 + eventOn(callback: (BottomAnnotationSwitchCleanall item) { + if (item.previousStep) { + if (points.isEmpty) { + ToastUtils.showInfo('批注已清空'); + return; + } + var index = pointsPureData.toList().lastIndexOf(null); + if (index != -1) { + if (index + 1 == pointsPureData.length) { + pointsPureData = pointsPureData.sublist(0, index); + points.sublist(0, index); + index = pointsPureData.toList().lastIndexOf(null); + index == -1 ? -1 : index + 1; + } + if (index != -1) { + pointsPureData = pointsPureData.sublist(0, index); + points = points.sublist(0, index); + toUpState(setState, () {}, mounted); + } else { + item.cleanAll = true; + } + } else { + item.cleanAll = true; + } + } + + if (item.cleanAll) { + pointsPureData.clear(); + points.clear(); + toUpState(setState, () {}, mounted); + } + + if (item.uploadImage) { + // 图片确认按钮,生成 + if (pointsPureData.isEmpty) { + ToastUtils.showInfo('请先批注再提交'); + return; + } + // _saveImage().then((FileResult? res) { + // if (res != null) { + // widget.imageCall(res.url!); + // } + // }); + } + }); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + eventCancel(); + } + + @override + Widget build(BuildContext context) { + return MyFutureBuilder.buildFutureBuilderOfSingleInstance( + context, + _future, + (ui.Image? theImage) { + if (theImage == null) return const Center(child: Text('图片加载错误')); + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final double containerWidth = constraints.maxWidth; // 展示区域总宽度 + final double containerHeight = constraints.maxHeight; // 展示区域总宽度 + + final double imageWidth = theImage.width.toDouble(); // 图片原始宽度 + final double imageHeight = theImage.height.toDouble(); // 图片原始高度 + final double widthRatio = containerWidth / imageWidth; // + final double heightRatio = containerHeight / imageHeight; + final double scale = widthRatio > heightRatio ? heightRatio : widthRatio; + final double destWidth = imageWidth * scale; + final double destHeight = imageHeight * scale; + + final bool homework = widget.homework; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onPanUpdate: (DragUpdateDetails details) { + if (globalPosition != null) { + // 预防双指同时作用于屏幕 + double dx = globalPosition!.dx; + double dy = globalPosition!.dy; + + double dxNew = details.globalPosition.dx; + double dyNew = details.globalPosition.dy; + if ((dxNew - dx).abs() > 22 || (dyNew - dy).abs() > 22) { + return; + } + } + globalPosition = details.globalPosition; + try { + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + RenderBox renderBox = context.findRenderObject() as RenderBox; + Offset localPosition = renderBox.globalToLocal(details.globalPosition); + pointsPureData = List.from(pointsPureData)..add(localPosition); + points = List.from(points) + ..add(GestureRecording(eraser: widget.graffitiSwitch.openEraser, data: localPosition)); + _eraserPosition = localPosition; + _isEraserPressed = true; + setState(() {}); + } + } catch (e) { + toPrint(val: '进入报错'); + } + }, + onPanEnd: (DragEndDetails details) { + print('离开.............'); + globalPosition = null; + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + pointsPureData.add(null); // 增加空点以分隔不同的线段 + points.add(GestureRecording(eraser: widget.graffitiSwitch.openEraser)); + _isEraserPressed = false; + _eraserPosition = null; + } + }, + child: RepaintBoundary( + key: widget.globalKey, + child: CustomPaint( + // isComplex: true, + // willChange: true, + painter: DrawingPainter( + image: theImage, + points: points, + isErasing: widget.graffitiSwitch.openEraser, + destWidth: destWidth, + destHeight: destHeight, + imageWidth: imageWidth, + imageHeight: imageHeight, + homework: homework, + containerWidth: containerWidth, + containerHeight: containerHeight, + ), + // size: Size(homework ? containerWidth : destWidth, homework ? containerHeight : destHeight), + size: Size(containerWidth, containerHeight), + ), + ), + ); + }, + ); + }, + ); + } +} + +class DrawingPainter extends CustomPainter { + final List points; + final bool isErasing; + final ui.Image image; + final bool homework; + final double containerWidth; + final double containerHeight; + double destWidth; + double destHeight; + final double imageWidth; + final double imageHeight; + + // final Rect destRect; + // final Rect srcRect; + final bool openErasing; + DrawingPainter({ + required this.homework, + required this.points, + required this.isErasing, + required this.image, + required this.destWidth, + required this.destHeight, + required this.containerWidth, + required this.containerHeight, + required this.imageWidth, + required this.imageHeight, + }) : + // destRect = Rect.fromLTWH(0, 0,destWidth,destHeight), + // srcRect = Rect.fromLTWH(0, 0, imageWidth, imageHeight), + openErasing = points.isNotEmpty && isErasing, + super(); + + Paint paintBrush = Paint() + ..color = Colors.red + ..strokeCap = StrokeCap.round + ..strokeWidth = 1.5; + + Paint eraser = Paint() + ..blendMode = BlendMode.clear + ..color = Colors.transparent + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = 100; + + final emptyPaint = Paint(); + final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; + + @override + void paint(Canvas canvas, Size size) { + double offsetX = (size.width - destWidth) / 2; + double offsetY = (size.height - destHeight) / 2; + if (destWidth < (size.width / 2)) { + destWidth = size.width / 2; + offsetX = (size.width - destWidth) / 2; + } + + // final rect = Rect.fromCenter(center: center, width: destWidth, height: destHeight); + Rect srcRect = Rect.fromLTRB(0, 0, image.width.toDouble(), image.height.toDouble()); + // Rect destRect = Rect.fromLTRB(offsetX, offsetY, containerWidth, containerHeight); + Rect destRect = Offset(offsetX, offsetY) & Size(destWidth, destHeight); + + canvas.drawImageRect(image, srcRect, destRect, emptyPaint); + + // canvas.drawImage(image, Offset.zero, emptyPaint); + if (points.isNotEmpty) { + // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 + canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 + + canvas.drawColor(Colors.transparent, BlendMode.clear); + } + + for (int i = 0; i < points.length - 1; i++) { + GestureRecording item = points[i]; + GestureRecording nextItem = points[i + 1]; + Offset? offsetData = item.data; + Offset? nextOffsetData = nextItem.data; + if (offsetData != null && nextOffsetData != null) { + canvas.drawLine(offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); + } + } + + // 恢复画布状态. + if (points.isNotEmpty) canvas.restore(); + } + + // @override + // bool shouldRepaint(DrawingPainter oldDelegate) { + // List thePoints = oldDelegate.points; + // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; + // return thePoints != points; + // } + + @override + bool shouldRepaint(DrawingPainter oldDelegate) => true; +} + +/** + * 手势记录 + */ +class GestureRecording { + bool eraser; // 是否是橡皮擦 + + Offset? data; // 位置记录 可能为null + + GestureRecording({required this.eraser, this.data}); +} + +@hwidget +Widget $myCachedNetworkImage({ + required String imageUrl, + required File? tempFile, + required double width, + required double height, + required ImageWidgetBuilder? imageBuilder, +}) { + UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use(); + + if (tempFile != null) { + // 注释临时本地图片 + return Image.file(tempFile, fit: BoxFit.contain, width: double.infinity, height: double.infinity); + } + + useEffect(() { + _useImgRefsh.eventOnSub(callback: (SwitchKeyboardToReloadImages event) { + if (event.reload) { + _useImgRefsh.imageKey.value = UniqueKey(); + } + }); + + return () { + _useImgRefsh.eventCancelSub(); + }; + }, []); + + return CacheNetImageView( + cacheNetImageKey: _useImgRefsh.imageKey.value, + imageUrl: imageUrl, + imageBuilder: imageBuilder, + errorWidget: (context, url, error) { + return GestureDetector( + onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset('assets/images/test_paper_loading_failed.png'), + quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), + ], + ), + ); + }, + ); + + // return Container( + // width: width, + // height: height, + // color: Colors.red, + // alignment: Alignment.center, + // child: tempFile != null + // ? Image.file( + // tempFile, + // fit: BoxFit.contain, + // width: double.infinity, + // height: double.infinity, + // ) + // : CachedNetworkImage( + // key: _useImgRefsh.imageKey.value, + // fit: BoxFit.contain, + // width: double.infinity, + // // height: double.infinity, + // imageUrl: imageUrl, + // placeholder: (context, url) => + // Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), + // // imageBuilder: (context, imageProvider) => Container( + // // decoration: BoxDecoration( + // // image: DecorationImage( + // // image: imageProvider, + // // fit: BoxFit.fitWidth, + // // // colorFilter: ColorFilter.mode(Colors.red, BlendMode.colorBurn), + // // ), + // // ), + // // ), + // errorWidget: (context, url, error) { + // return GestureDetector( + // onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), + // child: Column( + // mainAxisSize: MainAxisSize.min, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Image.asset('assets/images/test_paper_loading_failed.png'), + // quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), + // ], + // ), + // ); + // }, + // ), + // ); +} + +class CacheNetImageView extends ConsumerWidget { + final Key cacheNetImageKey; + final String imageUrl; + final ImageWidgetBuilder? imageBuilder; + final LoadingErrorWidgetBuilder? errorWidget; + const CacheNetImageView({ + required this.cacheNetImageKey, + required this.imageUrl, + required this.imageBuilder, + required this.errorWidget, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + double zoomHeight = ref.watch(zoomHeightProvider); + double initScale = ref.watch(zoomHistoryProvider); + + print('zoomView的视图最大高度:$zoomHeight 和 缩放比例$initScale'); + return CachedNetworkImage( + key: cacheNetImageKey, + imageUrl: imageUrl, + fit: BoxFit.fitWidth, + width: double.infinity, + alignment: Alignment.center, + imageBuilder: imageBuilder, + placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), + errorWidget: errorWidget, + ); + } +} + +// double zoomHeight = ref.watch(zoomHeightProvider); + +/// 网络图和本地图切换 +@hwidget +Widget $localAndNetworkSwitch( + BuildContext context, { + double imageScale = 1, + Offset imagePosition = const Offset(0, 0), + required ImageWidgetBuilder? imageBuilder, + required GlobalKey zoomGlobalKey, + required double containerWidth, + required double containerHeight, + required String testQuestionNumber, // 试题题号 + required String imageUrl, // 图片 + required bool drawFlag, // 是否打开绘画操作 + required GlobalKey theglobalKey, + required bool homework, + required GlobalKey<_ExamPaperDrawingState> examGlobalKey, + required AnnotationGraffitiSwitch graffitiSwitch, + required Function(File?) updateTempFileCall, +}) { + // ImageStream? imageStream; // 图片监听数据 + // TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 + // ImageStreamListener theImageStreamListener; + + UseLocalAndNetworkSwitch _useSwitch = UseLocalAndNetworkSwitch.use(!homework); + UseZoomImageHistory _useZoomHistory = UseZoomImageHistory.use(testQuestionNumber); + + // TransformationController _transContller = useTransformationController(); + + useValueChanged( + _useSwitch.temFile.value, + (oldValue, oldResult) { + updateTempFileCall(_useSwitch.temFile.value); + return null; + }, + ); + + useValueChanged(testQuestionNumber, (oldValue, oldResult) { + if (testQuestionNumber.length > 0) { + _useZoomHistory.initInfo(testQuestionNumber); + } + }); + + useValueChanged(drawFlag, (oldValue, oldResult) { + if (!drawFlag) { + // 关闭的时候创建临时图片文件在设备 + _useSwitch + .createTempFile(context, theglobalKey: theglobalKey, examGlobalKey: examGlobalKey) + .then((File? theFile) { + if (theFile == null) { + // TODO 代表保存失败的逻辑 + // 当前情况:_useSwich.showZoomImg.value 没有设置为true还是展示的原来的绘图组件ExamPaperDrawing + toPrint(val: '进入错误逻辑.........'); + } + _useSwitch.showZoomImg.value = true; + }); + return; + } + _useSwitch.showZoomImg.value = !drawFlag; + }); + useEffect(() { + _useZoomHistory.initInfo(); // 初始化历史数据 + + // _useScrollController + // ..addListener(() { + // _scrollPosition.value = _useScrollController.position.pixels; + // }); + return () { + // try { + // _useImageSize.imageStream.value?.removeListener(_useImageSize.imageListener.value!); + // } catch (e) {} + // ..removeListener(() {}) + }; + }, []); + + print('是否更新视图.... ${_useZoomHistory.initPosition.value}'); + return _useSwitch.showZoomImg.value + ? + /** + Scrollbar( + // thumbVisibility: true, + thumbVisibility: true, + controller: _useScrollController, + child: SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.zero, + scrollDirection: Axis.vertical, // 设置垂直滚动 + child: Transform.scale( + scale: 0.4, + alignment: Alignment.topCenter, + child: $MyCachedNetworkImage( + imageUrl: imageUrl, + tempFile: _useSwitch.temFile.value, + width: containerWidth, + height: containerHeight, + ), + ), + ), + )*/ + + /** */ + $MyCachedNetworkImage( + imageUrl: imageUrl, + tempFile: _useSwitch.temFile.value, + width: containerWidth, + height: containerHeight, + imageBuilder: imageBuilder, + ) + : ExamPaperDrawing( + imgUrl: imageUrl, + graffitiSwitch: graffitiSwitch, + points: _useSwitch.points.value, + pointsPureData: _useSwitch.pointsPureData.value, + decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)), + globalKey: theglobalKey, + key: examGlobalKey, + imageLoaded: _useSwitch.imageLoaded, + homework: homework, + // imageCall: (String str) => widget.imageCall(str, currentIndex), + ); +} + +class UseLocalAndNetworkSwitch { + ValueNotifier showZoomImg; + ValueNotifier temFile; + + ValueNotifier?> points; + ValueNotifier?> pointsPureData; + ValueNotifier> imageLoaded; + + UseLocalAndNetworkSwitch._({ + required this.showZoomImg, + required this.temFile, + required this.points, + required this.pointsPureData, + required this.imageLoaded, + }); + + // 工厂构造函数 + factory UseLocalAndNetworkSwitch.use(bool defaultVal) { + return UseLocalAndNetworkSwitch._( + points: useState(null), + pointsPureData: useState(null), + showZoomImg: useState(defaultVal), + temFile: useState(null), + imageLoaded: useState({}), + ); + } + + Future createTempFile( + BuildContext context, { + required GlobalKey theglobalKey, + required GlobalKey<_ExamPaperDrawingState> examGlobalKey, + }) async { + Timer? _timer; + try { + _timer = Timer(Duration(seconds: 1), () { + // 执行操作的代码 + ToastUtils.showLoading(); + }); + if (examGlobalKey.currentState?.pointsPureData.isEmpty ?? true) { + try { + temFile.value?.delete(); + } catch (e) {} + temFile.value = null; + return null; + } + + RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; + if (boundary == null) return null; + ui.Image image = await boundary.toImage(pixelRatio: 3.0); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData != null) { + Directory appDocDir = await getApplicationDocumentsDirectory(); + List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + await file.writeAsBytes(bytes); // 将ByteData写入文件 + + temFile.value?.delete(); + temFile.value = file; // 保存临时文件 + + points.value = examGlobalKey.currentState?.points; + pointsPureData.value = examGlobalKey.currentState?.pointsPureData; + toPrint(val: '图片保存成功:'); + return temFile.value; + } + } catch (e) { + toPrint(val: '图片生成错误:${e}'); + toPrint(val: e.toString()); + ToastUtils.getFluttertoast(context: context, msg: '保存图片报错,请稍后重试'); + } finally { + _timer?.cancel(); + ToastUtils.dismiss(); + } + return null; + } +} diff --git a/marking_app/lib/components/TestPaperItem.dart b/marking_app/lib/components/TestPaperItem.dart new file mode 100644 index 0000000..7e18c09 --- /dev/null +++ b/marking_app/lib/components/TestPaperItem.dart @@ -0,0 +1,555 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-12 16:29:30 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 13:48:52 + * @FilePath: \marking_app\lib\components\TestPaperItem.dart + * @Description: 阅卷任务ITEM + */ +import 'package:achievement_view/achievement_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; +import 'package:marking_app/common/model/enum/subject.dart'; +import 'package:marking_app/common/model/marking/current_review_task.dart'; +import 'package:marking_app/common/model/marking/marking_item.dart'; +import 'package:marking_app/provider/review_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:percent_indicator/percent_indicator.dart'; + +class TestPaperItem extends ConsumerWidget with CommonMixin { + final MarkingItem markingItem; + final bool isHomeworkCorrection; + final VoidCallback? call; + final MarkingListType? markingtype; + const TestPaperItem( + {required this.markingItem, this.markingtype, this.isHomeworkCorrection = false, this.call, Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + double indent = 6.w; // 缩进宽度. + switch (markingtype) { + case MarkingListType.EXCEPTIONAL: + case MarkingListType.ARBITRATE: + indent += 20.w + 8.w; + break; + case MarkingListType.NORMAL: + indent += 36.w + 8.w; + break; + default: + indent = 0; + } + + final textPainter = TextPainter( + text: TextSpan(text: " ", style: TextStyle(fontSize: 18.sp)), + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + final spaceWidth = textPainter.width; + + String spaceText = ''; + List.generate((indent / spaceWidth).floor(), (e) => null).forEach((element) { + spaceText += ' '; + }); + + return Container( + margin: EdgeInsets.only(top: 12.h, left: 14.w, right: 14.w), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), + constraints: BoxConstraints( + minHeight: 120.h, + maxHeight: 130.h, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(6.w)), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset(0, -0.4.h), //阴影y轴偏移量 + blurRadius: 10, //阴影模糊程度 + spreadRadius: 6, //阴影扩散程度 + ) + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Stack( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox(width: indent), + // if (isHomeworkCorrection) + // Container( + // padding: EdgeInsets.all(2.w), + // margin: EdgeInsets.only(right: 8.w), + // child: Icon(Icons.menu_book_rounded, size: 18.sp), + // ), + Expanded( + child: Text( + spaceText + markingItem.examName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(45, 56, 76, 1), + ), + ), + ), + ], + ), + Row( + children: [ + if (markingtype == MarkingListType.EXCEPTIONAL) + Container( + width: 20.w, + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 2.h), + margin: EdgeInsets.only(right: 8.w), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: + BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), + color: const Color.fromRGBO(245, 108, 108, 0.236), + ), + child: Text( + '异', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(215, 74, 74, 1), + ), + ), + ), + if (markingtype == MarkingListType.ARBITRATE) + Container( + width: 20.w, + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 2.h), + margin: EdgeInsets.only(right: 8.w), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: + BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), + color: const Color.fromRGBO(4, 201, 208, 0.10), + ), + child: Text( + '仲', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(4, 201, 208, 1), + ), + ), + ), + if (markingtype == MarkingListType.NORMAL) + Container( + width: 36.w, + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 2.h), + margin: EdgeInsets.only(right: 8.w), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: + BorderRadius.only(topLeft: Radius.circular(6.r), bottomRight: Radius.circular(6.r)), + color: const Color.fromRGBO(231, 236, 255, 1), + ), + child: Text( + '月考', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(46, 91, 255, 1), + ), + ), + ), + ], + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + getSubjectEnumName(markingItem.subjectId), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(80, 87, 103, 1), + ), + ), + Container( + width: 1.w, + height: 10.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + margin: EdgeInsets.symmetric(horizontal: 10.w), + ), + if (markingtype == MarkingListType.NORMAL || isHomeworkCorrection) + Text( + '题量:${markingItem.totalCount}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(80, 87, 103, 1), + ), + ) + else if (markingtype == MarkingListType.EXCEPTIONAL) + Container( + margin: EdgeInsets.only(right: 4.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning_amber_rounded, + color: const Color.fromRGBO(215, 74, 74, 1), + size: 15.sp, + ), + SizedBox(width: 2.w), + if ((markingItem.totalCount - markingItem.finishCount) > 0) + quickText( + '${markingItem.totalCount - markingItem.finishCount}个异常待处理', + color: Colors.red, + size: 11.sp, + ) + else + quickText('暂无异常', color: Colors.grey, size: 11.sp) + ], + ), + ) + else if (markingtype == MarkingListType.ARBITRATE) + Container( + margin: EdgeInsets.only(right: 4.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.edit_square, + color: const Color.fromRGBO(4, 201, 208, 1), + size: 15.sp, + ), + SizedBox(width: 2.w), + if ((markingItem.totalCount - markingItem.finishCount) > 0) + quickText( + '${markingItem.totalCount - markingItem.finishCount}个仲裁待处理', + color: const Color.fromRGBO(4, 201, 208, 1), + size: 11.sp, + ) + else + quickText('暂无仲裁', color: Colors.grey, size: 11.sp) + ], + ), + ) + ], + ), + Expanded(child: SizedBox()), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only(right: 4.w), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '${markingItem.finishCount}', + style: TextStyle( + color: markingItem.isFinish && isHomeworkCorrection + ? Colors.green + : Theme.of(context).primaryColor, + fontSize: 12.sp), + ), + Text( + '/', + style: TextStyle(color: const Color.fromRGBO(148, 163, 182, 1), fontSize: 12.sp), + ), + Text( + '${markingItem.totalCount}', + style: TextStyle(color: Color.fromRGBO(148, 163, 182, 1), fontSize: 12.sp), + ), + ], + ), + ), + Container( + width: 100.w, + child: LinearPercentIndicator( + padding: EdgeInsets.zero, + animation: true, + lineHeight: 8.h, + animationDuration: 2500, + percent: markingItem.completionRate, + center: Text( + '${markingItem.completionRateStr}%', + style: TextStyle(color: Colors.white, fontSize: 8.sp), + ), + // linearStrokeCap: LinearStrokeCap.butt, + progressColor: + markingItem.isFinish && isHomeworkCorrection ? Colors.green : Theme.of(context).primaryColor, + backgroundColor: const Color.fromRGBO(219, 224, 243, 1), + barRadius: Radius.circular(10.r), + ), + ), + ], + ), + ], + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (markingItem.isFinish) + Container( + margin: EdgeInsets.only(left: 7.w), + child: MaterialButton( + color: const Color.fromRGBO(245, 246, 251, 1), + disabledColor: const Color.fromRGBO(245, 246, 251, 1), + textColor: const Color.fromRGBO(80, 87, 103, 1), + minWidth: 60.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(6.w), + ), + ), + onPressed: () { + goToReviewHomework(markingItem: markingItem, ref: ref, context: context, call: () => call!()); + // goToReviewHomework(markingItem: markingItem, ref: ref, context: context); + }, + child: Text( + '已完成', + style: TextStyle( + fontSize: 16.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + ), + if (false && !markingItem.isFinish && !isHomeworkCorrection) + MaterialButton( + color: const Color.fromRGBO(245, 246, 251, 1), + disabledColor: const Color.fromRGBO(245, 246, 251, 1), + minWidth: 60.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(6.w)), + ), + onPressed: () { + int examSubjectId = markingItem.examSubjectId; + RouterManager.router.navigateTo( + context, + '${RouterManager.markingProgressPath}?examSubjectId=$examSubjectId&name=${Uri.encodeComponent(markingItem.examName)}', + transition: getTransition(), + ); + }, + child: Text( + '进度', + style: TextStyle( + fontSize: 16.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + /** 异常和仲裁没有回评 注释回评 + if (markingtype == MarkingListType.NORMAL && !markingItem.isFinish && markingItem.finishCount > 0) + Container( + margin: EdgeInsets.only(left: 7.w), + child: MaterialButton( + color: const Color.fromRGBO(245, 246, 251, 1), + disabledColor: const Color.fromRGBO(245, 246, 251, 1), + textColor: const Color.fromRGBO(80, 87, 103, 1), + minWidth: 60.w, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6.w))), + onPressed: () { + RouterManager.router.navigateTo( + context, + '${isHomeworkCorrection ? RouterManager.markingJobReviewPath : RouterManager.markingReviewPath}?markingtype=${markingtype?.index}&markingUserId=${markingItem.markingUserId}&examSubjectId=${markingItem.examSubjectId}&examName=${Uri.encodeComponent(markingItem.examName)}', + transition: getTransition(), + ); + }, + child: Text( + '回评', + style: TextStyle( + fontSize: 16.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + ), + */ + if (!markingItem.isFinish) + if ([MarkingListType.EXCEPTIONAL, MarkingListType.ARBITRATE].contains(markingtype)) + Container( + margin: EdgeInsets.only(left: 7.w), + child: MaterialButton( + color: const Color.fromRGBO(54, 86, 255, 0.99), + disabledColor: const Color.fromRGBO(54, 86, 255, 0.99), + onPressed: markingItem.isFinish + ? null + : () { + if (markingItem.finishCount == 0 && markingItem.totalCount == 0) { + return AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "暂时没有任务,休息一会吧", + color: Theme.of(context).primaryColor, + ).show(context); + } + goToReviewTestPaper( + markingtype: markingtype!, + markingItem: markingItem, + ref: ref, + context: context, + call: call, + ); + }, + minWidth: 60.w, + shape: RoundedRectangleBorder( + //边框颜色 + // side: const BorderSide( + // color: Colors.deepPurple, + // width: 1, + // ), + //边框圆角 + borderRadius: BorderRadius.all(Radius.circular(6.w)), + ), + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 4.h), + child: Text( + '处 理', + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w400, + ), + ), + ), + ) + else + Container( + margin: EdgeInsets.only(left: 7.w), + child: MaterialButton( + color: const Color.fromRGBO(54, 86, 255, 0.99), + disabledColor: const Color.fromRGBO(54, 86, 255, 0.99), + onPressed: markingItem.isFinish + ? null + : () { + if (markingItem.finishCount == 0 && markingItem.totalCount == 0) { + return AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "暂时没有任务,休息一会吧", + color: Theme.of(context).primaryColor, + ).show(context); + } + isHomeworkCorrection + ? goToReviewHomework( + markingItem: markingItem, ref: ref, context: context, call: () => call!()) + : goToReviewTestPaper( + markingtype: markingtype!, + markingItem: markingItem, + ref: ref, + context: context, + call: call, + ); + }, + minWidth: 60.w, + shape: RoundedRectangleBorder( + //边框颜色 + // side: const BorderSide( + // color: Colors.deepPurple, + // width: 1, + // ), + //边框圆角 + borderRadius: BorderRadius.all(Radius.circular(6.w)), + ), + child: Text( + isHomeworkCorrection ? '批阅' : '阅卷', + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + // 结束阅卷任务 + Future toTaskEnd(BuildContext context, WidgetRef ref, String markingUserId) async { + RestClient client = await getClient(); + BaseStructureResult result = await client.endMarkingTask(markingUserId); + if (result.code == RequestConfig.successCode && result.data == null ? false : result.data!) { + ref + .read(currentTaskIdProvider.notifier) + .setDoTaskEntity(CurrentReviewTask(taskId: markingItem.markingUserId, refresh: true)); + } else { + ToastUtils.getFluttertoast(context: context, msg: '提交失败'); + } + } + + // 批阅作业 + Future goToReviewHomework({ + required MarkingItem markingItem, + required WidgetRef ref, + required BuildContext context, + required VoidCallback call, + }) async { + int taskId = markingItem.markingUserId; + toPrint(val: '批阅作业.........'); + // ref.read(currentTaskIdProvider.notifier).setDoTaskEntity(CurrentReviewTask(taskId: taskId)); + // DetailId 回评需要传DetailId,正常阅卷不需要;PageOper(0:下一题,1:上一题,2:当前题) 回评需要传2 ;IsReview 是否会评 0否 1是 + RouterManager.router + .navigateTo(context, '${RouterManager.markingHomeworkDoPath}?taskId=$taskId', transition: getTransition()) + .then((value) { + call(); + }); + } + + // 批阅试卷 + Future goToReviewTestPaper({ + required MarkingListType markingtype, + required MarkingItem markingItem, + required WidgetRef ref, + required BuildContext context, + VoidCallback? call, + }) async { + int taskId = markingItem.markingUserId; + int examSubjectId = markingItem.examSubjectId; + ref.read(currentTaskIdProvider.notifier).setDoTaskEntity(CurrentReviewTask(taskId: taskId)); + + // DetailId 回评需要传DetailId,正常阅卷不需要;PageOper(0:下一题,1:上一题,2:当前题) 回评需要传2 ;IsReview 是否会评 0否 1是 + return RouterManager.router + .navigateTo( + context, + '${RouterManager.markingDoPath}?markingtype=${markingtype.index}&markingUserId=$taskId&examSubjectId=$examSubjectId&detailId=${null}&pageOper=${null}', + transition: getTransition(), + ) + .then((value) { + if (value == true && call != null) call(); + }); + } +} diff --git a/marking_app/lib/components/marking/Input_keyboard.dart b/marking_app/lib/components/marking/Input_keyboard.dart new file mode 100644 index 0000000..d914c94 --- /dev/null +++ b/marking_app/lib/components/marking/Input_keyboard.dart @@ -0,0 +1,470 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/marking/marking_text_question.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/marking_utils/index.dart'; + +// 输入型打分键盘 +class InputKeyboard extends StatefulHookConsumerWidget { + BoxDecoration? dexs; + BoxDecoration? notDexs; + TextStyle? theSty; + + QuestionTer? qter; + final MarkingTextQuestion data; + String questScore; // 当前分值 + String totalScore; // 总分 + bool hasSubtopic = false; // 是否有小题 + int subtopicIndex; // 小题下标 + SynchroScoreCallback synchroScore; + GestureTapCallback submitCall; + GestureTapCallback closeKeyboard; + + InputKeyboard({ + required this.data, + required this.questScore, + required this.totalScore, + required this.synchroScore, + required this.subtopicIndex, + required this.submitCall, + required this.closeKeyboard, + this.dexs, + this.notDexs, + this.theSty, + Key? key, + }) : super(key: key) { + hasSubtopic = data.subQuestionDetailList.isNotEmpty; // 是否有小题 + dexs = dexs ?? + BoxDecoration( + color: const Color.fromRGBO(249, 250, 254, 1), + borderRadius: BorderRadius.all(Radius.circular(4.w)), + border: Border.all( + width: 0.5.w, + color: const Color.fromRGBO(224, 230, 255, 1), + ), + ); + notDexs = notDexs ?? + BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.all(Radius.circular(4.w)), + border: Border.all( + width: 0.5.w, + color: const Color.fromRGBO(224, 230, 255, 1), + ), + ); + + theSty = theSty ?? + TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(80, 87, 103, 1), + ); + + qter = QuestionTer(item: data); + } + + @override + _InputTypeKeyboardState createState() => _InputTypeKeyboardState(); +} + +class _InputTypeKeyboardState extends ConsumerState { + String questScore = ''; // 已打分值 + bool hasZeroPointFive = false; + late Offset keyboardOrigin; // 键盘滑动起始坐标 + late Offset keyboarddestination; // 键盘滑动截止坐标 + late RemoveListener _markingSubtopicSwitchingListener; + + @override + void initState() { + super.initState(); + widget.qter?.addListener(() { + hasZeroPointFive = false; + }); + + _markingSubtopicSwitchingListener = ref.read(markingSubtopicSwitchingProvider.notifier).addListener((state) { + if (widget.subtopicIndex != state) widget.subtopicIndex = state; + if (widget.subtopicIndex > -1 && widget.hasSubtopic) { + toUpState(setState, () { + questScore = (widget.data.subQuestionDetailList[state].subQuestionGotScore ?? '').toString(); + }, mounted); + } + }, fireImmediately: false); + } + + @override + void dispose() { + super.dispose(); + _markingSubtopicSwitchingListener(); + widget.qter?.dispose(); + } + + // 清空得分 + void cleanCurrentScore() { + toUpState(setState, () { + hasZeroPointFive = false; + questScore = ''; + }, mounted); + + widget.synchroScore( + score: 0, + continueScoring: false, + hasSubtopic: widget.hasSubtopic, + cleanScore: true, + ); + } + + // 同步小题得分 + void synchroScore(String score, {bool full = false, bool zeroScore = false}) { + MarkingTextQuestion currentQuestion = widget.data; + bool hasSubtopic = widget.hasSubtopic; // 是否有小题 + + int subtopicIndex = widget.subtopicIndex; // 小题下标 + List subQuestionDetailList = currentQuestion.subQuestionDetailList; // 小题集合 + double totalScore = + hasSubtopic ? subQuestionDetailList[subtopicIndex].subQuestionScore : currentQuestion.totalScore; // 总分 + + double? doubleScore; + if (zeroScore) { + doubleScore = 0; + } else if (full) { + doubleScore = totalScore; + } else { + String newquestScore = questScore; + + /** 这是拼接打分 如:1+1 = 11 + if (questScore != '') { + List theScores = questScore.split('.'); + if (theScores.length > 1) { + if (theScores[1] == '0') { + newquestScore = theScores[0]; + } else { + newquestScore = ''; + } + // newquestScore + } + } + */ + + // doubleScore = double.parse(newquestScore + score); // 当前得分 + + doubleScore = double.parse(newquestScore == '' ? '0' : newquestScore) + double.parse(score); + print('最终得分111:${doubleScore}'); + } + + if (!full) { + // 非满分 + if (doubleScore > totalScore) { + // ToastUtils.showError('打分不得高于本${hasSubtopic ? '小' : ''}题总分,$totalScore 分'); + // score = ''; + // doubleScore = null; + // return; + + // 打分超过满分就定义为满分 + score = totalScore.toString(); + } else { + score = doubleScore.toString(); + } + } else { + score = totalScore.toString(); + } + + // 处理得分小数点 小数点后为0 取整 + List scoreStrList = score.toString().split('.'); + if (scoreStrList.length > 1) { + if (double.parse(scoreStrList[1]) == 0) { + score = scoreStrList[0]; + doubleScore = double.parse(score); + } + } + + toUpState(setState, () { + hasZeroPointFive = full || questScore.split('.').length > 1; // 包含了小数0.5 + questScore = score; + }, mounted); + widget.synchroScore(score: doubleScore, continueScoring: hasZeroPointFive, hasSubtopic: hasSubtopic); + } + + @override + Widget build(BuildContext context) { + BoxDecoration _notDexs = widget.notDexs!; + BoxDecoration _dexs = widget.dexs!; + + SubQuestions? subQuest; // 小题 + List? subQuestionDetailList; // 小题集合 + if (widget.hasSubtopic) { + subQuestionDetailList = widget.data.subQuestionDetailList; + subQuest = subQuestionDetailList[widget.subtopicIndex]; + } + + return Container( + width: 94.w, + height: ScreenUtil().screenHeight - 60.h, + padding: EdgeInsets.all(6.w), + margin: EdgeInsets.symmetric(vertical: 10.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4.w), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(2.w, 2.h), //阴影y轴偏移量 + blurRadius: 14, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 当前分值 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 53.w, + height: 20.w, + padding: EdgeInsets.symmetric(horizontal: 10.w), + alignment: Alignment.centerLeft, + decoration: widget.dexs, + child: Text(widget.questScore, style: widget.theSty), + ), + InkWell( + onTap: cleanCurrentScore, + child: Container( + width: 24.w, + height: 20.w, + alignment: Alignment.center, + decoration: widget.dexs, + child: Icon( + Icons.clear_sharp, + size: 24.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + ), + ), + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ScoreButton( + '满分', + widget.theSty!, + widget.dexs!, + (val) { + synchroScore(widget.hasSubtopic ? (subQuest!.subQuestionScore.toString()) : widget.totalScore, + full: true); + }, + ), + ScoreButton( + '零分', + widget.theSty!, + widget.dexs!, + (val) => synchroScore('0', zeroScore: true), + ), + ScoreButton( + '.5', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ScoreButton( + '7', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ScoreButton( + '8', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ScoreButton( + '9', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ScoreButton( + '4', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ScoreButton( + '5', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ScoreButton( + '6', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ScoreButton( + '1', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ScoreButton( + '2', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ScoreButton( + '3', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ScoreButton( + '0', + widget.theSty!, + hasZeroPointFive ? _notDexs : _dexs, + (val) { + if (!hasZeroPointFive) { + synchroScore(val); + } + }, + ), + InkWell( + onTap: () { + easyThrottle('toMarkingVal', () => widget.submitCall(), duration: Duration(seconds: 1)); + }, + child: Container( + width: 53.w, + height: 20.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color.fromRGBO(54, 97, 255, 1), + borderRadius: BorderRadius.all(Radius.circular(4.w)), + border: Border.all(width: 0.5.w, color: const Color.fromRGBO(224, 230, 255, 1)), + ), + child: Text( + '提 交', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ], + ), + onPanStart: (DragStartDetails detail) => keyboardOrigin = detail.localPosition, + onPanUpdate: (DragUpdateDetails detail) => keyboarddestination = detail.localPosition, + onPanEnd: (DragEndDetails details) { + bool flag = (keyboardOrigin.dy - keyboarddestination.dy).abs() > 50.0 || + (keyboardOrigin.dx - keyboarddestination.dx).abs() > 40.0; + if (!flag) return; + widget.closeKeyboard(); + }, + ), + ); + } +} + +// 打分按钮 +// ignore: must_be_immutable +class ScoreButton extends StatelessWidget { + String scoreVal; + Function call; + final TextStyle _theSty; + final BoxDecoration dexs; + ScoreButton(this.scoreVal, this._theSty, this.dexs, this.call, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => call(scoreVal), + child: Container( + width: 24.w, + height: 20.w, + alignment: Alignment.center, + decoration: dexs, + child: Text(scoreVal, style: _theSty), + ), + ); + } +} + +// 监听试题数据变化 +class QuestionTer extends ChangeNotifier { + MarkingTextQuestion item; + + QuestionTer({required this.item}) { + notifyListeners(); + } +} diff --git a/marking_app/lib/components/marking/Input_keyboard_guide_page.dart b/marking_app/lib/components/marking/Input_keyboard_guide_page.dart new file mode 100644 index 0000000..61468d7 --- /dev/null +++ b/marking_app/lib/components/marking/Input_keyboard_guide_page.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/utils/my_text.dart'; + +// 输入型键盘指导页 +class InputKeyboardGuidePage extends StatelessWidget { + final GestureTapCallback closeCall; + const InputKeyboardGuidePage({required this.closeCall,Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: double.infinity, + width: double.infinity, + color: const Color.fromRGBO(0, 0, 0, 0.65), + alignment: Alignment.centerRight, + child: Container( + padding: EdgeInsets.only(right: 8.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height:20.h), + quickText('向下或者向上滑动可关闭键盘', size: 16.sp, color: Colors.white), + SizedBox(height: 30.h), + Image.asset( + "assets/images/guide_page_gesture.png", + height: 60.w, + width: 60.w, + fit: BoxFit.fitHeight, + ), + SizedBox(height: 20.h), + SizedBox( + height: 40.h, + width: 50.w, + child: FloatingActionButton.extended( + onPressed: closeCall, + backgroundColor: const Color.fromARGB(51, 29, 29, 29), + splashColor: Theme.of(context).primaryColor, + label: quickText("我知道啦", color: Colors.white,size: 17.sp), + ), + ) + ], + ), + ), + ); + } +} diff --git a/marking_app/lib/components/marking/bottom_annotation_switch.dart b/marking_app/lib/components/marking/bottom_annotation_switch.dart new file mode 100644 index 0000000..607722f --- /dev/null +++ b/marking_app/lib/components/marking/bottom_annotation_switch.dart @@ -0,0 +1,298 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/index.dart'; + +/** + * 底部批注开关 + */ +class BottomAnnotationSwitch extends StatefulHookConsumerWidget { + final double? maxWidth; + final bool homework; + const BottomAnnotationSwitch({this.maxWidth, this.homework = false, Key? key}) : super(key: key); + + @override + _BottomAnnotationSwitchState createState() => _BottomAnnotationSwitchState(); +} + +class _BottomAnnotationSwitchState extends ConsumerState + with SingleTickerProviderStateMixin, EventBusMixin { + late RemoveListener _annotationsListener; // 批注关闭监听 + late AnimationController _animationController; // 动画 + late AnnotationGraffitiSwitch graffitiSwitch; + late DoMarkingKeyboardModel _preferenceModel; + late double upperBound; + late double lowerBound; + Color? bgc; + bool isIos = false; + + @override + void initState() { + if (Platform.isIOS) { + toPrint(val: 'IOS'); + isIos = true; + } else if (Platform.isAndroid) { + toPrint(val: '安卓'); + } + var graffitiHander = ref.read(annotationGraffitiSwitchProvider.notifier); + if (widget.homework) { + setTimeOut(500, () { + graffitiHander.setSwitch(true); + if (!graffitiHander.state.openBrush) graffitiHander.setSwitchBrush(); + }); // 默认打开可以书写 + } + + _preferenceModel = ref.read(markingKeyboardProvider.notifier).state; // 偏好设置 + bool isVertical = _preferenceModel.screenDirection == ScreenDirection.VERTICAL_SCREEN; // 是否是垂直方向 + + switch (_preferenceModel.keyboard) { + case KeyboardType.RIGHT_SELECTION: + double isVerticalNumber = 58.w; + double noVerticalNumber = 28.w; + if (isIos) { + noVerticalNumber = 50.w; + } + upperBound = ScreenUtil().screenWidth - (isVertical ? isVerticalNumber : noVerticalNumber); + lowerBound = isVertical ? 34.w : 18.w; + break; + case KeyboardType.INPUT_TYPE: + // 输入型键盘不存在竖屏 + upperBound = ScreenUtil().screenWidth - (isIos ? 115.w : 95.w); + lowerBound = 18.w; + + break; + case KeyboardType.BOTTOM_SELECTION: + upperBound = ScreenUtil().screenWidth; + lowerBound = isVertical ? 34.w : 18.w; + + break; + } + + upperBound = widget.maxWidth ?? upperBound; + _animationController = AnimationController( + value: graffitiHander.state.annotationSwitch ? upperBound : lowerBound, // 设置默认值 + duration: const Duration(milliseconds: 300), + lowerBound: lowerBound, + upperBound: upperBound, + vsync: this, + )..addListener(toUp); + + _annotationsListener = graffitiHander.addListener((state) { + graffitiSwitch = state; + if (state.annotationSwitch) { + bgc = const Color.fromRGBO(51, 57, 62, 1); + // toUp(); + _animationController.forward(); + } else { + _animationController.reverse(); + setTimeOut(300, () => bgc = null); + } + }); + + super.initState(); + } + + @override + void dispose() { + _annotationsListener(); + _animationController + ..removeListener(toUp) + ..dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + ScreenDirection _screenDirection = _preferenceModel.screenDirection; + bool isVertical = _screenDirection == ScreenDirection.VERTICAL_SCREEN; // 是否是垂直 + + // print('当前屏幕情况:${graffitiSwitch.openBrush}'); + // print(isVertical); + + AnnotationGraffitiSwitch _graffitiSwitch = ref.watch(annotationGraffitiSwitchProvider); + + double barrierSize = ScreenUtil().screenWidth / (isVertical ? 20 : 6); + Color actionColor = Colors.white; + Color defaultColor = Color.fromRGBO(132, 146, 163, 1); + return OrientationBuilder( + builder: (BuildContext context, Orientation orientation) { + bool isVertical = orientation == Orientation.portrait; + + double iconSize = (isVertical ? 32 : 28).sp; + return Container( + height: 52.h, + // width: widget.homework ? double.infinity : _animationController.value, + width: _animationController.value, + color: widget.homework ? Color.fromRGBO(51, 57, 62, 0.2) : bgc, + padding: EdgeInsets.symmetric(vertical: 1.h), + child: widget.homework + ? Row( + children: [ + SizedBox(width: barrierSize + 20.w), + InkWell( + onTap: () { + easyThrottle('setSwitchBrush', + () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchBrush()); + }, + child: Icon(const IconData(0xe623, fontFamily: "AlibabaIcon"), + size: iconSize, color: _graffitiSwitch.openBrush ? actionColor : defaultColor), + ), + SizedBox(width: 24.w), + InkWell( + onTap: () { + eventFire(model: BottomAnnotationSwitchCleanall(previousStep: true)); + }, + child: Icon(IconData(0xe61d, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), + ), + const Expanded(child: SizedBox()), + InkWell( + onTap: () { + easyThrottle('setSwitchMagnifier', + () => ref.read(annotationGraffitiSwitchProvider.notifier).setMagnifier()); + }, + // IconData(0xe62f, fontFamily: "AlibabaIcon") + child: Icon(IconData(0xe634, fontFamily: "AlibabaIcon"), + size: iconSize, color: _graffitiSwitch.magnifier ? actionColor : defaultColor), + ), + SizedBox(width: 24.w), + InkWell( + onTap: () { + eventFire(model: BottomAnnotationSwitchCleanall(cleanAll: true)); + }, + child: Icon(IconData(0xe61f, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), + ), + SizedBox(width: 24.w), + InkWell( + onTap: () { + easyThrottle( + 'setSwitchMagnifier', + () => ref.read(annotationGraffitiSwitchProvider.notifier).setTrajectory(), + ); + }, + child: Icon( + IconData(0xe629, fontFamily: "AlibabaIcon"), + size: iconSize, + color: _graffitiSwitch.trajectoryDisplay ? actionColor : defaultColor, + ), + ), + SizedBox(width: isIos ? 40.w : 50.w), + ], + ) + : Stack( + alignment: const FractionalOffset(0, 0.5), + children: [ + if (bgc != null) + Row( + children: [ + SizedBox(width: barrierSize + 20.w), + InkWell( + onTap: () { + easyThrottle('setSwitchBrush', + () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchBrush()); + }, + child: Icon(const IconData(0xe623, fontFamily: "AlibabaIcon"), + size: iconSize, color: _graffitiSwitch.openBrush ? actionColor : defaultColor), + ), + SizedBox(width: 24.w), + InkWell( + onTap: () { + eventFire(model: BottomAnnotationSwitchCleanall(previousStep: true)); + }, + child: + Icon(IconData(0xe61d, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), + ), + + // 不需要橡皮擦 + // InkWell( + // onTap: () { + // easyThrottle( + // 'setSwitchEraser', () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchEraser()); + // }, + // child: Icon(const IconData(0xe61c, fontFamily: "AlibabaIcon"), + // color: _graffitiSwitch.openEraser ? Theme.of(context).primaryColor : Colors.white), + // ), + const Expanded(child: SizedBox()), + InkWell( + onTap: () { + easyThrottle('setSwitchMagnifier', + () => ref.read(annotationGraffitiSwitchProvider.notifier).setMagnifier()); + }, + child: Icon(IconData(0xe62f, fontFamily: "AlibabaIcon"), + size: iconSize, color: _graffitiSwitch.magnifier ? actionColor : defaultColor), + ), + SizedBox(width: 24.w), + InkWell( + onTap: () { + eventFire(model: BottomAnnotationSwitchCleanall(cleanAll: true)); + }, + child: + Icon(IconData(0xe61f, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), + ), + // SizedBox(width: 24.w), + // InkWell( + // onTap: (){ + // eventFire(model:BottomAnnotationSwitchCleanall(uploadImage: true)); + // }, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Icon(const IconData(0xe614, fontFamily: "AlibabaIcon"), color: Colors.white,size: 22.sp,), + // quickText('提交批注',color: Colors.white,size: 9.sp), + // ], + // ) + // ), + + SizedBox(width: isIos ? 40.w : 26.w), + ], + ), + InkWell( + onTap: () { + easyThrottle('setSwitchMarkingGraffiti', () { + if ([upperBound, lowerBound].contains(_animationController.value)) { + ref + .read(annotationGraffitiSwitchProvider.notifier) + .setSwitch(!graffitiSwitch.annotationSwitch); + } + }); + }, + child: Container( + // width: isVertical ? 34.w : 16.w, + width: barrierSize, + padding: EdgeInsets.only(top: 4.h, bottom: 4.h, right: 3.w), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + color: const Color.fromRGBO(253, 147, 21, 1), + borderRadius: BorderRadius.only( + topRight: Radius.circular(30.sp), + bottomRight: Radius.circular(30.sp), + ), + ), + child: Icon( + !graffitiSwitch.annotationSwitch + ? const IconData(0xe622, fontFamily: "AlibabaIcon") + : const IconData(0xe621, fontFamily: "AlibabaIcon"), + color: Colors.white, + size: isVertical ? 20.sp : 26.sp, + ), + ), + ) + ], + ), + ); + }, + ); + } + + void toUp() { + toUpState(setState, () {}, mounted); + } +} diff --git a/marking_app/lib/components/marking/marking_keyboard_switch.dart b/marking_app/lib/components/marking/marking_keyboard_switch.dart new file mode 100644 index 0000000..6bfc09b --- /dev/null +++ b/marking_app/lib/components/marking/marking_keyboard_switch.dart @@ -0,0 +1,63 @@ +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/marking_text_question.dart'; +import 'package:marking_app/components/marking/Input_keyboard.dart'; +import 'package:marking_app/components/marking/selectable_keyboard.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/marking_utils/index.dart'; + +// 键盘切换 +class MarkingKeyboardSwitch extends ConsumerWidget { + final MarkingTextQuestion data; + final int markingUserId; + String questScore; // 当前分值 + String totalScore; // 总分 + int subtopicIndex; // 小题下标 + SynchroScoreCallback synchroScore; + GestureTapCallback submitCall; + GestureTapCallback closeKeyboard; + + MarkingKeyboardSwitch({ + required this.data, + required this.markingUserId, + required this.questScore, + required this.totalScore, + required this.synchroScore, + required this.subtopicIndex, + required this.submitCall, + required this.closeKeyboard, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final marking = ref.watch(markingKeyboardProvider); + + switch (marking.keyboard) { + case KeyboardType.INPUT_TYPE: + // 输入键盘 + return InputKeyboard( + data: data, + subtopicIndex: subtopicIndex, + questScore: questScore, + totalScore: data.totalScore.toString(), + closeKeyboard: closeKeyboard, + submitCall: submitCall, + synchroScore: synchroScore, + ); + case KeyboardType.RIGHT_SELECTION: // 右侧选择键盘 + return SelectableKeyboard( + data: data, + markingUserId: markingUserId, + subtopicIndex: subtopicIndex, + questScore: questScore, + totalScore: data.totalScore, + submitCall: submitCall, + synchroScore: synchroScore, + ); + case KeyboardType.BOTTOM_SELECTION: + return const SizedBox(); + } + } +} diff --git a/marking_app/lib/components/marking/marking_question_type_drawer.dart b/marking_app/lib/components/marking/marking_question_type_drawer.dart new file mode 100644 index 0000000..178ec3c --- /dev/null +++ b/marking_app/lib/components/marking/marking_question_type_drawer.dart @@ -0,0 +1,95 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; +import 'package:marking_app/utils/my_text.dart'; + +class MarkingQuestionTypeDrawer extends StatefulHookConsumerWidget { + final double widthPercent; + final GlobalKey scaffoldKey; + final List currentTabs; + final MarkingTextQuestionTab? currentTab; // 当前选中tab + final Function(String tabQuestionNum) call; + const MarkingQuestionTypeDrawer( + {required this.widthPercent, + required this.currentTabs, + required this.currentTab, + required this.scaffoldKey, + required this.call, + super.key}); + + @override + _MarkingQuestionTypeDrawerState createState() => _MarkingQuestionTypeDrawerState(); +} + +class _MarkingQuestionTypeDrawerState extends ConsumerState { + @override + Widget build(BuildContext context) { + final double _width = MediaQuery.of(context).size.width * widget.widthPercent; + return SafeArea( + child: Drawer( + width: _width, + child: Container( + height: double.infinity, + // color: Colors.red, + child: ListView.builder( + itemBuilder: (context, index) { + MarkingTextQuestionTab theBatchTab = widget.currentTabs[index]; + // bool taskCompleted = !getTagCondition(theBatchTab); + // bool taskCompleted = theBatchTab.percent == 1 && theBatchTab.total == theBatchTab.finishCount; + + bool taskCompleted = theBatchTab.isFinished; + if (theBatchTab.isExcess && theBatchTab.finishCount >= theBatchTab.total) { + // 超量题 + taskCompleted = true; + } + + return ListTile( + onTap: () { + if (widget.currentTab == theBatchTab) return; + + if (theBatchTab.total == 0) { + AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "当前试题Tag已经评阅完成。", + color: Theme.of(context).primaryColor, + ).show(context); + widget.scaffoldKey.currentState?.closeDrawer(); + return; + } + widget.call(theBatchTab.questionNum); + }, + tileColor: theBatchTab == widget.currentTab ? const Color.fromRGBO(46, 91, 255, 0.1) : null, + leading: Icon( + IconData(taskCompleted ? 0xe606 : (theBatchTab.isExcess ? 0xe628 : 0xe627), + fontFamily: "AlibabaIcon"), + color: taskCompleted ? Theme.of(context).primaryColor : null), + title: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText('第${theBatchTab.questionNum}题', + color: const Color.fromRGBO(45, 56, 76, 1), size: 19.sp, fontWeight: FontWeight.w500), + SizedBox(width: 3.w), + Padding( + padding: EdgeInsets.only(top: 2.h), + child: quickText( + // '${theBatchTab.finishCount}/${theBatchTab.isExcess ? (theBatchTab.total > theBatchTab.excessAvgCount ? theBatchTab.total : theBatchTab.excessAvgCount) : '${theBatchTab.total}'}', + '${theBatchTab.finishCount}/${theBatchTab.total}', + color: const Color.fromRGBO(148, 163, 182, 1), + size: 18.sp, + fontWeight: FontWeight.w400)), + // if (theBatchTab.isExcess) quickText(' 超量题', size: 10.sp, color: const Color.fromRGBO(148, 163, 182, 1)) + ], + ), + ); + }, + itemCount: widget.currentTabs.length, + ), + ), + ), + ); + } +} diff --git a/marking_app/lib/components/marking/marking_seting.dart b/marking_app/lib/components/marking/marking_seting.dart new file mode 100644 index 0000000..8d41be5 --- /dev/null +++ b/marking_app/lib/components/marking/marking_seting.dart @@ -0,0 +1,602 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:flutter_neumorphic/flutter_neumorphic.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/common/model/marking/marking_common_score_items.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/marking_utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:syncfusion_flutter_sliders/sliders.dart'; + +part 'marking_seting.g.dart'; + +// 阅卷设置页面 +class MarkingSeting extends StatefulHookConsumerWidget { + final VoidCallbackFutureBool close; + final int markingUserId; // 试卷ID + final String questionNum; // 题型题号 + double defaultScoreInterval; // 试题服务器间隔数据值 + final double? questTotalScore; // 当前试题总分 + final String? tagName; // 试题批次名称 + MarkingSeting({ + required this.defaultScoreInterval, + required this.close, + required this.markingUserId, + required this.questionNum, + this.tagName, + this.questTotalScore, + Key? key, + }) : super(key: key) { + defaultScoreInterval = defaultScoreInterval > 0 ? defaultScoreInterval : 1; + } + + @override + _MarkingSetingState createState() => _MarkingSetingState(); +} + +class _MarkingSetingState extends ConsumerState { + late List keyboardTypes; // 键盘集合 + late DoMarkingKeyboardModel keyboardModel; // 当前的键盘方式 + late bool isHorizontal; // 是否横屏 + SortKeyboard? sortType; // 键盘排序方式 + late double scoreInterval; // 分值间隔设置 + late int keyboardIndex; // 键盘下标 + late bool autoSubmitToNextQuestion; // 提交并自动跳转下一题 + late RemoveListener _markingKeyboardListener; + MarkingCommonScoreItems? _commonScoreItems; + @override + void initState() { + // 当前 + _markingKeyboardListener = ref.read(markingKeyboardProvider.notifier).addListener((state) { + MarkingCommonScoreItems? theCommonScores = state.commonScores; + if (theCommonScores != null && + widget.questionNum == theCommonScores.questionNum && + widget.markingUserId == theCommonScores.id) { + _commonScoreItems = state.commonScores; + } + keyboardModel = state; + keyboardIndex = state.keyboard.index; + sortType = state.sort; + autoSubmitToNextQuestion = state.autoSubmitToNextQuestion; + scoreInterval = state.getScoreStepSize( + widget.markingUserId, widget.questionNum, widget.defaultScoreInterval); // 或者对应试卷和试题的步长 + isHorizontal = state.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; + + keyboardTypes = KeyboardType.values.map((e) { + bool isCurrent = keyboardModel.keyboard == e; // 当前使用的键盘是不是当前这个键盘 + bool isInput = e == KeyboardType.INPUT_TYPE; + + DoMarkingKeyboardModel item = DoMarkingKeyboardModel( + keyboard: e, + open: isInput ? keyboardModel.open : true, // 只有输入型键盘可以开启关闭 + guidePageDisplay: isInput ? keyboardModel.guidePageDisplay : false, // 只有输入型键盘有引导页 + ); + + if (isCurrent) { + item.sort = state.sort; + item.autoSubmitToNextQuestion = state.autoSubmitToNextQuestion; + } + return TheDoMarkingKeyboardModel(item, isCurrent); + }).toList(); + toSetUp(); + }); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _markingKeyboardListener(); + } + + void toSetUp() { + toUpState(setState, () {}, mounted); + } + + // 设置 + void initMarkingSet() { + TheDoMarkingKeyboardModel theCurrent = keyboardTypes[keyboardIndex]; // 当前选中的键盘 + DoMarkingKeyboardModel model = theCurrent.model; + switch (model.keyboard) { + case KeyboardType.INPUT_TYPE: + // 输入型键盘 + break; + case KeyboardType.RIGHT_SELECTION: + case KeyboardType.BOTTOM_SELECTION: + // 底部键盘和右侧键盘 + model.setScoreStepSize( + widget.markingUserId, widget.questionNum, widget.defaultScoreInterval, scoreInterval); // 设置分值 + model.sort = sortType; + break; + } + model.screenDirection = keyboardModel.screenDirection; + model.autoSubmitToNextQuestion = autoSubmitToNextQuestion; + if (_commonScoreItems != null) model.commonScores = _commonScoreItems!.score.isNotEmpty ? _commonScoreItems : null; + ref.read(markingKeyboardProvider.notifier).toggleKeyboard(model); + setTimeOut(300, () => widget.close(true)); + + /// 取消保存数据确认按钮 + // showDialog( + // // 表示点击灰色背景的时候是否消失弹出框 + // barrierDismissible: false, + // context: context, + // builder: (context1) { + // return AlertDialog(content: quickText("请确定当前阅卷习惯配置?"), actions: [ + // TextButton( + // child: quickText("取消"), + // onPressed: () { + // Navigator.pop(context1, 'Cancle'); + // }, + // ), + // TextButton( + // child: quickText("确定", color: Theme.of(context).primaryColor), + // onPressed: () { + // TheDoMarkingKeyboardModel theCurrent = keyboardTypes[keyboardIndex]; // 当前选中的键盘 + // DoMarkingKeyboardModel model = theCurrent.model; + // switch (model.keyboard) { + // case KeyboardType.INPUT_TYPE: + // // 输入型键盘 + // break; + // case KeyboardType.RIGHT_SELECTION: + // case KeyboardType.BOTTOM_SELECTION: + // // 底部键盘和右侧键盘 + // model.setScoreStepSize(widget.markingUserId, widget.questionNum, widget.defaultScoreInterval, scoreInterval); // 设置分值 + // model.sort = sortType; + // break; + // } + // model.screenDirection = keyboardModel.screenDirection; + // model.autoSubmitToNextQuestion = autoSubmitToNextQuestion; + // ref.read(markingKeyboardProvider.notifier).toggleKeyboard(model); + // Navigator.pop(context1, 'Cancle'); + // setTimeOut(300, () => widget.close(true)); + // }) + // ]); + // }); + } + + @override + Widget build(BuildContext context) { + return Container( + height: double.infinity, + width: double.infinity, + color: const Color.fromARGB(255, 233, 232, 232), + child: Stack( + children: [ + Container( + color: const Color.fromRGBO(249, 250, 254, 1), + alignment: Alignment.center, + child: quickText( + "学而有道", + size: 170.sp, + color: Color.fromRGBO(255, 255, 255, 1), + ), + ), + Column( + children: [ + getHeadBox(), + Expanded( + child: Container( + width: double.infinity, + // color: const Color.fromARGB(255, 233, 232, 232), + padding: EdgeInsets.only(top: 12.h, left: 10.w), + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.start, + children: [ + getKeyboardBox(), + getAutoNextQuestBox(), + getScoreIntervalSetBox(), + if (keyboardIndex != 0) + $CommonScoringList( + score: widget.questTotalScore!, + scoreInterval: scoreInterval, + commonScoreItems: _commonScoreItems, + call: (List val) { + _commonScoreItems = MarkingCommonScoreItems( + id: widget.markingUserId, + questionNum: widget.questionNum, + score: val, + ); + setState(() {}); + }, + ), + getSetScoringRulesBox(), + ], + ), + ), + ) + ], + ) + ], + ), + ); + } + + // 获取键盘区域 + Widget getKeyboardBox() { + bool isBroadwise = keyboardModel.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横向 + + return SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText( + '键盘选择', + color: const Color.fromRGBO(45, 56, 76, 1), + size: 14.sp, + fontWeight: FontWeight.w500, + ), + Wrap( + children: keyboardTypes.asMap().keys.map((e) { + TheDoMarkingKeyboardModel item = keyboardTypes[e]; + bool isInputAndHor = !isHorizontal && item.model.keyboard == KeyboardType.INPUT_TYPE; // 横屏和输入键盘 + return Padding( + padding: EdgeInsets.only(right: 36.w, left: isBroadwise ? 0 : 5.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8.w, + margin: EdgeInsets.only(right: isBroadwise ? 2.w : 8.w), + child: Checkbox( + tristate: isInputAndHor, // 三态复选框,默认 false,当设置为 true 时,设置 value = null,复选框中间会变成破折号(-) + activeColor: Theme.of(context).primaryColor, + checkColor: Colors.white, + value: isInputAndHor ? null : keyboardIndex == e, + onChanged: (value) { + if (keyboardIndex == e || isInputAndHor) return; + keyboardIndex = e; + DoMarkingKeyboardModel model = keyboardTypes[keyboardIndex].model; + if (model.keyboard != KeyboardType.INPUT_TYPE) { + // 不重置,若后面需要重置再打开 + // scoreInterval = model.getScoreStepSize(widget.markingUserId, widget.questionNum, widget.defaultScoreInterval); + sortType ??= model.sort; + } + toSetUp(); + }, + ), + ), + InkWell( + onTap: () { + if (keyboardIndex == e || isInputAndHor) return; + keyboardIndex = e; + DoMarkingKeyboardModel model = keyboardTypes[keyboardIndex].model; + if (model.keyboard != KeyboardType.INPUT_TYPE) { + // 不重置,若后面需要重置再打开 + // scoreInterval = model.getScoreStepSize(widget.markingUserId, widget.questionNum, widget.defaultScoreInterval); + sortType ??= model.sort; + } + toSetUp(); + }, + child: quickText( + item.title, + color: const Color.fromRGBO(80, 87, 103, 1), + size: 14.sp, + ), + ), + ], + ), + ); + }).toList(), + ), + SizedBox(height: 20.h), + ], + ), + ); + } + + // 自动跳转下一题 + Widget getAutoNextQuestBox() { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(keyboardTypes[keyboardIndex].model.keyboard == KeyboardType.INPUT_TYPE ? '自动跳转下一题' : '自动提交', + size: 14.sp, color: const Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.bold), + Transform.scale( + scale: 0.7, + alignment: Alignment.centerLeft, + child: CupertinoSwitch( + value: autoSubmitToNextQuestion, + activeColor: Theme.of(context).primaryColor, + trackColor: const Color.fromRGBO(148, 163, 182, 1), + //dragStartBehavior: DragStartBehavior.start, + onChanged: (value) { + setState(() { + autoSubmitToNextQuestion = value; + }); + }, + ), + ), + ], + ); + } + + // 给分规则区域 + Widget getSetScoringRulesBox() { + if (keyboardTypes[keyboardIndex].model.keyboard == KeyboardType.INPUT_TYPE) { + return Container(); + } + + bool isBroadwise = keyboardModel.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横向 + + return Container( + margin: EdgeInsets.only(top: 20.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText( + '键盘排序方式', + size: 14.sp, + color: const Color.fromRGBO(45, 56, 76, 1), + fontWeight: FontWeight.bold, + ), + Wrap( + children: SortKeyboard.values + .where((element) => element != SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER) + .map((e) { + String title; + switch (e) { + case SortKeyboard.INVERTED_ORDER: + title = '按倒序排列'; + break; + case SortKeyboard.FULL_AND_AERO_TOP: + // title = '顺序排列满分零分置顶'; + title = '按顺序排列'; + break; + case SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER: + title = '按倒序排列满分零分置顶'; + break; + } + return Padding( + padding: EdgeInsets.only(right: 30.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8.w, + margin: EdgeInsets.only(right: isBroadwise ? 2.w : 8.w, left: isBroadwise ? 0 : 5.w), + child: Checkbox( + activeColor: Theme.of(context).primaryColor, + checkColor: Colors.white, + value: e == sortType, + onChanged: (value) { + sortType = e; + toSetUp(); + }, + ), + ), + InkWell( + onTap: () { + sortType = e; + toSetUp(); + }, + child: quickText( + title, + color: const Color.fromRGBO(80, 87, 103, 1), + size: 14.sp, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); + } + + // 分值间隔设置 + Widget getScoreIntervalSetBox() { + if (keyboardTypes[keyboardIndex].model.keyboard == KeyboardType.INPUT_TYPE) { + return Container(); + } + + bool isBroadwise = keyboardModel.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横向 + + return Container( + margin: EdgeInsets.only(top: 20.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + AchievementView(title: "分值间隔", subTitle: "设定当前题目的分数按钮的间隔分值", color: Theme.of(context).primaryColor) + .show(context); + }, + child: Row( + children: [ + Container( + padding: EdgeInsets.only(right: 2.w), + alignment: Alignment.bottomLeft, + child: quickText( + '分值间隔设置', + size: 14.sp, + color: const Color.fromRGBO(45, 56, 76, 1), + fontWeight: FontWeight.bold, + ), + ), + Tooltip( + message: '这是一个提示', + child: Icon(Icons.help, color: Colors.black, size: 18.sp), + ), + ], + ), + ), + SfSlider( + onChanged: (value) { + int flVal = value.floor(); + double differenceVal = value - flVal; + if (differenceVal != 0) { + value = differenceVal > 0.5 ? value.ceil().toDouble() : flVal + 0.5; + } + if (widget.questTotalScore != null && value > widget.questTotalScore) { + value = widget.questTotalScore; + AchievementView( + icon: Icon( + Icons.warning_amber, + color: Colors.yellow, + size: 40.sp, + ), + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "设定分值间隔不得大于当前分试题总分", + textStyleTitle: TextStyle(color: Colors.yellow), + color: Theme.of(context).primaryColor) + .show(context); + } + toUpState(setState, () => scoreInterval = value, mounted); + }, + showTicks: true, + showLabels: true, + enableTooltip: true, + //值 + value: scoreInterval, + //滑块划过的颜色 + activeColor: Theme.of(context).primaryColor, + //滑块未划过的颜色 + inactiveColor: Colors.grey[400], + min: 0.5, + max: 5, + interval: 0.5, + ), + ], + ), + ); + } + + // 顶部区域 + Widget getHeadBox() { + return Container( + height: 50.h, + color: const Color.fromRGBO(255, 255, 255, 1), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 10.w), + InkWell( + onTap: () => widget.close(false), + child: Icon(Icons.arrow_back_ios, size: 28.sp), + ), + SizedBox(width: 4.w), + quickText('阅卷操作设置', size: 17.sp), + Expanded( + child: Container( + padding: EdgeInsets.only(right: 12.w), + alignment: Alignment.centerRight, + child: InkWell( + onTap: initMarkingSet, + child: quickText('保存', size: 18.sp, color: Theme.of(context).primaryColor), + ), + ), + ) + ], + ), + ); + } + + void _showPopover(BuildContext context) {} +} + +class TheDoMarkingKeyboardModel { + final DoMarkingKeyboardModel model; + bool current; + late String title; + + TheDoMarkingKeyboardModel(this.model, this.current) { + switch (model.keyboard) { + case KeyboardType.INPUT_TYPE: + title = '输入型键盘'; + break; + case KeyboardType.RIGHT_SELECTION: + title = '右侧选择型键盘'; + break; + case KeyboardType.BOTTOM_SELECTION: + title = '底部选择型键盘'; + break; + } + } +} + +// 常用打分项列表 +@hwidget +Widget $commonScoringList(BuildContext context, + {required double score, + required double scoreInterval, + required MarkingCommonScoreItems? commonScoreItems, + required Function(List) call}) { + List array = List.generate(score.ceil() + 1, (index) => index).where((element) => element > 0).toList(); + + // useEffect(() { + // return () {}; + // }, []); + return Container( + width: double.infinity, + height: 80.h, + margin: EdgeInsets.only(top: 30.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: + quickText('常用打分项', size: 14.sp, color: const Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.bold), + ), + SizedBox(height: 10.h), + Expanded( + child: ListView( + scrollDirection: Axis.horizontal, // 设置垂直滚动, + children: array.map((e) { + bool isSelected = (commonScoreItems?.score.contains(e) ?? false); + return InkWell( + splashColor: Colors.cyanAccent, + onTap: () { + var items = commonScoreItems?.score ?? []; + double val = e.toDouble(); + if (items.contains(val)) + items = items.where((e1) => e1 != val).toList(); + else + items.add(val); + call(items); + }, + child: Container( + width: 20.w, + height: 56.h, + margin: EdgeInsets.only(top: 3.h, right: 6.w), + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all( + width: 1.h, + color: isSelected + ? Theme.of(context).primaryColor.withOpacity(0.5) + : const Color.fromRGBO(224, 230, 255, 1), + ), + color: isSelected ? Colors.white : Colors.grey[300], + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + child: quickText( + e, + size: 15.sp, + color: isSelected ? Theme.of(context).primaryColor : const Color.fromRGBO(148, 163, 182, 1), + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ); +} diff --git a/marking_app/lib/components/marking/marking_seting_main.dart b/marking_app/lib/components/marking/marking_seting_main.dart new file mode 100644 index 0000000..61dec85 --- /dev/null +++ b/marking_app/lib/components/marking/marking_seting_main.dart @@ -0,0 +1,245 @@ +// 设置 +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/toast_utils.dart'; + +class SetingBox extends ConsumerWidget { + final Function closeHandle; + final Function viewWholeTestPaper; + final Function viewAnswer; + final Function exitPaper; + final Function setingOperation; + const SetingBox( + {required this.closeHandle, + required this.viewWholeTestPaper, + required this.viewAnswer, + required this.exitPaper, + required this.setingOperation, + Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + DoMarkingKeyboardModel model = ref.watch(markingKeyboardProvider); + bool isBroadwise = model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否横向 + double textSize = isBroadwise ? 14.sp : 12.sp; + return Stack( + alignment: const FractionalOffset(0.88, 0.14), + children: [ + Container( + height: double.infinity, + width: double.infinity, + color: const Color.fromRGBO(0, 0, 0, 0.65), + padding: EdgeInsets.symmetric(horizontal: isBroadwise ? 50.w : 30.w, vertical: 112.h), + child: Wrap( + direction: Axis.horizontal, + //横向间距 + spacing: 28.w, + //竖向间距 + runSpacing: 40.h, + //横向对齐方式 + alignment: WrapAlignment.spaceBetween, + runAlignment: WrapAlignment.center, + children: [ + // 阅卷设置 + InkWell( + onTap: () => setingOperation(true), + child: SizedBox( + width: 60.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("assets/images/marking_settings.png", height: 32.w, width: 32.w, fit: BoxFit.cover), + Container(height: 4.h), + Text( + '偏好设置', + style: TextStyle( + fontSize: textSize, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + // 竖屏模式 + InkWell( + onTap: () { + ScreenDirection screenDirection = model.screenDirection; + model.screenDirection = screenDirection == ScreenDirection.HORIZONTAL_SCREEN + ? ScreenDirection.VERTICAL_SCREEN + : ScreenDirection.HORIZONTAL_SCREEN; + + if (model.keyboard == KeyboardType.INPUT_TYPE && + model.screenDirection == ScreenDirection.VERTICAL_SCREEN) { + // 如果当前是输入键盘并且是竖屏模式就把键盘改为底部键盘 + model.keyboard = KeyboardType.BOTTOM_SELECTION; + } + print(model.toJson()); + try { + ref + .read(markingKeyboardProvider.notifier) + .toggleKeyboard(DoMarkingKeyboardModel.fromJson(model.toJson())) + .then((value) => closeHandle()); + } catch (e) {} + }, + child: SizedBox( + width: 60.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("assets/images/vertical_screen_btn.png", + height: 32.w, width: 32.w, fit: BoxFit.cover), + Container(height: 4.h), + Text( + model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN ? '竖屏模式' : '横屏模式', + style: TextStyle( + fontSize: textSize, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + )), + ), + // 查看答案 + InkWell( + onTap: () => viewAnswer(), + child: SizedBox( + width: 60.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("assets/images/do_marking_answer.png", height: 32.w, width: 32.w, fit: BoxFit.cover), + Container(height: 4.h), + Text( + '查看答案', + style: TextStyle( + fontSize: textSize, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + // 隐藏试题ID + InkWell( + onTap: () { + bool isBroadwise = model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横屏 + if (!isBroadwise) { + // 竖屏无法展示 试题ID + // ignore: void_checks + return ToastUtils.showInfo('竖屏模式下,无法展示试题ID'); + } + model.hideQuestionId = !model.hideQuestionId; + setTimeOut( + 0, + () => { + ToastUtils.showSuccess(!model.hideQuestionId ? '隐藏试题ID' : '展示试题ID', + duration: const Duration(milliseconds: 500)) + }); + ref.read(markingKeyboardProvider.notifier).toggleKeyboard(model); + closeHandle(); + }, + child: SizedBox( + width: 60.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("assets/images/hide_question_id.png", height: 32.w, width: 32.w, fit: BoxFit.cover), + Container(height: 4.h), + Text( + '${model.hideQuestionId ? '隐藏' : '显示'}试卷编号', + style: TextStyle( + fontSize: isBroadwise ? 14.sp : 10.sp, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + // 查看整卷 + InkWell( + onTap: () => viewWholeTestPaper(context), + child: SizedBox( + width: 60.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("assets/images/do_marking_test_paper.png", + height: 32.w, width: 32.w, fit: BoxFit.cover), + Container(height: 4.h), + Text( + '查看整卷', + style: TextStyle( + fontSize: textSize, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + + // 退出阅卷 + InkWell( + onTap: () { + WidgetsFlutterBinding.ensureInitialized(); //不加这个强制横/竖屏会报错 + SystemChrome.setPreferredOrientations([ + // 强制竖屏 + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown + ]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]); + + exitPaper(); + RouterManager.router.pop(context); + }, + child: SizedBox( + width: 60.w, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset("assets/images/do_exit_marking.png", height: 32.w, width: 32.w, fit: BoxFit.cover), + Container(height: 4.h), + Text( + '退出阅卷', + style: TextStyle( + fontSize: textSize, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + ], + ), + ), + InkWell( + onTap: () => closeHandle(), + child: Icon( + Icons.clear_sharp, + color: const Color.fromARGB(255, 229, 233, 238), + size: 40.sp, + ), + ) + ], + ); + } +} diff --git a/marking_app/lib/components/marking/rating_progress_view.dart b/marking_app/lib/components/marking/rating_progress_view.dart new file mode 100644 index 0000000..217cc35 --- /dev/null +++ b/marking_app/lib/components/marking/rating_progress_view.dart @@ -0,0 +1,94 @@ +// 右侧抽屉(评阅记录) +import 'package:collection/collection.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/rating_progress_model.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +// part 'rating_progress_view.g.dart'; + +class RatingProgressView extends StatelessWidget with CommonMixin { + final int markingUserId; + final ScreenDirection direction; + const RatingProgressView({required this.markingUserId, required this.direction, super.key}); + + Future?> _getData() async { + RestClient _client = await getClient(); + BaseStructureResult> result = await _client.getMarkingRatingInfo(markingUserId); + if (result.success) { + return result.data; + } + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + bottom: false, + child: Drawer( + width: MediaQuery.of(context).size.width * (ScreenDirection.HORIZONTAL_SCREEN != direction ? 0.7 : 0.35), + child: MyFutureBuilder.buildFutureBuilderOfSingleInstance>( + context, + _getData(), + (val) { + if (val == null) { + return Container( + alignment: Alignment.center, + child: quickText('获取数据失败,请重试'), + ); + } + return Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 4.w), + padding: EdgeInsets.only(left: 6.w), + alignment: Alignment.centerLeft, + child: quickText('评分进度数据每分钟更新一次', size: 14.sp, fontWeight: FontWeight.bold), + ), + Expanded( + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: DataTable( + horizontalMargin: 0, + columnSpacing: 0, + columns: [ + DataColumn(label: Container(child: Text('题号'))), + DataColumn(label: Container(child: Text('整体进度'))), + DataColumn(label: Container(child: Text('整体均分'))), + DataColumn(label: Container(child: Text('我的均分'))), + ], + rows: val + .map( + (e) => DataRow( + cells: [ + DataCell(quickText(e.questionNum + '题')), + DataCell(quickText(getDoubleRemoveZero(e.schedule) + '%')), + DataCell(quickText(getDoubleRemoveZero(e.avgScore) + '分')), + DataCell(quickText(getDoubleRemoveZero(e.myAvgScore) + '分')) + ], + ), + ) + .toList(), + ), + ), + ) + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/marking_app/lib/components/marking/review_records_view.dart b/marking_app/lib/components/marking/review_records_view.dart new file mode 100644 index 0000000..5898c54 --- /dev/null +++ b/marking_app/lib/components/marking/review_records_view.dart @@ -0,0 +1,219 @@ +// 右侧抽屉(评阅记录) +import 'package:collection/collection.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; +import 'package:marking_app/common/model/marking/review_records_item.dart'; +import 'package:marking_app/common/model/marking/review_records_params.dart'; +import 'package:marking_app/components/marking/rating_progress_view.dart'; +import 'package:marking_app/pages/marking/provider/rating_progress_provider.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +part 'review_records_view.g.dart'; + +class ReviewRecords extends StatefulHookConsumerWidget { + final int markingUserId; + final String? questionNum; + final List tabs; + final ScreenDirection direction; + final String tabName; + final Function(int detailId, String questionNum) call; + const ReviewRecords( + {required this.markingUserId, + required this.tabName, + required this.tabs, + this.questionNum, + required this.direction, + required this.call, + super.key}); + + @override + _ReviewRecordsState createState() => _ReviewRecordsState(); +} + +class _ReviewRecordsState extends ConsumerState + with CommonMixin, RefreshDataHandle { + late final EasyRefreshController _refreshController; + late final List _currentTabs; + late final ReviewRecordsParams _params; + List _records = []; + + @override + void initState() { + super.initState(); + _refreshController = EasyRefreshController(); + _currentTabs = widget.tabs.map((e) => e.questionNum + '题').toList(); + _params = new ReviewRecordsParams( + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + questionNum: widget.questionNum ?? '', + markingUserId: widget.markingUserId, + ); + } + + @override + void dispose() { + super.dispose(); + _refreshController?.dispose(); + } + + /* 发起请求 */ + Future> getData(EasyRefreshController controller, ReviewRecordsParams params, + {bool isReFresh = false}) async { + print('进来....................'); + if (!isReFresh) { + params.page++; + } else { + params.page = 1; + } + RestClient client = await getClient(); + BasePageData? results = await toRefreshDataWithPath( + controller, + api: client.getMarkingRecords, + params: params, + isReFresh: isReFresh, + context: context, + pahtVal: widget.markingUserId.toString(), + ); + List theRecordsData = results?.items ?? []; + isReFresh ? setState(() => _records = theRecordsData) : setState(() => _records.addAll(theRecordsData)); + return _records; + } + + @override + Widget build(BuildContext context) { + EndDrawerViewEnum theVal = ref.watch(ratingProgressProvider); + if (theVal == EndDrawerViewEnum.RATING_PROGRESS) + return RatingProgressView( + direction: widget.direction, + markingUserId: widget.markingUserId, + ); + + if (widget.questionNum == null) return Container(); + + return SafeArea( + top: false, + bottom: false, + child: Drawer( + width: MediaQuery.of(context).size.width * (ScreenDirection.HORIZONTAL_SCREEN != widget.direction ? 0.7 : 0.35), + child: Column( + children: [ + Container( + padding: EdgeInsets.only(left: 4.w, right: 4.w, top: 10.h), + child: Row( + children: [ + quickText('题型:', size: 20.sp), + Expanded( + child: DropdownButtonFormField2( + value: _params.questionNum + '题', + isExpanded: true, + decoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(vertical: 12.h), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.sp)), + ), + hint: const Text( + '选择试题题型', + style: TextStyle(fontSize: 14), + ), + items: _currentTabs + .map((item) => DropdownMenuItem( + value: item, child: Text(item, style: TextStyle(fontSize: 16.sp)))) + .toList(), + onChanged: (value) { + if (value != null) { + var _tab = widget.tabs.firstWhereOrNull((element) => value.contains(element.questionNum)); + if (_tab != null) { + _params.questionNum = _tab.questionNum; + _refreshController.callRefresh(); + } + } + }, + buttonStyleData: const ButtonStyleData( + padding: EdgeInsets.only(right: 8), + ), + iconStyleData: const IconStyleData( + icon: Icon( + Icons.arrow_drop_down, + color: Colors.black45, + ), + iconSize: 24, + ), + dropdownStyleData: DropdownStyleData( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)), + ), + menuItemStyleData: MenuItemStyleData(padding: EdgeInsets.symmetric(horizontal: 14.h)), + ), + ) + ], + ), + // quickText('题型:' + widget.tabName), + ), + Expanded( + child: EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: _records.isEmpty ? MyEmptyWidget(imgHeight: 50.w, imgWidth: 50.w) : null, + controller: _refreshController, + header: MaterialHeader(), + footer: TaurusFooter(), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: DataTable( + horizontalMargin: 10.w, + columnSpacing: 0.w, + columns: [ + DataColumn(label: Container(child: Text('编号'), width: 30.w)), + DataColumn(label: Container(child: Text('得分'), width: 30.w)), + DataColumn(label: Container(child: Text('批阅时间'), width: 80.w)), + ], + rows: _records + .map((e) => DataRow( + cells: [ + DataCell(quickText(e.sort), + onTap: () => widget.call(e.markingUserDetailId, _params.questionNum)), + DataCell(quickText(e.score), + onTap: () => widget.call(e.markingUserDetailId, _params.questionNum)), + DataCell( + quickText(e.finishTime.length > 13 ? e.finishTime.substring(0, 16) : e.finishTime), + onTap: () => widget.call(e.markingUserDetailId, _params.questionNum)) + ], + )) + .toList(), + ), + ), + onRefresh: () async => getData(_refreshController, _params, isReFresh: true), + onLoad: () async => getData(_refreshController, _params), + ), + ), + ], + ), + ), + ); + } +} + +@swidget +Widget $reviewRecordsItem({required ReviewRecordsItem item, required Function(ReviewRecordsItem) call}) { + return Container( + child: Row( + children: [ + quickText(item.sort), + quickText(item.score), + quickText(item.finishTime), + ], + ), + ); +} diff --git a/marking_app/lib/components/marking/selectable_keyboard.dart b/marking_app/lib/components/marking/selectable_keyboard.dart new file mode 100644 index 0000000..f4048d1 --- /dev/null +++ b/marking_app/lib/components/marking/selectable_keyboard.dart @@ -0,0 +1,965 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/common/model/marking/keyboard_assist_event.dart'; +import 'package:marking_app/common/model/marking/marking_common_score_items.dart'; +import 'package:marking_app/common/model/marking/marking_keyboard_sliding_position.dart'; +import 'package:marking_app/common/model/marking/marking_text_question.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/marking_utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; + +// 选择型键盘 + +class SelectableKeyboard extends StatefulHookConsumerWidget { + int markingUserId; + MarkingTextQuestion data; + String questScore; // 当前分值 + double totalScore; // 总分 + bool hasSubtopic = false; // 是否有小题 + int subtopicIndex; // 小题下标 + SynchroScoreCallback synchroScore; + GestureTapCallback submitCall; + SelectableKeyboard({ + required this.data, + required this.markingUserId, + required this.questScore, + required this.totalScore, + required this.synchroScore, + required this.subtopicIndex, + required this.submitCall, + Key? key, + }) : hasSubtopic = data.subQuestionDetailList.isNotEmpty, + super(key: key); + + @override + _SelectableKeyboardState createState() => _SelectableKeyboardState(); +} + +class _SelectableKeyboardState extends ConsumerState + with SingleTickerProviderStateMixin, EventBusMixin { + late ScrollController _scrollController; + late AnimationController _animationController; + + bool isRight = true; // 是否是右侧打分键盘 + String? _smallScore; // 小题打分 + + late List nums; // 打开辅助键盘的个位数集合 + late List tens = []; // 十位数集合 + List subNums = []; // 关闭辅助键盘后的小键盘 + late RemoveListener _markingKeyboardListener; + late RemoveListener _markingSubtopicSwitchingListener; + late bool _openAuxiliary; + num? _tensVal; // 当前选中十位分值 + final Duration easyThrottleTime = Duration(milliseconds: 800); + + @override + void initState() { + super.initState(); + // 初始化不执行 + + _scrollController = ScrollController()..addListener(scrollListener); + + /// 小题切换监听,重置键盘 + _markingSubtopicSwitchingListener = ref.read(markingSubtopicSwitchingProvider.notifier).addListener((state) { + if (widget.subtopicIndex != state) widget.subtopicIndex = state; + + DoMarkingKeyboardModel preference = ref.read(markingKeyboardProvider); + initKeyboardSuper( + preference.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval), + preference.sort!); + toUp(); + }, fireImmediately: false); + + _markingKeyboardListener = ref.read(markingKeyboardProvider.notifier).addListener((state) { + isRight = state.keyboard == KeyboardType.RIGHT_SELECTION; + if (state.sort == null) return; + initKeyboardSuper( + state.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval), + state.sort!); + toUp(); + }); + _openAuxiliary = ref.read(markingKeyboardProvider).openAuxiliary; + _animationController = AnimationController( + value: _openAuxiliary ? 144 : 72, // 设置默认值 + duration: const Duration(milliseconds: 400), + lowerBound: 72, + upperBound: 144, + vsync: this, + )..addListener(toUp); + + // 事件总线监听 + eventOn(callback: (KeyboardAssistEvent item) { + bool openAuxiliary = item.openAuxiliary; + if (!openAuxiliary && _tensVal != null) { + // 关闭辅助键盘并且清空当前打分的分值 + marking(fraction: _tensVal!, isTens: true); + } + openAuxiliary ? _animationController.forward() : _animationController.reverse(); + ref.read(markingKeyboardProvider.notifier).changeOpenAuxiliary(openAuxiliary).then((value) { + if (value != null) _openAuxiliary = value; + }); + }); + } + + void toUp() { + toUpState(setState, () {}, mounted); + } + + /** 键盘滚动位置监听 */ + void scrollListener() { + // 保存当前滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" data + FastData.getInstance().setMarkingKeyboardSlidingPosition(MarkingKeyboardSlidingPosition( + id: widget.markingUserId, + questionNum: widget.data.questionNum, + position: _scrollController.position.pixels, + )); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 获取历史滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" + FastData.getInstance().getMarkingKeyboardSlidingPosition().then((historyPosition) { + // 检查是否有历史滑动位置并且ListView已经完成渲染 + bool flag = historyPosition != null && + !historyPosition.isVertical && + historyPosition.position >= 0 && + _scrollController.hasClients && + historyPosition.questionNum == widget.data.questionNum && + historyPosition.id == widget.markingUserId; + if (flag) { + // 使用JumpTo或AnimateTo方法滚动到历史位置 + _scrollController.jumpTo(historyPosition.position); + // _scrollController.animateTo( + // historyPosition.position, + // duration: Duration(milliseconds: 350), + // curve: Curves.easeInOut, + // ); + } + }); + } + + @override + void dispose() { + super.dispose(); + + _scrollController + ..removeListener(scrollListener) + ..dispose(); + _markingKeyboardListener(); + _markingSubtopicSwitchingListener(); + _animationController + ..removeListener(toUp) + ..dispose(); + eventCancel(); + } + + /// 键盘计算数据 + /// @params localScoreInterval 本地设置分值步长 + /// @params sort 键盘排序方式 + void initKeyboardSuper(double? localScoreInterval, SortKeyboard sort) { + double scoreInterval = widget.data.scoreInterval; // 服务器针对当前实体设置的分值步长 + if (localScoreInterval != null && localScoreInterval != 0 && localScoreInterval != scoreInterval) { + scoreInterval = localScoreInterval; + } + double totalScore = widget.hasSubtopic + ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + : widget.totalScore; + + if (scoreInterval == 0) scoreInterval = 1; + initKeyboard(totalScore, scoreInterval, sort); + } + + /// 初始化键盘 + /// @params {double} totalScore 总分 + /// @params {double} scoreInterval 小分步长 + void initKeyboard(double totalScore, double scoreInterval, SortKeyboard sort) { + tens.clear(); + int floorScore = totalScore.floor(); // 总分向下取整 + int singleDigit = floorScore; // 个位小数 + int numsLength = singleDigit; + if (floorScore > 10) { + singleDigit = 9; + for (var i = 10; i < floorScore; i += 10) { + tens.add(i); + } + } + numsLength = ((numsLength - (numsLength % scoreInterval)) / scoreInterval).floor(); + + /// +1 添加满分 + subNums = List.generate(numsLength + 1, (e) => e * scoreInterval); // 小分键盘 + + // 加一个总分 + if (numsLength * scoreInterval < totalScore) subNums.add(totalScore); + nums = jsonDecode(jsonEncode(subNums)); // 辅助键盘的小分键盘 + if (sort != SortKeyboard.FULL_AND_AERO_TOP) { + if (totalScore > 19) { + nums = nums.reversed.toList(); + } else { + nums = nums.reversed.where((e) => e <= 10).toList(); + } + subNums = subNums.reversed.toList(); + } + + // // 计算关闭辅助键盘后的小键盘 + // if (floorScore > 10) { + // int a = scoreInterval == 0.5 ? floorScore * 2 : floorScore; + // subNums = List.generate(a+1, (e) => e * scoreInterval); // 小分键盘 + // } + } + + /// 打分 + /// @params {num} fraction 分数 + /// @params {bool} isTens 十位分数 + /// @params {bool} fullScore 满分 + Future marking({required num fraction, String? strScore, bool isTens = false, bool fullScore = false}) async { + // 零分的处理 + bool allWrong = fraction == 0 && fullScore; + if (allWrong) { + fullScore = false; + } + + // 十位 + if (!fullScore && isTens) { + bool cleanScore = _tensVal == fraction; + if (!cleanScore) { + _tensVal = fraction; + } else { + _tensVal = null; + toUpState(setState, () => _smallScore = null, mounted); + } + return await widget.synchroScore( + score: fraction.toDouble(), + continueScoring: true, + hasSubtopic: widget.hasSubtopic, + cleanScore: cleanScore, + ); + } + + toUpState(setState, () => _smallScore = strScore, mounted); + EasyLoading.show(status: 'loading...'); + try { + double theScore = fraction.toDouble(); + if (fullScore) { + // 满分 + theScore = widget.data.totalScore; + /** 小题单个分值 + theScore = widget.hasSubtopic + ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + : fraction.toDouble(); */ + } else if (_tensVal != null) { + // 有十位数 + theScore = (_tensVal! + fraction).toDouble(); + } + + if (theScore > widget.totalScore) { + return ToastUtils.showInfo('评分已大于总分,请重新评分'); + } + + await widget + .synchroScore( + score: theScore, + continueScoring: false, + allWrong: allWrong, + hasSubtopic: widget.hasSubtopic, + ) + .then((_) { + if (widget.hasSubtopic) { + // 是否有小题 + List subQuestionDetailList = widget.data.subQuestionDetailList; + + if (subQuestionDetailList.map((e) => e.completeRating).contains(false)) { + setTimeOut(100, () => toUpState(setState, () => _smallScore = null, mounted)); + return; + } + } + widget.submitCall(); // 提交 + }); + } catch (e) { + toPrint(val: e.toString()); + } finally { + EasyLoading.dismiss(); + } + } + + @override + Widget build(BuildContext context) { + DoMarkingKeyboardModel thePreference = ref.watch(markingKeyboardProvider); + bool henping = thePreference.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 屏幕方向 + MarkingCommonScoreItems? commonScores = thePreference.commonScores; + List theCommonScores = []; + if (commonScores != null && + commonScores.questionNum == widget.data.questionNum && + commonScores.id == widget.markingUserId) { + theCommonScores = commonScores.score; + } + SortKeyboard sort = thePreference.sort!; + + if (!isRight) { + return Container(); + } + // 当前试题有无小题,有小题就是当前小题的总分,无小题就是当前试题的总分 + double newTotalScore = widget.hasSubtopic + ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + : widget.totalScore; + + // 已经选中辅助键盘的分数,扣除十位分数剩余分数差值 + double dValue = _tensVal != null ? newTotalScore - _tensVal!.toDouble() : newTotalScore; // 差值 + + double animationVal = _animationController.value.h; + + List miniKeyboard = [...theCommonScores, ...(!_openAuxiliary && newTotalScore > 10 ? subNums : nums)]; + int theCommonScoresLength = theCommonScores.length; + // 右侧键盘 + return Container( + width: henping ? animationVal : animationVal - 16.h, + // width: animationVal, + margin: EdgeInsets.only(top: 0.6.w), + child: Row( + children: [ + // 辅助栏 + SizedBox( + width: animationVal - (henping ? 72.h : 72.h), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Ink( + color: const Color.fromRGBO(249, 250, 254, 1), + child: InkWell( + splashColor: Colors.cyanAccent, + onTap: () { + easyThrottle('toMarkingVal', () => marking(fraction: 0, fullScore: true, strScore: '0'), + duration: easyThrottleTime); + }, + child: Container( + width: double.infinity, + height: 56.h, + margin: EdgeInsets.only(top: 3.h), + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all( + width: 1.h, + color: Color.fromARGB(255, 189, 195, 216), + ), + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + child: quickText(widget.hasSubtopic ? '全错' : '零分', + size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + ), + ), + ), + GestureDetector( + onTap: () { + easyThrottle('toMarkingVal', () => marking(fraction: widget.totalScore, fullScore: true), + duration: easyThrottleTime); + }, + child: Container( + height: 58.h, + margin: EdgeInsets.only(top: 1.w), + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color.fromRGBO(4, 201, 208, 1), + borderRadius: BorderRadius.all(Radius.circular(1.w)), + ), + child: quickText( + widget.hasSubtopic ? '全对' : '满分', + size: 17.sp, + fontWeight: FontWeight.w400, + color: const Color.fromRGBO(255, 255, 255, 1), + ), + ), + ), + ...tens.map((e) { + return InkWell( + onTap: () { + easyThrottle('toMarkingVal', () => marking(fraction: e, isTens: true), duration: easyThrottleTime); + }, + child: Container( + height: 58.h, + margin: EdgeInsets.only(top: 1.w), + alignment: Alignment.center, + decoration: BoxDecoration( + color: _tensVal == e ? Colors.amber[800] : Theme.of(context).primaryColor, + borderRadius: BorderRadius.all(Radius.circular(1.w)), + ), + child: quickText( + '$e+', + size: 17.sp, + fontWeight: FontWeight.w500, + color: const Color.fromRGBO(255, 255, 255, 1), + ), + ), + ); + }).toList() + ]), + ), + // 小分栏 + Expanded( + child: Container( + width: henping ? 72.h : 60.h, + // width: 72.h, + padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 4.h), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(0, 8.w), //阴影y轴偏移量 + blurRadius: 1, //阴影模糊程度 + spreadRadius: 2, //阴影扩散程度 + ) + ], + color: const Color.fromRGBO(255, 255, 255, 1), + ), + child: ListView( + controller: _scrollController, + children: miniKeyboard.asMap().keys.map((i) { + bool isCommonScoring = theCommonScores.isNotEmpty && i <= theCommonScoresLength - 1; + + double e = miniKeyboard[i]; + String strScore = getDoubleRemoveZero(e); + bool isCurrentSelect = _smallScore == strScore; // 当前选中 + + if (e == 0 && SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER == sort) { + return Container(); + } + bool effective = e <= dValue; + return Ink( + child: InkWell( + splashColor: Colors.cyanAccent, + onTap: effective + ? () => easyThrottle('toMarkingVal', () => marking(fraction: e, strScore: strScore), + duration: easyThrottleTime) + : null, + child: Container( + width: double.infinity, + height: 56.h, + margin: EdgeInsets.only(top: 3.h), + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all( + width: 1.h, + color: const Color.fromRGBO(224, 230, 255, 1), + ), + color: isCurrentSelect + ? Theme.of(context).primaryColor + : effective + ? const Color.fromRGBO(249, 250, 254, 1) + : Colors.grey[300], + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + child: quickText( + strScore, + size: 15.sp, + color: isCurrentSelect ? Colors.white : const Color.fromRGBO(148, 163, 182, 1), + ), + ), + ), + ); + }).toList(), + ), + ), + ), + ], + ), + ); + } +} + + + + + + + +// class SelectableKeyboard extends StatefulHookConsumerWidget { +// int markingUserId; +// MarkingTextQuestion data; +// String questScore; // 当前分值 +// double totalScore; // 总分 +// bool hasSubtopic = false; // 是否有小题 +// int subtopicIndex; // 小题下标 +// SynchroScoreCallback synchroScore; +// GestureTapCallback submitCall; +// SelectableKeyboard({ +// required this.data, +// required this.markingUserId, +// required this.questScore, +// required this.totalScore, +// required this.synchroScore, +// required this.subtopicIndex, +// required this.submitCall, +// Key? key, +// }) : hasSubtopic = data.subQuestionDetailList.isNotEmpty, +// super(key: key); + +// @override +// _SelectableKeyboardState createState() => _SelectableKeyboardState(); +// } + +// class _SelectableKeyboardState extends ConsumerState +// with SingleTickerProviderStateMixin, EventBusMixin { +// late ScrollController _scrollController; +// late AnimationController _animationController; + +// bool isRight = true; // 是否是右侧打分键盘 +// String? _smallScore; // 小题打分 + +// late List nums; // 打开辅助键盘的个位数集合 +// late List tens = []; // 十位数集合 +// List subNums = []; // 关闭辅助键盘后的小键盘 +// late RemoveListener _markingKeyboardListener; +// late RemoveListener _markingSubtopicSwitchingListener; +// late bool _openAuxiliary; +// num? _tensVal; // 当前选中十位分值 +// final Duration easyThrottleTime = Duration(seconds: 1); + +// @override +// void initState() { +// super.initState(); +// // 初始化不执行 + +// _scrollController = ScrollController()..addListener(scrollListener); + +// /// 小题切换监听,重置键盘 +// _markingSubtopicSwitchingListener = ref.read(markingSubtopicSwitchingProvider.notifier).addListener((state) { +// if (widget.subtopicIndex != state) widget.subtopicIndex = state; + +// DoMarkingKeyboardModel preference = ref.read(markingKeyboardProvider); +// initKeyboardSuper( +// preference.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval), +// preference.sort!); +// toUp(); +// }, fireImmediately: false); + +// _markingKeyboardListener = ref.read(markingKeyboardProvider.notifier).addListener((state) { +// isRight = state.keyboard == KeyboardType.RIGHT_SELECTION; +// if (state.sort == null) return; +// initKeyboardSuper( +// state.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval), +// state.sort!); +// toUp(); +// }); +// _openAuxiliary = ref.read(markingKeyboardProvider).openAuxiliary; +// _animationController = AnimationController( +// value: _openAuxiliary ? 144 : 72, // 设置默认值 +// duration: const Duration(milliseconds: 400), +// lowerBound: 72, +// upperBound: 144, +// vsync: this, +// )..addListener(toUp); + +// // 事件总线监听 +// eventOn(callback: (KeyboardAssistEvent item) { +// bool openAuxiliary = item.openAuxiliary; +// if (!openAuxiliary && _tensVal != null) { +// // 关闭辅助键盘并且清空当前打分的分值 +// marking(fraction: _tensVal!, isTens: true); +// } +// openAuxiliary ? _animationController.forward() : _animationController.reverse(); +// ref.read(markingKeyboardProvider.notifier).changeOpenAuxiliary(openAuxiliary).then((value) { +// if (value != null) _openAuxiliary = value; +// }); +// }); +// } + +// void toUp() { +// toUpState(setState, () {}, mounted); +// } + +// /** 键盘滚动位置监听 */ +// void scrollListener() { +// // 保存当前滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" data +// FastData.getInstance().setMarkingKeyboardSlidingPosition(MarkingKeyboardSlidingPosition( +// id: widget.markingUserId, +// questionNum: widget.data.questionNum, +// position: _scrollController.position.pixels, +// )); +// } + +// @override +// void didChangeDependencies() { +// super.didChangeDependencies(); +// // 获取历史滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" +// FastData.getInstance().getMarkingKeyboardSlidingPosition().then((historyPosition) { +// print('键盘滑动位置:${historyPosition?.toJson()}'); +// // 检查是否有历史滑动位置并且ListView已经完成渲染 +// bool flag = historyPosition != null && +// historyPosition.position >= 0 && +// _scrollController.hasClients && +// historyPosition.questionNum == widget.data.questionNum && +// historyPosition.id == widget.markingUserId; +// if (flag) { +// // 使用JumpTo或AnimateTo方法滚动到历史位置 +// _scrollController.jumpTo(historyPosition.position); +// // _scrollController.animateTo( +// // historyPosition.position, +// // duration: Duration(milliseconds: 350), +// // curve: Curves.easeInOut, +// // ); +// } +// }); +// } + +// @override +// void dispose() { +// super.dispose(); + +// _scrollController +// ..removeListener(scrollListener) +// ..dispose(); +// _markingKeyboardListener(); +// _markingSubtopicSwitchingListener(); +// _animationController +// ..removeListener(toUp) +// ..dispose(); +// eventCancel(); +// } + +// /// 键盘计算数据 +// /// @params localScoreInterval 本地设置分值步长 +// /// @params sort 键盘排序方式 +// void initKeyboardSuper(double? localScoreInterval, SortKeyboard sort) { +// double scoreInterval = widget.data.scoreInterval; // 服务器针对当前实体设置的分值步长 +// if (localScoreInterval != null && localScoreInterval != 0 && localScoreInterval != scoreInterval) { +// scoreInterval = localScoreInterval; +// } +// double totalScore = widget.hasSubtopic +// ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore +// : widget.totalScore; + +// if (scoreInterval == 0) scoreInterval = 1; +// initKeyboard(totalScore, scoreInterval, sort); +// } + +// /// 初始化键盘 +// /// @params {double} totalScore 总分 +// /// @params {double} scoreInterval 小分步长 +// void initKeyboard(double totalScore, double scoreInterval, SortKeyboard sort) { +// tens.clear(); +// int floorScore = totalScore.floor(); // 总分向下取整 +// int singleDigit = floorScore; // 个位小数 +// int numsLength = singleDigit; +// if (floorScore > 10) { +// singleDigit = 9; +// for (var i = 10; i < floorScore; i += 10) { +// tens.add(i); +// } +// } +// numsLength = ((numsLength - (numsLength % scoreInterval)) / scoreInterval).floor(); + +// /// +1 添加满分 +// subNums = List.generate(numsLength + 1, (e) => e * scoreInterval); // 小分键盘 + +// // 加一个总分 +// if (numsLength * scoreInterval < totalScore) subNums.add(totalScore); +// nums = jsonDecode(jsonEncode(subNums)); // 辅助键盘的小分键盘 +// if (sort != SortKeyboard.FULL_AND_AERO_TOP) { +// if (totalScore > 19) { +// nums = nums.reversed.toList(); +// } else { +// nums = nums.reversed.where((e) => e <= 10).toList(); +// } +// subNums = subNums.reversed.toList(); +// } + +// // // 计算关闭辅助键盘后的小键盘 +// // if (floorScore > 10) { +// // int a = scoreInterval == 0.5 ? floorScore * 2 : floorScore; +// // subNums = List.generate(a+1, (e) => e * scoreInterval); // 小分键盘 +// // } +// } + +// /// 打分 +// /// @params {num} fraction 分数 +// /// @params {bool} isTens 十位分数 +// /// @params {bool} fullScore 满分 +// Future marking({required num fraction, String? strScore, bool isTens = false, bool fullScore = false}) async { +// // 十位 +// if (!fullScore && isTens) { +// bool cleanScore = _tensVal == fraction; +// if (!cleanScore) { +// _tensVal = fraction; +// } else { +// _tensVal = null; +// toUpState(setState, () => _smallScore = null, mounted); +// } +// return await widget.synchroScore( +// score: fraction.toDouble(), +// continueScoring: true, +// hasSubtopic: widget.hasSubtopic, +// cleanScore: cleanScore, +// ); +// } + +// toUpState(setState, () => _smallScore = strScore, mounted); +// EasyLoading.show(status: 'loading...'); +// try { +// double theScore = fraction.toDouble(); +// if (fullScore) { +// // 满分 +// theScore = widget.hasSubtopic +// ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore +// : fraction.toDouble(); +// } else if (_tensVal != null) { +// // 有十位数 +// theScore = (_tensVal! + fraction).toDouble(); +// } + +// if (theScore > widget.totalScore) { +// return ToastUtils.showInfo('评分已大于总分,请重新评分'); +// } + +// await widget +// .synchroScore( +// score: theScore, +// continueScoring: false, +// hasSubtopic: widget.hasSubtopic, +// ) +// .then((_) { +// if (widget.hasSubtopic) { +// // 是否有小题 +// List subQuestionDetailList = widget.data.subQuestionDetailList; + +// if (subQuestionDetailList.map((e) => e.completeRating).contains(false)) { +// setTimeOut(100, () => toUpState(setState, () => _smallScore = null, mounted)); +// return; +// } +// } +// widget.submitCall(); // 提交 +// }); +// } catch (e) { +// toPrint(val: e.toString()); +// } finally { +// EasyLoading.dismiss(); +// } +// } + +// @override +// Widget build(BuildContext context) { +// DoMarkingKeyboardModel thePreference = ref.watch(markingKeyboardProvider); +// bool henping = thePreference.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 屏幕方向 +// SortKeyboard sort = thePreference.sort!; + +// if (!isRight) { +// return Container(); +// } +// // 当前试题有无小题,有小题就是当前小题的总分,无小题就是当前试题的总分 +// double newTotalScore = widget.hasSubtopic +// ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore +// : widget.totalScore; + +// // 已经选中辅助键盘的分数,扣除十位分数剩余分数差值 +// double dValue = _tensVal != null ? newTotalScore - _tensVal!.toDouble() : newTotalScore; // 差值 + +// double animationVal = _animationController.value.h; +// // 右侧键盘 +// return Container( +// width: henping ? animationVal : animationVal - 16.h, +// // width: animationVal, +// margin: EdgeInsets.only(top: 0.6.w), +// child: Row( +// children: [ +// // 辅助栏 +// SizedBox( +// width: animationVal - (henping ? 72.h : 72.h), +// child: Column(mainAxisSize: MainAxisSize.min, children: [ +// GestureDetector( +// onTap: () { +// easyThrottle('toMarkingVal', () => marking(fraction: widget.totalScore, fullScore: true), +// duration: easyThrottleTime); +// }, +// child: Container( +// height: 58.h, +// alignment: Alignment.center, +// decoration: BoxDecoration( +// color: const Color.fromRGBO(4, 201, 208, 1), +// borderRadius: BorderRadius.all(Radius.circular(1.w)), +// ), +// child: quickText( +// '满分', +// size: 17.sp, +// fontWeight: FontWeight.w400, +// color: const Color.fromRGBO(255, 255, 255, 1), +// ), +// ), +// ), +// ...tens.map((e) { +// return InkWell( +// onTap: () { +// easyThrottle('toMarkingVal', () => marking(fraction: e, isTens: true), duration: easyThrottleTime); +// }, +// child: Container( +// height: 58.h, +// margin: EdgeInsets.only(top: 1.w), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// color: _tensVal == e ? Colors.amber[800] : Theme.of(context).primaryColor, +// borderRadius: BorderRadius.all(Radius.circular(1.w)), +// ), +// child: quickText( +// '$e+', +// size: 17.sp, +// fontWeight: FontWeight.w500, +// color: const Color.fromRGBO(255, 255, 255, 1), +// ), +// ), +// ); +// }).toList() +// ]), +// ), +// // 小分栏 +// Expanded( +// child: Container( +// width: henping ? 72.h : 60.h, +// // width: 72.h, +// padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 4.h), +// decoration: BoxDecoration( +// boxShadow: [ +// BoxShadow( +// color: const Color.fromRGBO(46, 91, 255, 0.2), +// offset: Offset(0, 8.w), //阴影y轴偏移量 +// blurRadius: 1, //阴影模糊程度 +// spreadRadius: 2, //阴影扩散程度 +// ) +// ], +// color: const Color.fromRGBO(255, 255, 255, 1), +// ), +// child: ListView( +// controller: _scrollController, +// children: [ +// if ([SortKeyboard.FULL_AND_AERO_TOP, SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER].contains(sort)) +// Ink( +// color: const Color.fromRGBO(249, 250, 254, 1), +// child: InkWell( +// splashColor: Colors.cyanAccent, +// onTap: () { +// easyThrottle( +// 'toMarkingVal', +// () => marking( +// fraction: widget.totalScore, fullScore: true, strScore: widget.totalScore.toString()), +// duration: easyThrottleTime); +// }, +// child: Container( +// width: double.infinity, +// height: 56.h, +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ), +// if (SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER == sort) +// Ink( +// color: const Color.fromRGBO(249, 250, 254, 1), +// child: InkWell( +// splashColor: Colors.cyanAccent, +// onTap: () { +// easyThrottle('toMarkingVal', () => marking(fraction: 0, fullScore: true, strScore: '0'), +// duration: easyThrottleTime); +// }, +// child: Container( +// width: double.infinity, +// height: 56.h, +// margin: EdgeInsets.only(top: 3.h), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText('零分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ), +// ...(!_openAuxiliary && newTotalScore > 10 ? subNums : nums).map((e) { +// String strScore = getDoubleRemoveZero(e); +// bool isCurrentSelect = _smallScore == strScore; // 当前选中 + +// if (e == 0 && SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER == sort) { +// return Container(); +// } +// bool effective = e <= dValue; +// return Ink( +// child: InkWell( +// splashColor: Colors.cyanAccent, +// onTap: effective +// ? () => easyThrottle('toMarkingVal', () => marking(fraction: e, strScore: strScore), +// duration: easyThrottleTime) +// : null, +// child: Container( +// width: double.infinity, +// height: 56.h, +// margin: EdgeInsets.only(top: 3.h), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// color: isCurrentSelect +// ? Theme.of(context).primaryColor +// : effective +// ? const Color.fromRGBO(249, 250, 254, 1) +// : Colors.grey[300], +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText( +// strScore == '0' ? '零分' : strScore, +// size: 15.sp, +// color: isCurrentSelect ? Colors.white : const Color.fromRGBO(148, 163, 182, 1), +// ), +// ), +// ), +// ); +// }).toList(), +// if (sort == SortKeyboard.INVERTED_ORDER) +// Ink( +// color: const Color.fromRGBO(249, 250, 254, 1), +// child: InkWell( +// splashColor: Colors.cyanAccent, +// onTap: () { +// easyThrottle( +// 'toMarkingVal', +// () => marking( +// fraction: widget.totalScore, fullScore: true, strScore: widget.totalScore.toString()), +// duration: easyThrottleTime); +// }, +// child: Container( +// width: double.infinity, +// height: 56.h, +// margin: EdgeInsets.only(top: 3.h), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ), +// ], +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } diff --git a/marking_app/lib/components/marking/selectable_keyboard_bottom.dart b/marking_app/lib/components/marking/selectable_keyboard_bottom.dart new file mode 100644 index 0000000..b513a4b --- /dev/null +++ b/marking_app/lib/components/marking/selectable_keyboard_bottom.dart @@ -0,0 +1,423 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/common/model/marking/keyboard_assist_event.dart'; +import 'package:marking_app/common/model/marking/marking_common_score_items.dart'; +import 'package:marking_app/common/model/marking/marking_keyboard_sliding_position.dart'; +import 'package:marking_app/common/model/marking/marking_text_question.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/marking_utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; + +// 选择型键盘 +class SelectableKeyboardBottom extends StatefulHookConsumerWidget { + MarkingTextQuestion data; + int markingUserId; + String questScore; // 当前分值 + double totalScore; // 总分 + bool hasSubtopic = false; // 是否有小题 + int subtopicIndex; // 小题下标 + SynchroScoreCallback synchroScore; + GestureTapCallback submitCall; + SelectableKeyboardBottom({ + required this.data, + required this.markingUserId, + required this.questScore, + required this.totalScore, + required this.synchroScore, + required this.subtopicIndex, + required this.submitCall, + Key? key, + }) : hasSubtopic = data.subQuestionDetailList.isNotEmpty, + super(key: key); + + @override + _SelectableKeyboardState createState() => _SelectableKeyboardState(); +} + +class _SelectableKeyboardState extends ConsumerState + with SingleTickerProviderStateMixin, EventBusMixin { + late AnimationController _animationController; + late ScrollController _scrollController; + + bool isRight = true; // 是否是右侧打分键盘 + String? _smallScore; // 小分栏打分 + + late List nums; // 个位数集合 + late List tens = []; // 十位数集合 + late RemoveListener _markingKeyboardListener; + late RemoveListener _markingSubtopicSwitchingListener; + num? _tensVal; // 当前选中十位分值 + final Duration easyThrottleTime = Duration(seconds: 1); + + @override + void initState() { + super.initState(); + // 初始化不执行 + _scrollController = ScrollController()..addListener(scrollListener); + _markingSubtopicSwitchingListener = ref.read(markingSubtopicSwitchingProvider.notifier).addListener((state) { + if (widget.subtopicIndex != state) widget.subtopicIndex = state; + DoMarkingKeyboardModel preference = ref.read(markingKeyboardProvider); + initKeyboardSuper( + preference.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval), + preference.sort!); + toUp(); + }, fireImmediately: false); + _markingKeyboardListener = ref.read(markingKeyboardProvider.notifier).addListener((state) { + isRight = state.keyboard == KeyboardType.RIGHT_SELECTION; + initKeyboardSuper( + state.getScoreStepSize(widget.markingUserId, widget.data.questionNum, widget.data.scoreInterval), + state.sort!); + toUp(); + }); + _animationController = AnimationController( + duration: const Duration(milliseconds: 400), + lowerBound: 72, + upperBound: 144, + vsync: this, + )..addListener(toUp); + + // 事件总线监听 + // eventOn(callback: (KeyboardAssistEvent item) { + // bool openAuxiliary = item.openAuxiliary; + // openAuxiliary ? _animationController.forward() : _animationController.reverse(); + + // ref.read(markingKeyboardProvider.notifier).changeOpenAuxiliary(openAuxiliary); + // }); + } + + void toUp() { + toUpState(setState, () {}, mounted); + } + + /** 键盘滚动位置监听 */ + void scrollListener() { + // 保存当前滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" data + FastData.getInstance().setMarkingKeyboardSlidingPosition(MarkingKeyboardSlidingPosition( + id: widget.markingUserId, + questionNum: widget.data.questionNum, + position: _scrollController.position.pixels, + isVertical: true, + )); + } + + @override + void dispose() { + super.dispose(); + _markingKeyboardListener(); + _markingSubtopicSwitchingListener(); + _animationController + ..removeListener(toUp) + ..dispose(); + eventCancel(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 获取历史滑动位置,这里假设使用SharedPreferences存储位置为"scroll_position" + FastData.getInstance().getMarkingKeyboardSlidingPosition().then((historyPosition) { + print('键盘滑动位置:${historyPosition?.toJson()}'); + // 检查是否有历史滑动位置并且ListView已经完成渲染 + bool flag = historyPosition != null && + historyPosition.isVertical && + historyPosition.position >= 0 && + _scrollController.hasClients && + historyPosition.questionNum == widget.data.questionNum && + historyPosition.id == widget.markingUserId; + if (flag) { + // 使用JumpTo或AnimateTo方法滚动到历史位置 + _scrollController.jumpTo(historyPosition.position); + // _scrollController.animateTo( + // historyPosition.position, + // duration: Duration(milliseconds: 350), + // curve: Curves.easeInOut, + // ); + } + }); + } + + /// 键盘计算数据 + /// @params localScoreInterval 本地设置分值步长 + /// @params sort 键盘排序方式 + void initKeyboardSuper(double? localScoreInterval, SortKeyboard sort) { + double scoreInterval = widget.data.scoreInterval; // 服务器针对当前实体设置的分值步长 + if (localScoreInterval != null && localScoreInterval != 0 && localScoreInterval != scoreInterval) { + scoreInterval = localScoreInterval; + } + print(widget.data.subQuestionDetailList.map((e) => e.toJson())); + print(widget.subtopicIndex); + double totalScore = widget.hasSubtopic + ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + : widget.totalScore; + initKeyboard(totalScore, scoreInterval, sort); + } + + /// 初始化键盘 + /// @params {double} totalScore 总分 + /// @params {double} scoreInterval 小分步长 + void initKeyboard(double totalScore, double scoreInterval, SortKeyboard sort) { + // tens.clear(); + // int floorScore = totalScore.floor(); // 总分向下取整 + // int singleDigit = floorScore; // 个位小数 + // int numsLength = singleDigit; + // if (floorScore > 10) { + // singleDigit = floorScore; + // for (var i = 10; i < floorScore; i += 10) { + // tens.add(i); + // } + + // numsLength = singleDigit; + // if (scoreInterval == 0.5) { + // numsLength = singleDigit * 2; + // } + // } else { + // if (scoreInterval == 0.5) { + // numsLength = (singleDigit) * 2; + // } + // } + // nums = List.generate(numsLength, (e) => e * scoreInterval); // 小分键盘 + // if (sort != SortKeyboard.FULL_AND_AERO_TOP) { + // nums = nums.reversed.toList(); + // } + tens.clear(); + int floorScore = totalScore.floor(); // 总分向下取整 + int singleDigit = floorScore; // 个位小数 + int numsLength = singleDigit; + if (floorScore > 10) { + singleDigit = 9; + for (var i = 10; i < floorScore; i += 10) { + tens.add(i); + } + } + numsLength = ((numsLength - (numsLength % scoreInterval)) / scoreInterval).floor(); + + /// +1 添加满分 + nums = List.generate(numsLength + 1, (e) => e * scoreInterval); // 小分键盘 + // 加一个总分 + if (numsLength * scoreInterval < totalScore) nums.add(totalScore); + if (sort != SortKeyboard.FULL_AND_AERO_TOP) { + nums = nums.reversed.toList(); + } + } + + /// 打分 + /// @params {num} fraction 分数 + /// @params {bool} isTens 十位分数 + /// @params {bool} fullScore 满分 + Future marking({required num fraction, String? strScore, bool isTens = false, bool fullScore = false}) async { + // 零分的处理 + bool allWrong = fraction == 0 && fullScore; + if (allWrong) { + fullScore = false; + } + // 十位 + if (!fullScore && isTens) { + _tensVal = fraction; + + return await widget.synchroScore( + score: fraction.toDouble(), + continueScoring: true, + hasSubtopic: widget.hasSubtopic, + ); + } + + EasyLoading.show(status: 'loading...'); + try { + double theScore = fraction.toDouble(); + if (fullScore) { + // 满分 + // theScore = widget.hasSubtopic + // ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + // : fraction.toDouble(); + theScore = widget.data.totalScore; + } else if (_tensVal != null) { + // 有十位数 + theScore = (_tensVal! + fraction).toDouble(); + } + + if (theScore > widget.totalScore) { + return ToastUtils.showInfo('评分已大于总分,请重新评分'); + } + + await widget + .synchroScore( + score: theScore, + continueScoring: false, + allWrong: allWrong, + hasSubtopic: widget.hasSubtopic, + ) + .then((_) { + if (widget.hasSubtopic) { + // 是否有小题 + List subQuestionDetailList = widget.data.subQuestionDetailList; + if (subQuestionDetailList.map((e) => e.completeRating).contains(false)) { + return; + } + } + widget.submitCall(); // 提交 + }); + } catch (e) { + toPrint(val: e.toString()); + } finally { + EasyLoading.dismiss(); + } + } + + @override + Widget build(BuildContext context) { + DoMarkingKeyboardModel model = ref.watch(markingKeyboardProvider); + SortKeyboard sort = model.sort!; + bool isBroadwise = model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否横向 + MarkingCommonScoreItems? commonScores = model.commonScores; + List theCommonScores = []; + if (commonScores != null && + commonScores.questionNum == widget.data.questionNum && + commonScores.id == widget.markingUserId) { + theCommonScores = commonScores.score; + } + + double totalScore = widget.hasSubtopic + ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + : widget.totalScore; + + return Container( + height: 62.h, + width: double.infinity, + padding: EdgeInsets.only(left: 4.h, right: 4.h), + decoration: BoxDecoration( + // boxShadow: [ + // BoxShadow( + // color: const Color.fromRGBO(46, 91, 255, 0.2), + // offset: Offset(26.h, 0), //阴影y轴偏移量 + // blurRadius: 1, //阴影模糊程度 + // spreadRadius: 0.4, //阴影扩散程度 + // ) + // ], + // color: Colors.white, + ), + child: Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 4.h), + color: Colors.transparent, + child: ListView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + children: [ + InkWell( + onTap: () { + easyThrottle( + 'toMarkingVal', + () => marking( + fraction: widget.totalScore, fullScore: true, strScore: widget.totalScore.toString()), + duration: easyThrottleTime); + }, + child: Container( + width: isBroadwise ? 28.w : 52.w, + height: double.infinity, + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all( + width: 1.h, + color: const Color.fromRGBO(224, 230, 255, 1), + ), + color: const Color.fromRGBO(249, 250, 254, 1), + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + child: quickText(widget.hasSubtopic ? '全对' : '满分', + size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + ), + ), + InkWell( + onTap: () { + easyThrottle('toMarkingVal', () => marking(fraction: 0, fullScore: true, strScore: '0'), + duration: easyThrottleTime); + }, + child: Container( + width: isBroadwise ? 28.w : 52.w, + height: double.infinity, + alignment: Alignment.center, + margin: EdgeInsets.only(left: 3.h), + decoration: BoxDecoration( + border: Border.all(width: 1.h, color: const Color.fromRGBO(224, 230, 255, 1)), + color: const Color.fromRGBO(249, 250, 254, 1), + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + child: quickText(widget.hasSubtopic ? '全错' : '零分', + size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + ), + ), + ...[...theCommonScores, ...nums].map((e) { + String strScore = getDoubleRemoveZero(e); + bool isCurrentSelect = _smallScore == strScore; // 当前选中 + + bool effective = e <= totalScore; + return InkWell( + onTap: effective + ? () { + easyThrottle('toMarkingVal', () => marking(fraction: e, strScore: strScore), + duration: easyThrottleTime); + } + : null, + child: Container( + width: isBroadwise ? 28.w : 52.w, + height: double.infinity, + margin: EdgeInsets.only(left: 3.h), + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all(width: 1.h, color: const Color.fromRGBO(224, 230, 255, 1)), + color: isCurrentSelect + ? Theme.of(context).primaryColor + : effective + ? const Color.fromRGBO(249, 250, 254, 1) + : Colors.grey[300], + borderRadius: BorderRadius.all(Radius.circular(2.w)), + ), + child: quickText( + strScore, + size: 15.sp, + color: isCurrentSelect ? Colors.white : const Color.fromRGBO(148, 163, 182, 1), + ), + ), + ); + }).toList(), + // if (sort != SortKeyboard.FULL_AND_AERO_TOP) + // InkWell( + // onTap: () { + // easyThrottle( + // 'toMarkingVal', + // () => marking( + // fraction: widget.totalScore, fullScore: true, strScore: widget.totalScore.toString()), + // duration: easyThrottleTime); + // }, + // child: Container( + // width: isBroadwise ? 28.w : 52.w, + // height: double.infinity, + // margin: EdgeInsets.only(left: 3.h), + // alignment: Alignment.center, + // decoration: BoxDecoration( + // border: Border.all(width: 1.h, color: const Color.fromRGBO(224, 230, 255, 1)), + // color: const Color.fromRGBO(249, 250, 254, 1), + // borderRadius: BorderRadius.all(Radius.circular(2.w)), + // ), + // child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + // ), + // ), + ], + ), + ), + ) + ], + ), + ); + } +} diff --git a/marking_app/lib/components/pz.dart b/marking_app/lib/components/pz.dart new file mode 100644 index 0000000..d4f97b4 --- /dev/null +++ b/marking_app/lib/components/pz.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class ZoomAnnotationsWidget extends StatefulWidget { + @override + _ZoomAnnotationsWidgetState createState() => _ZoomAnnotationsWidgetState(); +} + +class _ZoomAnnotationsWidgetState extends State { + double _scale = 1.0; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onScaleUpdate: (ScaleUpdateDetails details) { + setState(() { + _scale = details.scale; + }); + }, + child: Container( + height: double.infinity, + width: double.infinity, + child: CustomPaint( + painter: AnnotationsPainter(_scale), + ), + ), + ); + } +} + +class AnnotationsPainter extends CustomPainter { + final double scale; + + AnnotationsPainter(this.scale); + + @override + void paint(Canvas canvas, Size size) { + // Calculate the scaled height based on the scale factor + double scaledHeight = size.height * scale; + + Paint paint = Paint() + ..color = Colors.red + ..strokeWidth = 3.0 + ..strokeCap = StrokeCap.round; + + canvas.drawLine(Offset(0, 0), Offset(size.width, scaledHeight), paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/marking_app/lib/components/report/switch_identity_exams.dart b/marking_app/lib/components/report/switch_identity_exams.dart new file mode 100644 index 0000000..ff830f7 --- /dev/null +++ b/marking_app/lib/components/report/switch_identity_exams.dart @@ -0,0 +1,25 @@ +// 切换用户身份和考试 +import 'package:flutter/material.dart'; + +class SwitchIdentityExams extends StatelessWidget { + const SwitchIdentityExams({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // return showModalBottomSheet( + // //showModalBottomSheet函数底部面板相当于一个新的页面 + // backgroundColor: Colors.transparent, + // //颜色空白 + // isDismissible: true, + // //能否点击消失 + // isScrollControlled: true, + // //能否拖动消失 + // context: context, + // //接受cotext + // builder: (context) { + // return Container(); + // }, + // ); + return Container(); + } +} diff --git a/marking_app/lib/components/review_item_view.dart b/marking_app/lib/components/review_item_view.dart new file mode 100644 index 0000000..f5fde4c --- /dev/null +++ b/marking_app/lib/components/review_item_view.dart @@ -0,0 +1,164 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-21 16:16:27 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 18:13:22 + * @FilePath: \marking_app\lib\components\review_item_view.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/model/review/review_item.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; + +class ReviewItemView extends StatelessWidget { + final int markingUserId; + final int examSubjectId; + final ReviewItem item; + final bool exceptional; + final bool isHomeworkCorrection; + const ReviewItemView(this.markingUserId, this.item, this.examSubjectId, + {this.exceptional = false, this.isHomeworkCorrection = false, Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (isHomeworkCorrection) { + // 作业跳转 + RouterManager.router.navigateTo( + context, + '${RouterManager.markingHomeworkDoPath}?taskId=$markingUserId&detailId=${item.markingUserDetailId}&tabQuestionNum=${item.questionNo}', + transition: getTransition(), + ); + return; + } + RouterManager.router.navigateTo( + context, + '${RouterManager.markingDoPath}?questionNum=${Uri.encodeComponent(item.questionNum)}&markingUserId=$markingUserId&examSubjectId=$examSubjectId&detailId=${item.markingUserDetailId}&pageOper=${2}&isReview=${1}&exceptional=${exceptional ? 1 : ''}', + transition: getTransition(), + ); + }, + child: Container( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 16.h, bottom: 10.h), + margin: EdgeInsets.only(top: 14.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(6.w), + ), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(2.w, 2.h), //阴影y轴偏移量 + blurRadius: 14, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: Column(children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (item.isException) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(right: 2.w), + padding: EdgeInsets.only(bottom: 2.h), + child: Icon( + Icons.warning_amber_rounded, + color: const Color.fromRGBO(215, 74, 74, 1), + size: 18.sp, + ), + ), + Text( + '异常', + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14.sp, + color: const Color.fromRGBO(215, 74, 74, 1), + ), + ) + ], + ) + else + Text( + '评分:【${item.score}】', + style: TextStyle( + fontSize: 14.sp, + color: const Color.fromRGBO(45, 56, 76, 1), + ), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: quickText( + isHomeworkCorrection ? '学生: ' + item.studentName! : 'ID: ${item.markingUserDetailId}', + color: const Color.fromRGBO(148, 163, 182, 1), + size: 12.sp, + maxLines: 1, + ), + ), + ) + ], + ), + Container( + width: double.infinity, + margin: EdgeInsets.only(top: 10.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(4.w, 6.h), //阴影y轴偏移量 + blurRadius: 8, //阴影模糊程度 + spreadRadius: 0.2, //阴影扩散程度 + ) + ], + ), + child: ClipRRect( + //是ClipRRect,不是ClipRect + borderRadius: BorderRadius.circular(5.w), + child: FadeInImage.assetNetwork( + height: 100.h, + width: double.infinity, + placeholder: "assets/images/review_loding.png", + image: item.studentAnswerList[0], + fit: BoxFit.cover, + imageErrorBuilder: (context, error, stackTrace) { + return ClipRRect( + //是ClipRRect,不是ClipRect + borderRadius: BorderRadius.circular(5.w), + child: Image.asset( + width: double.infinity, + height: 100.h, + fit: BoxFit.cover, + "assets/images/review_error.png", + ), + ); + }, + ), + ), + ), + Container( + width: double.infinity, + margin: EdgeInsets.only(top: 12.h), + alignment: Alignment.centerRight, + child: Text( + item.finishedTime, + style: TextStyle( + fontSize: 12.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ), + ), + ]), + ), + ); + } +} diff --git a/marking_app/lib/main.dart b/marking_app/lib/main.dart new file mode 100644 index 0000000..6b57722 --- /dev/null +++ b/marking_app/lib/main.dart @@ -0,0 +1,99 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-06-15 17:58:30 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-03 11:53:08 + * @FilePath: \marking_app\lib\main.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:flutter/services.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/the_global.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + RouterManager.initRouter(); + + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]); // 屏幕刘海 + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // 屏幕强制竖屏 + // OrientationHelper.setEnabledSystemUIOverlays(SystemUiOverlay.values); + + runApp(ProviderScope(child: MyApp())); +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + late final StreamSubscription subscription; + late bool networkSituation = true; // 网络情况 + @override + void initState() { + networkMonitoring(); + super.initState(); + } + + @override + void dispose() { + subscription.cancel(); + super.dispose(); + } + + /// 网络监控 + void networkMonitoring() async { + subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { + if (ConnectivityResult.none == result) { + ToastUtils.showInfo('无法连接网络'); + networkSituation = false; + } else { + if (!networkSituation) { + if (ConnectivityResult.mobile == result) { + ToastUtils.showInfo('当前为移动网络,请注意网络使用情况', duration: const Duration(milliseconds: 500)); + } else { + ToastUtils.showSuccess('网络恢复正常'); + } + } + networkSituation = true; + } + }); + } + + @override + Widget build(BuildContext context) { + return ScreenUtilInit( + designSize: const Size(375, 812), + minTextAdapt: true, + splitScreenMode: true, + builder: (context1, child) { + return MaterialApp( + title: '远轩阅卷系统', + navigatorKey: TheGlobal.navigatorKey, + debugShowCheckedModeBanner: false, + theme: ThemeData( + primarySwatch: createMaterialColor(const Color.fromRGBO(46, 91, 255, 1)), + // textTheme: Typography.englishLike2018.apply(fontSizeFactor: 1.sp,), + textTheme: TextTheme( + bodyLarge: TextStyle(fontSize: 14.sp, color: Colors.black87), + ), + ), + onGenerateRoute: RouterManager.generator, //全局注册 + builder: EasyLoading.init(), + ); + }, + ); + } +} diff --git a/marking_app/lib/pages/common/event_bus_mixin.dart b/marking_app/lib/pages/common/event_bus_mixin.dart new file mode 100644 index 0000000..ad54f75 --- /dev/null +++ b/marking_app/lib/pages/common/event_bus_mixin.dart @@ -0,0 +1,83 @@ +// event_bus 混入类 +import 'dart:async'; + +import 'package:event_bus/event_bus.dart'; +import 'package:marking_app/utils/event_bus_utils.dart'; + +mixin EventBusMixin { + StreamSubscription? _subscription; + static final EventBusUtils _exampleUtil = EventBusUtils(); + static final EventBus _eBus = _exampleUtil.getEventBus(); + + /* + * 发起事件总线监听 + * @param {Function} callback 回调函数 + */ + /// 发起事件总线监听 @param {Function} callback 回调函数 + StreamSubscription eventOn({required void Function(T) callback}) { + _subscription = _eBus.on().listen(callback); + return _subscription!; + } + + // 触发事件总线 + void eventFire({required T model}) { + _exampleUtil.toFire(model); + } + + // 关闭监听总线事件 + void eventCancel() { + _subscription?.cancel(); + } +} + +mixin EventBusMixinSub { + StreamSubscription? _subscriptionSub; + static final EventBusUtils _exampleUtilSub = EventBusUtils(); + static final EventBus _eBusSub = _exampleUtilSub.getEventBus(); + + /* + * 发起事件总线监听 + * @param {Function} callback 回调函数 + */ + /// 发起事件总线监听 @param {Function} callback 回调函数 + StreamSubscription eventOnSub({required void Function(T) callback}) { + _subscriptionSub = _eBusSub.on().listen(callback); + return _subscriptionSub!; + } + + // 触发事件总线 + void eventFireSub({required T model}) { + _exampleUtilSub.toFire(model); + } + + // 关闭监听总线事件 + void eventCancelSub() { + _subscriptionSub?.cancel(); + } +} + +mixin EventBusMixinSubLast { + StreamSubscription? _subscriptionSubLast; + static final EventBusUtils _exampleUtilSubLast = EventBusUtils(); + static final EventBus _eBusSubLast = _exampleUtilSubLast.getEventBus(); + + /* + * 发起事件总线监听 + * @param {Function} callback 回调函数 + */ + /// 发起事件总线监听 @param {Function} callback 回调函数 + StreamSubscription eventOnSubLast({required void Function(T) callback}) { + _subscriptionSubLast = _eBusSubLast.on().listen(callback); + return _subscriptionSubLast!; + } + + // 触发事件总线 + void eventFireSubLast({required T model}) { + _exampleUtilSubLast.toFire(model); + } + + // 关闭监听总线事件 + void eventCancelSubLast() { + _subscriptionSubLast?.cancel(); + } +} diff --git a/marking_app/lib/pages/common/notFoundPage.dart b/marking_app/lib/pages/common/notFoundPage.dart new file mode 100644 index 0000000..e69de29 diff --git a/marking_app/lib/pages/common/startUpPage.dart b/marking_app/lib/pages/common/startUpPage.dart new file mode 100644 index 0000000..7bd546d --- /dev/null +++ b/marking_app/lib/pages/common/startUpPage.dart @@ -0,0 +1,33 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-06 10:33:08 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-06 10:34:50 + * @FilePath: \marking_app\lib\pages\common\startUpPage.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:flutter/material.dart'; + +class StartUpPage extends StatefulWidget { + const StartUpPage({Key? key}) : super(key: key); + + @override + State createState() => _StartUpPageState(); +} + +class _StartUpPageState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '这是启动页', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ); + } +} diff --git a/marking_app/lib/pages/home/hooks/use_marking_statistics.dart b/marking_app/lib/pages/home/hooks/use_marking_statistics.dart new file mode 100644 index 0000000..8e02051 --- /dev/null +++ b/marking_app/lib/pages/home/hooks/use_marking_statistics.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; +import 'package:marking_app/common/model/marking/marking_statistics.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +class UseMarkingStatistics with CommonMixin { + final List typeNames = ['阅卷', '仲裁', '异常']; + List theTypes; + ValueNotifier currentType; // 设置主页 + ValueNotifier> statistics; // 统计数据 + + UseMarkingStatistics._({required this.theTypes, required this.statistics, required this.currentType}); + + factory UseMarkingStatistics.use({required int pageType}) { + List _theTypes = MarkingListType.values; + + return UseMarkingStatistics._( + theTypes: _theTypes.toList(), + currentType: useState(_theTypes[pageType]), + statistics: useState({}), + ); + } + + getData() async { + RestClient client = await getClient(); + Map map = {}; + BaseStructureResult> result = await client.getMarkingStatistics(); + if (result.success && result.data != null) { + // [ { 0,"正常"}, { 1,"仲裁"},{ 2,"异常"}] + List _theTypes = MarkingListType.values; + result.data!.forEach((e) => map[_theTypes[e.navld]] = e.total); + } + statistics.value = map; + } +} diff --git a/marking_app/lib/pages/home/index.dart b/marking_app/lib/pages/home/index.dart new file mode 100644 index 0000000..20a94c6 --- /dev/null +++ b/marking_app/lib/pages/home/index.dart @@ -0,0 +1,466 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-05 16:42:39 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 12:18:22 + * @FilePath: \marking_app\lib\pages\home\index.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/model/common/base_page.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:marking_app/common/model/event_bus/home_marking_statistics.dart'; +import 'package:marking_app/common/model/job/job_task_item.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/home/hooks/use_marking_statistics.dart'; +import 'package:marking_app/pages/homework_correction/components/new_version_of_homework/homework_tasks_view_item.dart'; +import 'package:marking_app/provider/review_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/marking/marking_item.dart'; +import 'package:marking_app/common/model/marking/marking_list_params.dart'; +import 'package:marking_app/components/TestPaperItem.dart'; +import 'package:marking_app/provider/user_provider.dart'; + +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; + +part 'index.g.dart'; + +class TheHomePage extends StatefulHookConsumerWidget { + const TheHomePage({Key? key}) : super(key: key); + + @override + TheHomePageState createState() => TheHomePageState(); +} + +class TheHomePageState extends ConsumerState + with + CommonMixin, + EventBusMixin, + SingleTickerProviderStateMixin, + RefreshDataHandle, + AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + + /* Tab控制器 */ + late TabController _tabController; + int _tabIndex = 0; + bool completedToRefresh = true; + + /* 阅卷 */ + bool markingRequesting = false; // 是否正在请求中 + late final EasyRefreshController _refreshController1; + MarkingListParams params1 = MarkingListParams( + isFinish: false, + page: RequestConfig.basePage.page, + // limit: RequestConfig.basePage.limit, + limit: 5, pageType: 0, + ); + List markingDatas1 = []; + int totalMarkNumber = 0; + RemoveListener? _currentTaskIdListener; + + /* 作业 */ + late final EasyRefreshController _refreshController2; + MarkingListParams params2 = MarkingListParams( + isFinish: false, + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + pageType: 0, + ); + List markingDatas2 = []; + int totalJobNumber = 0; + + @override + void initState() { + _tabController = TabController(initialIndex: _tabIndex, length: 2, vsync: this); + _refreshController1 = EasyRefreshController(); + _refreshController2 = EasyRefreshController(); + + Future.delayed(Duration.zero, () { + //在这里处理页面 + _currentTaskIdListener = ref.read(currentTaskIdProvider.notifier).addListener((state) { + int? taskId = state.taskId; + if (taskId != null && state.refresh) { + _refreshController1.callRefresh(); + } + }); + }); + super.initState(); + } + + @override + void dispose() { + // ref.read(currentTaskIdProvider.notifier).clean(); + if (_currentTaskIdListener != null) { + _currentTaskIdListener!(); + } + eventCancel(); + _tabController.dispose(); + _refreshController1.dispose(); + _refreshController2.dispose(); + super.dispose(); + } + + /* 发起请求 => 作业 */ + Future toGetPageDataForWork({bool isReFresh = false}) async { + if (!isReFresh) { + params2.page++; + } + RestClient client = await getClient(); + BasePageData? results = await toRefreshData( + _refreshController2, + api: client.getJobsByPage, + params: params2, + isReFresh: isReFresh, + context: context, + ); + if (results != null) { + if (isReFresh) { + markingDatas2.clear(); + setState(() { + totalJobNumber = results.total; + markingDatas2 = results.items; + }); + } else { + setState(() { + totalJobNumber = results.total; + markingDatas2.addAll(results.items); + }); + } + } + } + + /* 发起请求 => 阅卷 */ + Future toGetPageDataForMarking({bool isReFresh = false}) async { + markingRequesting = true; + try { + if (!isReFresh) { + params1.page++; + } + RestClient client = await getClient(); + BasePageData? results = await toRefreshData( + _refreshController1, + api: client.getMarkingsByPage, + params: params1, + isReFresh: isReFresh, + context: context, + ); + if (results != null) { + eventFire(model: HomeMarkingStatistics()); + if (isReFresh) { + markingDatas1.clear(); + totalMarkNumber = results.total; + + // NORMAL, // 正常 + // ARBITRATE, // 仲裁 + // EXCEPTIONAL, // 异常 + int pageType = params1.pageType; + markingDatas1 = results.items; + setState(() {}); + } else { + setState(() { + totalMarkNumber = results.total; + markingDatas1.addAll(results.items); + }); + } + } + } catch (e) { + } finally { + markingRequesting = false; + } + } + + @override + Widget build(BuildContext context) { + super.build(context); //调用super.build(返回值始终返回null,应将其忽略) + List markingTypes = MarkingListType.values; + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: Color.fromRGBO(249, 250, 254, 1), + body: Column( + children: [ + TopUserInfo(), + $TheTabBar( + controller: _tabController, + onTap: (int val) { + setState(() { + _tabIndex = val; + if (val == 1 && completedToRefresh) { + _refreshController2.callRefresh(); + completedToRefresh = false; + } + }); + }, + ), + SizedBox(height: 10.h), + MarkingStatisticsBox( + controller: _refreshController1, + isShow: _tabIndex != 0, + params: params1, + markingRequesting: markingRequesting, + ), + SizedBox(height: 4.h), + Expanded( + child: IndexedStack( + index: _tabIndex, + children: [ + EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: markingDatas1.isEmpty ? const MyEmptyWidget() : null, + controller: _refreshController1, + header: MaterialHeader(), + footer: TaurusFooter(), + child: ListView.builder( + padding: EdgeInsets.only(top: 4.h, bottom: 10.h), + itemBuilder: (context, index) { + return TestPaperItem( + markingItem: markingDatas1[index], + markingtype: markingTypes[params1.pageType], + call: () { + _refreshController1.callRefresh(); + }, + ); + }, + itemCount: markingDatas1.length, + ), + onRefresh: () => toGetPageDataForMarking(isReFresh: true), + onLoad: () => toGetPageDataForMarking(isReFresh: false), + ), + EasyRefresh( + firstRefresh: false, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: markingDatas2.isEmpty ? const MyEmptyWidget() : null, + controller: _refreshController2, + header: MaterialHeader(), + footer: TaurusFooter(), + child: ListView.builder( + padding: EdgeInsets.only(top: 4.h, bottom: 10.h, left: 12.w, right: 12.w), + itemBuilder: (context, index) => HomeworkTasksViewItem( + completed: false, + jobTaskItem: markingDatas2[index], + call: () => _refreshController2.callRefresh(), + ), + itemCount: markingDatas2.length, + ), + onRefresh: () => toGetPageDataForWork(isReFresh: true), + onLoad: () => toGetPageDataForWork(isReFresh: false), + ), + ], + ), + ) + ], + )), + ); + } +} + +class TopUserInfo extends ConsumerWidget { + const TopUserInfo({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userState = ref.watch(userProvider); + + return InkWell( + onTap: () { + RouterManager.router.navigateTo(context, RouterManager.userMinePath, transition: getTransition()); + }, + child: Container( + color: Colors.white, + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 4.h, left: 14.w, right: 14.w, bottom: 19.h), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: Color.fromRGBO(245, 246, 251, 1), + borderRadius: BorderRadius.circular(30.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(50.w), + child: Container( + alignment: Alignment.center, + color: Color.fromRGBO(163, 211, 255, 1), + padding: EdgeInsets.all(1.r), + child: Image.asset('assets/images/logo.png', width: 32.w, height: 32.w), + ), + ), + Container( + margin: EdgeInsets.only(left: 10.h), + child: Text( + userState.userName, + style: TextStyle(fontSize: 15.sp, color: const Color.fromRGBO(45, 56, 76, 0.9)), + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: Icon( + Icons.arrow_forward_ios_rounded, + size: 12.sp, + color: const Color.fromRGBO(45, 56, 76, 0.9), + ), + ), + SizedBox(width: 5.w) + ], + ), + ), + Expanded(child: SizedBox()) + ], + ), + ), + ); + } +} + +@swidget +Widget $theTabBar({required TabController controller, ValueChanged? onTap}) { + return Container( + alignment: Alignment.centerLeft, + height: 38.h, + child: TabBar( + controller: controller, + unselectedLabelStyle: TextStyle(fontSize: 17.sp, color: const Color.fromRGBO(45, 56, 76, 1)), + labelStyle: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + isScrollable: true, + labelColor: const Color.fromRGBO(45, 56, 76, 1), + indicatorSize: TabBarIndicatorSize.label, // 设置指示器高度和标签一样高 + // labelPadding: EdgeInsets.symmetric(vertical: 0), // 设置标签的内边距 + // background: linear-gradient(270deg, #2E5BFF 30.23%, rgba(46, 91, 255, 0.00) 96.59%); + // indicatorColor: RectangleIndicator(), + // indicator: BoxDecoration( + // gradient: LinearGradient( + // begin: Alignment.centerLeft, + // end: Alignment.centerRight, + // colors: [ + // Color.fromRGBO(46, 91, 255, 0.00), + // Color(0xFF2E5BFF), + // ], + // stops: [0.3023, 0.9659], + // // transform: GradientRotation(3.14 / 2), //将270度转换为弧度 + // ), + // ), + // indicator: BoxDecoration( + // // 设置指示器样式 + // gradient: LinearGradient( + // colors: [Colors.yellow, Colors.green], // 设置渐变色 + // begin: Alignment.centerLeft, + // end: Alignment.centerRight, + // ), + // ), + onTap: onTap, + tabs: const [Tab(text: '阅卷'), Tab(text: '作业')], + ), + ); +} + +class MarkingStatisticsBox extends HookWidget with EventBusMixin { + bool isShow; + MarkingListParams params; + EasyRefreshController controller; + bool markingRequesting; + + MarkingStatisticsBox( + {required this.controller, required this.params, required this.isShow, required this.markingRequesting}); + + @override + Widget build(BuildContext context) { + UseMarkingStatistics _useTypesOf = UseMarkingStatistics.use(pageType: params.pageType); + MarkingListType _currentType = _useTypesOf.currentType.value; + Map _statisticMap = _useTypesOf.statistics.value; + + useEffect(() { + eventOn(callback: (HomeMarkingStatistics e) { + _useTypesOf.getData(); + }); + return () { + eventCancel(); + }; + }, []); + return Offstage( + offstage: isShow, + child: Container( + padding: EdgeInsets.only(left: 14.w, top: 4.h), + width: double.infinity, + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: _useTypesOf.theTypes.map((e) { + bool isCurrent = _currentType == e; + int? total = _statisticMap[e]; + + // if (total == null && e != MarkingListType.NORMAL) return Container(); + return GestureDetector( + onTap: () => easyThrottle('typeOfCall', () { + if (markingRequesting) return; + controller.callRefresh(duration: Duration(milliseconds: 101)); + _useTypesOf.currentType.value = e; + params.pageType = e.index; + // _useTypesOf.setType(controller: controller, params: params); + }, duration: Duration(milliseconds: 2000)), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + margin: EdgeInsets.only(right: 20.w), + decoration: !isCurrent + ? BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3.r)), + color: Color.fromRGBO(46, 91, 255, 0.05), + ) + : BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3.r)), + color: Color.fromRGBO(46, 91, 255, 0.05), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.1), + blurRadius: 4, + spreadRadius: 0, + offset: Offset(0, 0), + ), + ], + ), + child: Text( + _useTypesOf.typeNames[e.index] + (total == null || total <= 0 ? '' : '($total)'), + style: TextStyle( + color: isCurrent ? Color.fromRGBO(46, 56, 76, 1) : Color.fromRGBO(80, 87, 103, 1), + fontSize: isCurrent ? 14.sp : 13.sp), + ), + ), + ); + }).toList(), + ), + ), + ); + } +} diff --git a/marking_app/lib/pages/homework_correction/components/examImage.dart b/marking_app/lib/pages/homework_correction/components/examImage.dart new file mode 100644 index 0000000..5c084f6 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/examImage.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class ExamImage extends HookWidget { + final String imageUrl; // 默认考试图片 + final bool isDraw; // 是否开始画图 + const ExamImage({required this.imageUrl, required this.isDraw, Key? key}) : super(key: key); + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/marking_app/lib/pages/homework_correction/components/jobPictureOverview.dart b/marking_app/lib/pages/homework_correction/components/jobPictureOverview.dart new file mode 100644 index 0000000..ac579f0 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/jobPictureOverview.dart @@ -0,0 +1,1598 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-23 11:55:16 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 17:01:15 + * @FilePath: \marking_app\lib\components\JobPictureOverview.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:ui' as ui; +import 'dart:io'; +import 'dart:async'; +import 'package:achievement_view/achievement_view.dart'; +import 'package:crypto/crypto.dart' as crypto; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image/image.dart' as img; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:marking_app/common/model/job/job_note_taking_trajectory.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +import 'package:marking_app/common/model/job/test_questions_image_info.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config_params.dart'; +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/components/theExamPaperDrawing.dart'; +import 'package:marking_app/pages/homework_correction/components/trajectoryView.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/do_papers_job_custom_paint_size_bus.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/job_notes_view_bus.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_provider.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_trajectory_provider.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/provider/upload_file_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'package:uuid/uuid.dart'; + +import '../eventBus/job_do_papers_switch_operation_sub_bus.dart'; + +part 'jobPictureOverview.g.dart'; + +class JobPictureOverview extends StatefulHookConsumerWidget { + final double containerWidth; + final double containerHeight; + final MarkingTextQuestionJob jobData; + + const JobPictureOverview({ + required this.jobData, + required this.containerWidth, + required this.containerHeight, + Key? key, + }) : super(key: key); + + @override + JobPictureOverviewState createState() => JobPictureOverviewState(); +} + +class JobPictureOverviewState extends ConsumerState + with CommonMixin, EventBusMixin { + final GlobalKey theglobalKey = GlobalKey(); + final GlobalKey<_ExamPaperDrawingState> examPaperDrawingKey = GlobalKey<_ExamPaperDrawingState>(); + final GlobalKey trajectoryViewStateKey = GlobalKey(); + + final int currentIndex = 0; + late AnnotationGraffitiSwitch graffitiSwitch; + late RemoveListener _annotationsListener; // 批注关闭监听 + late bool showAnnotations; + JobNoteTakingTrajectory? _jobNoteTakingTrajectory; + File? temFile; // 批注临时数据 + + bool canScroll = true; + + bool viewHomeworkNotes = false; + JobNotesViewBus? _oldNotesModel; + + @override + void initState() { + showAnnotations = widget.jobData.commentImageUrl != null; + // questionImgUrl = widget.jobData.commentImageUrl ?? widget.jobData.imageUrl; + eventOn(callback: (eventModel) async { + switch (eventModel.runtimeType) { + case JobNotesViewBus: // 笔记回显 + var notesModel = eventModel as JobNotesViewBus; + try { + ToastUtils.showLoading(); + ref.read(jobDrawingTrajectoryProvider.notifier).setVal([]); + if (notesModel.questionNo == _oldNotesModel?.questionNo) { + viewHomeworkNotes = false; + _oldNotesModel = null; + _jobNoteTakingTrajectory = null; + toUpState(setState, () {}, mounted); + return; + } + + // 切换试题回显数据 + bool switchNotesFlag = false; + if (viewHomeworkNotes && _oldNotesModel != null && _oldNotesModel?.questionNo != notesModel.questionNo) { + switchNotesFlag = true; + } + + JobNoteTakingTrajectory? trajectory = + await toGetNoteTakingTrajectory(notesModel.paperId, notesModel.questionNo); + if (trajectory == null) { + // 重置数据 + viewHomeworkNotes = false; + _oldNotesModel = null; + _jobNoteTakingTrajectory = null; + } else { + viewHomeworkNotes = true; + _oldNotesModel = notesModel; + _jobNoteTakingTrajectory = trajectory; + if (switchNotesFlag) { + viewHomeworkNotes = false; + _oldNotesModel = null; + _jobNoteTakingTrajectory = null; + toUpState(setState, () {}, mounted); + await Future.delayed(Duration(seconds: 1)); + viewHomeworkNotes = true; + _oldNotesModel = notesModel; + _jobNoteTakingTrajectory = trajectory; + } + } + toUpState(setState, () {}, mounted); + } catch (e) { + print(e); + } finally { + ToastUtils.dismiss(); + } + + break; + case BottomAnnotationSwitchCleanall: + // 底部清空按钮监听 提示恢复原图 + BottomAnnotationSwitchCleanall theAnnotation = eventModel as BottomAnnotationSwitchCleanall; // 底部动作 + if ((theAnnotation.cleanAll || theAnnotation.previousStep) && showAnnotations) { + List? pointsPureData = examPaperDrawingKey.currentState?.pointsPureData; + if (pointsPureData == null || pointsPureData.isEmpty) { + showDialog( + // 表示点击灰色背景的时候是否消失弹出框 + barrierDismissible: false, + context: context, + builder: (context1) { + return AlertDialog(content: quickText("请确定要重置已经批注的试题图片吗?"), actions: [ + TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)), + TextButton( + child: quickText("确定", color: Theme.of(context).primaryColor), + onPressed: () => Navigator.pop(context1, true)) + ]); + }).then((value) { + if (value == null || !value) return; + showAnnotations = false; + toUpState(setState, () {}, mounted); + }); + } + } + break; + default: + } + }); + // 更新批注记录 + Future.delayed(Duration.zero, () { + int theTaskdetailId = ref.read(jobHomeWorkIdProvider); // 唯一的任务ID + List taskDatas = ref.read(jobHomeWorkProvider); + ref.read(jobHomeWorkIdProvider.notifier).setVal(widget.jobData.taskId); + if (theTaskdetailId > 0 && theTaskdetailId != widget.jobData.taskId && taskDatas.isNotEmpty) { + ref.read(jobHomeWorkProvider.notifier).setVal([]); + } + }); + _annotationsListener = ref.read(annotationGraffitiSwitchProvider.notifier).addListener((state) async { + graffitiSwitch = state; + canScroll = !state.magnifier; + if (mounted) { + // 清空笔记回显 + Future.delayed(const Duration(seconds: 0)).then((_) { + ref.read(jobDrawingTrajectoryProvider.notifier).setVal([]); + }); + } + toUpState(setState, () {}, mounted); + }); + super.initState(); + } + + // 获取学生笔记轨迹 + Future toGetNoteTakingTrajectory(int paperId, String questionNo) async { + try { + RestClient _client = await getClient(); + BaseStructureResult baseRes = await _client.getNoteTakingTrajectory(paperId, questionNo); + if (baseRes.success && baseRes.data != null) { + return baseRes.data!; + } + ToastUtils.showError('获取学生笔记失败,请重试'); + } catch (e) { + print('${e}'); + } + return null; + } + + @override + void dispose() { + super.dispose(); + eventCancel(); + _annotationsListener(); + try { + if (temFile != null) { + bool flieExist = temFile!.existsSync(); + if (flieExist) temFile!.delete(); + } + } catch (e) { + print(e); + } + } + + Future saveImage() async { + try { + ToastUtils.showLoading(); + TestQuestionsImageInfo? theImageInfo = examPaperDrawingKey.currentState?.imagInfoModel; // 图片和容器比例数据 + double pixelRatio = 1; + if (temFile == null) { + if (examPaperDrawingKey.currentState?.pointsPureData.isEmpty ?? true) return null; + // 没有图片就上传图片 + RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; + if (boundary == null) return null; + if (theImageInfo != null) { + // 计算像素比例 + double imageWidth = theImageInfo.width; // 图片宽 + double boxViewWidth = theImageInfo.boxWidth; // 图片容器宽 + pixelRatio = imageWidth / boxViewWidth; // 得到比例 + } + ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + /// 等于null 已经是异常了 + return null; + } + Directory appDocDir = await getApplicationDocumentsDirectory(); + List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + await file.writeAsBytes(bytes); // 将ByteData写入文件 + temFile = file; + } + if (theImageInfo != null) { + // 剪切图片 + List? points = examPaperDrawingKey.currentState?.points; + if (points?.isNotEmpty ?? false) { + // print('开始打印......................'); + List dyArray = + points!.where((e) => !e.eraser && e.data != null).map((e) => e.data!.dy).toList(); // 获得有效的批注数据 + if (dyArray.isNotEmpty && theImageInfo.scaleHeight != null) { + /** + * 1:图片小于视图 按照最低点和最高点剪切图片 + * 2:图片大于视图 不剪切图片 + */ + double remainingHeight = theImageInfo.boxHeight - theImageInfo.scaleHeight!; // 剩余高度 + /** 不需要再切割,图片和容器已经按照容器中部展示 + if (remainingHeight > 1) { + // print('全部的数.............'); + // print(dyArray.toString()); + double lowestNumber = dyArray.reduce((currMin, next) => currMin < next ? currMin : next); + double highestNumber = dyArray.reduce((currMax, next) => currMax > next ? currMax : next); + + // print('最低的数: $lowestNumber'); + // print('最高的数: $highestNumber'); + + final img.Image? originalImage = img.decodeImage(temFile!.readAsBytesSync()); + if (originalImage != null) { + temFile?.delete(); // 删除以后缓存图片 + // 计算裁剪区域的像素坐标 + // 裁剪图像 + + double imageStartPositionY = remainingHeight / 2; // 图片起始Y轴位置 + double imageEndPositionY = imageStartPositionY + theImageInfo.scaleHeight!; // 图片结束Y轴位置 + double startY = imageStartPositionY; + if (lowestNumber < imageStartPositionY) { + // 画图最小坐标小于图片 (向上操作了图片) + startY = lowestNumber; + } + + double endY = highestNumber - startY; // 超出 + // endY = endY + 10 > theImageInfo.boxHeight ? endY : endY + 10; + + if (highestNumber < imageEndPositionY) { + endY = imageEndPositionY - startY; + // endY = endY + 10 > theImageInfo.scaleHeight! ? endY : endY + 10; + } + + final img.Image clippedImage = img.copyCrop( + originalImage, + x: 0, + y: (startY * pixelRatio).toInt(), + height: (endY * pixelRatio).toInt(), + width: originalImage.width, + ); + + Directory appDocDir = await getApplicationDocumentsDirectory(); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + temFile = await file.writeAsBytes(img.encodePng(clippedImage)); + } + } */ + } + } + } + crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes()); + String? fileName = temFile?.path.split('/').last; + // print('获得文件名'); // 打印文件名 + // print(fileName); // 打印文件名 + + RestClient _client = await getClient(); + BaseStructureResult resUploadConfig = + await _client.getUploadFile(UploadFileInterfaceConfigParams( + fileName: fileName ?? '1.png', + fileMd5: fileMd5.toString(), + contentLength: temFile!.lengthSync(), + )); + if (!resUploadConfig.success || resUploadConfig.data == null) { + ToastUtils.getFluttertoast(msg: '获取图片上传配置失败', context: context); + return null; + } + if (resUploadConfig.data!.uploadUri == null) { + return FileResult(myObject: '', success: true, url: resUploadConfig.data!.downloadUri); + } + FileResult? resFile = + await ref.read(uploadFileProvider.notifier).getUploadFileConfig(resUploadConfig.data!, temFile!); + // FileResult? resFile = await ref + // .read(uploadFileProvider.notifier) + // .uploadFile(temFile!.path, widget.imageItems[currentIndex], ref.read(userProvider).id.toString()); + if (resFile != null && resFile.success) { + resFile.otherParam = currentIndex; + return resFile; + } + } catch (e) { + toPrint(val: e.toString()); + } finally { + // ToastUtils.dismiss(); + } + return null; + } + + @override + Widget build(BuildContext context) { + if (viewHomeworkNotes && _jobNoteTakingTrajectory?.paperPicture != null && _jobNoteTakingTrajectory != null) + return InkWell( + onTap: () { + if (_oldNotesModel != null) { + return eventFire(model: _oldNotesModel!); // todo 是否关闭试题 + } + AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "笔记回显提示", + subTitle: "当前正处于笔记回显", + color: Theme.of(context).primaryColor, + ).show(context); + }, + child: TrajectoryView( + key: trajectoryViewStateKey, + boxWidth: widget.containerWidth, + boxHeight: widget.containerHeight, + questionNumber: _oldNotesModel?.questionNo ?? '', + trajectory: _jobNoteTakingTrajectory!, + ), + ); + print('最新图片:${widget.jobData.imageUrl}'); + return Container( + margin: EdgeInsets.only(top: 1.h), + width: widget.containerWidth, + height: widget.containerHeight, + child: Scrollbar( + // thumbVisibility: true, + child: SingleChildScrollView( + physics: canScroll ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.zero, + scrollDirection: Axis.vertical, // 设置垂直滚动 + child: $LocalAndNetworkSwitch( + boxWidth: widget.containerWidth, + boxHeight: widget.containerHeight, + theglobalKey: theglobalKey, + graffitiSwitch: graffitiSwitch, + drawFlag: false, // TODO 是否正在绘制批阅 + examGlobalKey: examPaperDrawingKey, + showAnnotations: showAnnotations, + questionImgUrl: widget.jobData.imageUrl, + commentImageUrl: widget.jobData.commentImageUrl, + ), + ), + ), + ); + } +} + +// 试卷绘制 +class ExamPaperDrawing extends StatefulHookConsumerWidget { + double boxWidth; + double boxHeight; + bool showAnnotations; + String questionImgUrl; + String? commentImageUrl; + BoxDecoration? decoration; + AnnotationGraffitiSwitch graffitiSwitch; + List? points; + List? pointsPureData; + ValueNotifier> imageLoaded; + + GlobalKey globalKey; + // Function(String) imageCall; + ExamPaperDrawing({ + required this.boxWidth, + required this.boxHeight, + required this.points, + required this.showAnnotations, + required this.questionImgUrl, + required this.commentImageUrl, + required this.pointsPureData, + required this.graffitiSwitch, + required this.globalKey, + required this.imageLoaded, + this.decoration, + Key? key, + }) : super(key: key); + + @override + _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); +} + +class _ExamPaperDrawingState extends ConsumerState with EventBusMixin { + ImageStream? imageStream; // 图片监听数据 + TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 + late ImageStreamListener theImageStreamListener; + // 用于记录手指位置的变量 + late List points; + late List pointsPureData; + // 用于记录绘图结果的变量 + bool _isEraserPressed = false; // 橡皮擦按下 + Offset? _eraserPosition; // 按下位置 + Offset? globalPosition = null; // 是否正在绘制 + + int activePointers = 0; + + @override + void initState() { + points = widget.points ?? []; + pointsPureData = widget.pointsPureData ?? []; + // 事件总线监听 + eventOn(callback: (eventModel) { + switch (eventModel.runtimeType) { + case BottomAnnotationSwitchCleanall: + var item = eventModel as BottomAnnotationSwitchCleanall; + if (item.previousStep) { + if (points.isEmpty) { + return ToastUtils.getFluttertoast(context: context, msg: '已经没有批注了', gravity: ToastGravity.BOTTOM); + } + var index = pointsPureData.toList().lastIndexOf(null); + if (index != -1) { + if (index + 1 == pointsPureData.length) { + pointsPureData = pointsPureData.sublist(0, index); + points.sublist(0, index); + index = pointsPureData.toList().lastIndexOf(null); + index == -1 ? -1 : index + 1; + } + if (index != -1) { + pointsPureData = pointsPureData.sublist(0, index); + points = points.sublist(0, index); + ref.read(jobHomeWorkProvider.notifier).setVal(points); + // toUpState(setState, () {}, mounted); + } else { + item.cleanAll = true; + } + } else { + item.cleanAll = true; + } + } + + if (item.cleanAll) { + pointsPureData.clear(); + points.clear(); + toUpState(setState, () {}, mounted); + } + + if (item.uploadImage) { + // 图片确认按钮,生成 + if (pointsPureData.isEmpty) { + ToastUtils.showInfo('请先批注再提交'); + return; + } + } + break; + case JobDoPapersSwitchOperationSubBus: // 关闭底部功能栏 有笔画需要改变笔画位置 + // var item = eventModel as JobDoPapersSwitchOperationSubBus; + // bool annotationSwitch = item.open; + // double heightVal = item.height; + // if (points.isNotEmpty) { + // // item.height + // points.forEach((point) { + // bool open = point.annotationSwitch; // 是打开的功能栏的记录 + // Offset? locatPoint = point.data; + // if (annotationSwitch != open && locatPoint != null) { + // point.annotationSwitch = annotationSwitch; + // locatPoint = + // Offset(locatPoint.dx, annotationSwitch ? locatPoint.dy - heightVal : locatPoint.dy + heightVal); + // point.data = locatPoint; + // } + // }); + // ref.read(jobHomeWorkProvider.notifier).setVal(List.from(points)); + // } + break; + default: + } + }); + + theImageStreamListener = ImageStreamListener((ImageInfo info, bool _) { + // 获取图片的宽高 + imagInfoModel = TestQuestionsImageInfo( + boxHeight: widget.boxHeight, + boxWidth: widget.boxWidth, + url: widget.showAnnotations ? (widget.commentImageUrl ?? widget.questionImgUrl) : widget.questionImgUrl, + height: info.image.height.toDouble(), + width: info.image.width.toDouble(), + ); + var theHeight = imagInfoModel?.scaleHeight; + if (theHeight != null) eventFire(model: DoPapersJobCustomPaintSizeBus(theHeight)); + print(imagInfoModel?.toJson()); + }); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + try { + imageStream?.removeListener(theImageStreamListener); + } catch (e) {} + eventCancel(); + } + + @override + Widget build(BuildContext context) { + String imgUrl = widget.showAnnotations ? (widget.commentImageUrl ?? widget.questionImgUrl) : widget.questionImgUrl; + return Listener( + onPointerDown: (PointerDownEvent event) { + // 判断当前是否已经有触摸点,如果有则忽略该触摸事件 + + // 处理单个触摸点按下的逻辑 + activePointers++; + }, + onPointerMove: (PointerMoveEvent event) { + try { + // imagInfoModel==null 图片加载完成 + if (activePointers != 1 || imagInfoModel == null) return; + + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + Offset localPosition = event.localPosition; + double imageHeightOffset = (imagInfoModel?.imageHeightOffsetStart ?? 0); + if (imageHeightOffset > 0) { + if (localPosition.dy < imageHeightOffset) return; + if (localPosition.dy > imagInfoModel!.imageHeightOffsetend!) return; + } + bool annotationSwitch = ref.read(annotationGraffitiSwitchProvider).annotationSwitch; // 底部开关是否打开 + + // + double remainingHeight = imagInfoModel!.boxHeight - imagInfoModel!.scaleHeight!; // 剩余高度 + if (remainingHeight > 1) { + double imageStartPositionY = remainingHeight / 2; // 图片起始Y轴位置 + localPosition = Offset(localPosition.dx, localPosition.dy - imageStartPositionY); + } + + pointsPureData = List.from(pointsPureData)..add(localPosition); + points = List.from(points) + ..add(GestureRecording( + eraser: widget.graffitiSwitch.openEraser, data: localPosition, annotationSwitch: annotationSwitch)); + _eraserPosition = localPosition; + _isEraserPressed = true; + ref.read(jobHomeWorkProvider.notifier).setVal(points); + } + } catch (e) { + toPrint(val: '进入报错'); + } + }, + onPointerUp: (PointerUpEvent event) { + // 处理单个触摸点抬起的逻辑 + activePointers--; + globalPosition = null; + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + pointsPureData.add(null); // 增加空点以分隔不同的线段 + bool annotationSwitch = ref.read(annotationGraffitiSwitchProvider).annotationSwitch; // 底部开关是否打开 + points.add(GestureRecording(eraser: widget.graffitiSwitch.openEraser, annotationSwitch: annotationSwitch)); + _isEraserPressed = false; + _eraserPosition = null; + ref.read(jobHomeWorkProvider.notifier).setVal(points); + } + }, + child: Stack( + alignment: const FractionalOffset(0.5, 0.5), + children: [ + Container( + color: Colors.transparent, + constraints: BoxConstraints(minHeight: widget.boxHeight), + alignment: Alignment.center, + child: $TheCachedNetworkImage( + (context, imageProvider) { + Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); + imageStream?.removeListener(theImageStreamListener); + imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(theImageStreamListener); + return imageWidget; + }, + imageUrl: widget.questionImgUrl, + ), + ), + if (widget.showAnnotations && widget.commentImageUrl != null) + $AnnotationImageDisplay(widget.boxHeight, widget.commentImageUrl!), + // Container( + // constraints: BoxConstraints(minHeight: widget.boxHeight), + // alignment: Alignment.center, + // child: $TheCachedNetworkImage( + // (context, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth), + // imageUrl: widget.commentImageUrl!, + // ), + // ), + RepaintBoundary( + key: widget.globalKey, + child: TheExamPaperDrawing( + imageUrl: imgUrl, + commentImageUrl: widget.commentImageUrl, + boxWidth: widget.boxWidth, + boxHeight: widget.boxHeight, + openEraser: widget.graffitiSwitch.openEraser, + showAnnotations: widget.showAnnotations && widget.commentImageUrl != null, + ), + ), + ], + ), + ); + } +} + +// CustomPaint( +// foregroundPainter: DrawingPainter( +// points: points, +// isErasing: widget.graffitiSwitch.openEraser, +// ), +// child: RepaintBoundary( +// child: Container( +// constraints: BoxConstraints(minHeight: widget.boxHeight), +// child: $TheCachedNetworkImage( +// (context, imageProvider) { +// Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); + +// imageStream?.removeListener(theImageStreamListener); +// imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(theImageStreamListener); + +// return imageWidget; +// }, +// imageUrl: widget.imgUrl, +// ), +// ), +// ), +// ), + +@hcwidget +Widget $annotationImageDisplay(WidgetRef ref, double boxHeight, String commentImageUrl) { + List datas = ref.watch(jobHomeWorkProvider); + if (datas.isNotEmpty) return Container(); + return Container( + alignment: Alignment.center, + child: $TheCachedNetworkImage( + (context, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth), + imageUrl: commentImageUrl, + ), + ); +} + +@hwidget +Widget $theCachedNetworkImage(ImageWidgetBuilder imageBuilder, {required String imageUrl}) { + UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use(); + + return CachedNetworkImage( + key: _useImgRefsh.imageKey.value, + fit: BoxFit.fitWidth, + width: double.infinity, + imageUrl: imageUrl, + imageBuilder: imageBuilder, + placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), + errorWidget: (context, url, error) { + return GestureDetector( + onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset('assets/images/test_paper_loading_failed.png'), + quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), + ], + ), + ); + }, + ); +} + +class DrawingPainter extends CustomPainter { + final List points; + final bool isErasing; + final bool openErasing; + + DrawingPainter({required this.points, required this.isErasing}) + : openErasing = points.isNotEmpty && isErasing, + super(); + + Paint paintBrush = Paint() + ..color = Colors.red + ..strokeCap = StrokeCap.round + ..strokeWidth = 1.5; + + Paint eraser = Paint() + ..blendMode = BlendMode.clear + ..color = Colors.transparent + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = 100; + + final emptyPaint = Paint(); + final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; + + @override + void paint(Canvas canvas, Size size) { + if (points.isNotEmpty) { + // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 + canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 + + canvas.drawColor(Colors.transparent, BlendMode.clear); + } + + for (int i = 0; i < points.length - 1; i++) { + GestureRecording item = points[i]; + GestureRecording nextItem = points[i + 1]; + Offset? offsetData = item.data; + Offset? nextOffsetData = nextItem.data; + if (offsetData != null && nextOffsetData != null) { + canvas.drawLine(offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); + } + } + + // 恢复画布状态. + if (points.isNotEmpty) canvas.restore(); + } + + // @override + // bool shouldRepaint(DrawingPainter oldDelegate) { + // List thePoints = oldDelegate.points; + // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; + // return thePoints != points; + // } + + @override + bool shouldRepaint(DrawingPainter oldDelegate) => true; +} + +/// 网络图和本地图切换 +@hwidget +Widget $localAndNetworkSwitch( + BuildContext context, { + required double boxWidth, + required double boxHeight, + required bool showAnnotations, + required String questionImgUrl, + required String? commentImageUrl, + required bool drawFlag, // 是否打开绘画操作 + required GlobalKey theglobalKey, + required GlobalKey<_ExamPaperDrawingState> examGlobalKey, + required AnnotationGraffitiSwitch graffitiSwitch, +}) { + UseLocalAndNetworkSwitch _useSwitch = UseLocalAndNetworkSwitch.use(); + return ExamPaperDrawing( + boxWidth: boxWidth, + boxHeight: boxHeight, + key: examGlobalKey, + globalKey: theglobalKey, + graffitiSwitch: graffitiSwitch, + points: _useSwitch.points.value, + imageLoaded: _useSwitch.imageLoaded, + pointsPureData: _useSwitch.pointsPureData.value, + decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)), + questionImgUrl: questionImgUrl, + commentImageUrl: commentImageUrl, + showAnnotations: showAnnotations, + ); +} + +class UseLocalAndNetworkSwitch { + ValueNotifier showZoomImg; + ValueNotifier temFile; + + ValueNotifier?> points; + ValueNotifier?> pointsPureData; + ValueNotifier> imageLoaded; + + UseLocalAndNetworkSwitch._({ + required this.showZoomImg, + required this.temFile, + required this.points, + required this.pointsPureData, + required this.imageLoaded, + }); + + // 工厂构造函数 + factory UseLocalAndNetworkSwitch.use([bool defaultVal = true]) { + return UseLocalAndNetworkSwitch._( + points: useState(null), + pointsPureData: useState(null), + showZoomImg: useState(defaultVal), + temFile: useState(null), + imageLoaded: useState({}), + ); + } + + Future createTempFile( + BuildContext context, { + required GlobalKey theglobalKey, + required GlobalKey<_ExamPaperDrawingState> examGlobalKey, + }) async { + Timer? _timer; + try { + _timer = Timer(Duration(seconds: 1), () { + // 执行操作的代码 + ToastUtils.showLoading(); + }); + if (examGlobalKey.currentState?.pointsPureData.isEmpty ?? true) { + try { + temFile.value?.delete(); + } catch (e) {} + temFile.value = null; + return null; + } + + RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; + if (boundary == null) return null; + ui.Image image = await boundary.toImage(pixelRatio: 3.0); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData != null) { + Directory appDocDir = await getApplicationDocumentsDirectory(); + List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + await file.writeAsBytes(bytes); // 将ByteData写入文件 + + temFile.value?.delete(); + temFile.value = file; // 保存临时文件 + + points.value = examGlobalKey.currentState?.points; + pointsPureData.value = examGlobalKey.currentState?.pointsPureData; + toPrint(val: '图片保存成功:'); + return temFile.value; + } + } catch (e) { + toPrint(val: '图片生成错误:${e}'); + toPrint(val: e.toString()); + ToastUtils.getFluttertoast(context: context, msg: '保存图片报错,请稍后重试'); + } finally { + _timer?.cancel(); + ToastUtils.dismiss(); + } + return null; + } +} + +/** 旧数据 + +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-23 11:55:16 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 17:01:15 + * @FilePath: \marking_app\lib\components\JobPictureOverview.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:ui' as ui; +import 'dart:io'; +import 'dart:async'; +import 'package:crypto/crypto.dart' as crypto; +import 'package:image/image.dart' as img; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:marking_app/common/model/job/job_note_taking_trajectory.dart'; +import 'package:marking_app/common/model/job/test_questions_image_info.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config_params.dart'; +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/components/theExamPaperDrawing.dart'; +import 'package:marking_app/pages/homework_correction/components/trajectoryView.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_provider.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_trajectory_provider.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/provider/upload_file_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'package:uuid/uuid.dart'; + +part 'jobPictureOverview.g.dart'; + +class JobPictureOverview extends StatefulHookConsumerWidget { + final int taskdetailId; + final double containerWidth; + final double containerHeight; + final List imageItems; //图片列表 + final bool annotationsFlag; + final String? questionImgUrl; + final Map commentImageMap; + + const JobPictureOverview({ + required this.taskdetailId, + required this.containerWidth, + required this.containerHeight, + required this.imageItems, + required this.annotationsFlag, + required this.commentImageMap, + this.questionImgUrl, + Key? key, + }) : super(key: key); + + @override + JobPictureOverviewState createState() => JobPictureOverviewState(); +} + +class JobPictureOverviewState extends ConsumerState with CommonMixin { + final GlobalKey theglobalKey = GlobalKey(); + final GlobalKey<_ExamPaperDrawingState> examPaperDrawingKey = GlobalKey<_ExamPaperDrawingState>(); + + final int currentIndex = 0; + late AnnotationGraffitiSwitch graffitiSwitch; + late RemoveListener _annotationsListener; // 批注关闭监听 + JobNoteTakingTrajectory? _jobNoteTakingTrajectory; + File? temFile; // 批注临时数据 + + ScrollController? _scrollController; // 滚动条 + double _scrollPosition = 0; + bool canScroll = true; + + @override + void initState() { + _scrollController = ScrollController() + ..addListener(() { + _scrollPosition = _scrollController!.position.pixels; + }); + + // 更新批注记录 + Future.delayed(Duration.zero, () { + int theTaskdetailId = ref.read(jobHomeWorkIdProvider); // 唯一的任务ID + List taskDatas = ref.read(jobHomeWorkProvider); + ref.read(jobHomeWorkIdProvider.notifier).setVal(widget.taskdetailId); + if (theTaskdetailId > 0 && theTaskdetailId != widget.taskdetailId && taskDatas.isNotEmpty) { + ref.read(jobHomeWorkProvider.notifier).setVal([]); + } + }); + _annotationsListener = ref.read(annotationGraffitiSwitchProvider.notifier).addListener((state) async { + graffitiSwitch = state; + canScroll = !state.magnifier; + if (graffitiSwitch.trajectoryDisplay) { + if (widget.questionImgUrl == null) { + // AchievementView( + // elevation: 1, + // duration: Duration(milliseconds: 800), + // title: "回显提示", + // subTitle: "没有试题图片,无法回显试题", + // color: Color.fromRGBO(46, 91, 255, 1), + // ).show(context); + ToastUtils.showInfo('没有试题图片,无法回显笔记'); + ref.read(annotationGraffitiSwitchProvider.notifier).setTrajectory(); + return; + } + JobNoteTakingTrajectory? trajectory = await toGetNoteTakingTrajectory(); + if (trajectory == null) return; + } else { + if (mounted) + Future.delayed(const Duration(seconds: 0)).then((_) { + ref.read(jobDrawingTrajectoryProvider.notifier).setVal([]); + }); + } + toUpState(setState, () {}, mounted); + }); + super.initState(); + } + + // 获取学生笔记轨迹 + Future toGetNoteTakingTrajectory() async { + if (_jobNoteTakingTrajectory != null) { + return null; + } + RestClient _client = await getClient(); + BaseStructureResult baseRes = await _client.getNoteTakingTrajectory(widget.taskdetailId); + if (baseRes.success && baseRes.data != null) { + _jobNoteTakingTrajectory = baseRes.data!; + return _jobNoteTakingTrajectory; + } + ToastUtils.showError('获取学生笔记失败,请重试'); + ref.read(annotationGraffitiSwitchProvider.notifier).setTrajectory(); + return null; + } + + @override + void dispose() { + super.dispose(); + _scrollController?.dispose(); + _annotationsListener(); + try { + if (temFile != null) { + bool flieExist = temFile!.existsSync(); + if (flieExist) temFile!.delete(); + } + } catch (e) { + print('1111111111111111111111'); + print(e); + } + } + + Future saveImage() async { + try { + ToastUtils.showLoading(); + if (temFile == null) { + if (examPaperDrawingKey.currentState?.pointsPureData.isEmpty ?? true) return null; + // 没有图片就上传图片 + RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; + + // imagInfoModel + if (boundary == null) return null; + // double dpr = MediaQuery.of(context).devicePixelRatio; + // print('设备像素比:$dpr'); + ui.Image image = await boundary.toImage(); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + /// 等于null 已经是异常了 + return null; + } + Directory appDocDir = await getApplicationDocumentsDirectory(); + List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + await file.writeAsBytes(bytes); // 将ByteData写入文件 + temFile = file; + } + TestQuestionsImageInfo? theImageInfo = examPaperDrawingKey.currentState?.imagInfoModel; + if (theImageInfo != null) { + // 剪切图片 + List? points = examPaperDrawingKey.currentState?.points; + if (points?.isNotEmpty ?? false) { + // print('开始打印......................'); + List dyArray = points! + .where((e) => !e.eraser && e.data != null) + .map((e) => double.parse(e.data!.dy.toStringAsFixed(2))) + .toList(); // 获得有效的批注数据 + if (dyArray.isNotEmpty && theImageInfo.scaleHeight != null) { + /** + * 1:图片小于视图 按照最低点和最高点剪切图片 + * 2:图片大于视图 不剪切图片 + */ + double remainingHeight = theImageInfo.boxHeight - theImageInfo.scaleHeight!; // 剩余高度 + if (remainingHeight > 20) { + // print('全部的数.............'); + // print(dyArray.toString()); + double lowestNumber = dyArray.reduce((currMin, next) => currMin < next ? currMin : next); + double highestNumber = dyArray.reduce((currMax, next) => currMax > next ? currMax : next); + + // print('最低的数: $lowestNumber'); + // print('最高的数: $highestNumber'); + + final img.Image? originalImage = img.decodeImage(temFile!.readAsBytesSync()); + if (originalImage != null) { + temFile?.delete(); // 删除以后缓存图片 + // 计算裁剪区域的像素坐标 + // 裁剪图像 + + double imageStartPositionY = remainingHeight / 2; // 图片起始Y轴位置 + double imageEndPositionY = imageStartPositionY + theImageInfo.scaleHeight!; // 图片结束Y轴位置 + int startY = imageStartPositionY.toInt(); + if (lowestNumber < imageStartPositionY) { + // 画图最小坐标小于图片 (向上操作了图片) + startY = lowestNumber.toInt(); + } + + int endY = (highestNumber - startY).toInt(); // 超出 + endY = endY + 10 > theImageInfo.boxHeight ? endY : endY + 10; + + if (highestNumber < imageEndPositionY) { + endY = (imageEndPositionY - startY).toInt(); + endY = endY + 10 > theImageInfo.scaleHeight! ? endY : endY + 10; + } + + final img.Image clippedImage = img.copyCrop( + originalImage, + x: 0, + y: startY, + height: endY, + width: originalImage.width, + ); + + Directory appDocDir = await getApplicationDocumentsDirectory(); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + temFile = await file.writeAsBytes(img.encodePng(clippedImage)); + } + } + } + } + } + /** */ + crypto.Digest fileMd5 = crypto.md5.convert(await temFile!.readAsBytes()); + String? fileName = temFile?.path.split('/').last; + // print('获得文件名'); // 打印文件名 + // print(fileName); // 打印文件名 + + RestClient _client = await getClient(); + BaseStructureResult resUploadConfig = + await _client.getUploadFile(UploadFileInterfaceConfigParams( + fileName: fileName ?? '1.png', + fileMd5: fileMd5.toString(), + contentLength: temFile!.lengthSync(), + )); + if (!resUploadConfig.success || resUploadConfig.data == null) { + ToastUtils.getFluttertoast(msg: '获取图片上传配置失败', context: context); + return null; + } + if (resUploadConfig.data!.uploadUri == null) { + return FileResult(myObject: '', success: true, url: resUploadConfig.data!.downloadUri); + } + FileResult? resFile = + await ref.read(uploadFileProvider.notifier).getUploadFileConfig(resUploadConfig.data!, temFile!); + // FileResult? resFile = await ref + // .read(uploadFileProvider.notifier) + // .uploadFile(temFile!.path, widget.imageItems[currentIndex], ref.read(userProvider).id.toString()); + if (resFile != null && resFile.success) { + resFile.otherParam = currentIndex; + return resFile; + } + } catch (e) { + toPrint(val: e.toString()); + } finally { + // ToastUtils.dismiss(); + } + return null; + } + + @override + Widget build(BuildContext context) { + if (graffitiSwitch.trajectoryDisplay && widget.questionImgUrl != null && _jobNoteTakingTrajectory != null) + return TrajectoryView( + imageUrl: widget.questionImgUrl!, + boxWidth: widget.containerWidth, + boxHeight: widget.containerHeight, + trajectory: _jobNoteTakingTrajectory!, + // imageUrl: 'https://cdn9-banquan.ituchong.com/weili/image/l/903493495151657025.jpeg', + // imageUrl: 'https://cdn6-banquan.ituchong.com/weili/image/l/903121241746964534.jpeg', + ); + + return Container( + margin: EdgeInsets.only(top: 1.h), + width: widget.containerWidth, + height: widget.containerHeight, + child: Scrollbar( + // thumbVisibility: true, + controller: _scrollController, + child: SingleChildScrollView( + physics: canScroll ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only(right: 1.w, left: 1.w), + scrollDirection: Axis.vertical, // 设置垂直滚动 + child: $LocalAndNetworkSwitch( + boxWidth: widget.containerWidth, + boxHeight: widget.containerHeight, + theglobalKey: theglobalKey, + graffitiSwitch: graffitiSwitch, + drawFlag: widget.annotationsFlag, + examGlobalKey: examPaperDrawingKey, + imageUrl: widget.commentImageMap[widget.imageItems[currentIndex]]!, + ), + ), + ), + ); + } +} + +// 试卷绘制 +class ExamPaperDrawing extends StatefulHookConsumerWidget { + double boxWidth; + double boxHeight; + String imgUrl; + BoxDecoration? decoration; + AnnotationGraffitiSwitch graffitiSwitch; + List? points; + List? pointsPureData; + ValueNotifier> imageLoaded; + + GlobalKey globalKey; + // Function(String) imageCall; + ExamPaperDrawing({ + required this.boxWidth, + required this.boxHeight, + required this.imgUrl, + required this.points, + required this.pointsPureData, + required this.graffitiSwitch, + required this.globalKey, + required this.imageLoaded, + this.decoration, + Key? key, + }) : super(key: key); + + @override + _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); +} + +class _ExamPaperDrawingState extends ConsumerState + with EventBusMixin { + ImageStream? imageStream; // 图片监听数据 + TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 + late ImageStreamListener theImageStreamListener; + // 用于记录手指位置的变量 + late List points; + late List pointsPureData; + // 用于记录绘图结果的变量 + bool _isEraserPressed = false; // 橡皮擦按下 + Offset? _eraserPosition; // 按下位置 + Offset? globalPosition = null; // 是否正在绘制 + + int activePointers = 0; + + @override + void initState() { + points = widget.points ?? []; + pointsPureData = widget.pointsPureData ?? []; + // 事件总线监听 + eventOn(callback: (BottomAnnotationSwitchCleanall item) { + if (item.previousStep) { + if (points.isEmpty) { + ToastUtils.showInfo('批注已清空'); + return; + } + var index = pointsPureData.toList().lastIndexOf(null); + if (index != -1) { + if (index + 1 == pointsPureData.length) { + pointsPureData = pointsPureData.sublist(0, index); + points.sublist(0, index); + index = pointsPureData.toList().lastIndexOf(null); + index == -1 ? -1 : index + 1; + } + if (index != -1) { + pointsPureData = pointsPureData.sublist(0, index); + points = points.sublist(0, index); + ref.read(jobHomeWorkProvider.notifier).setVal(points); + // toUpState(setState, () {}, mounted); + } else { + item.cleanAll = true; + } + } else { + item.cleanAll = true; + } + } + + if (item.cleanAll) { + pointsPureData.clear(); + points.clear(); + toUpState(setState, () {}, mounted); + } + + if (item.uploadImage) { + // 图片确认按钮,生成 + if (pointsPureData.isEmpty) { + ToastUtils.showInfo('请先批注再提交'); + return; + } + } + }); + + theImageStreamListener = ImageStreamListener((ImageInfo info, bool _) { + // 获取图片的宽高 + imagInfoModel = TestQuestionsImageInfo( + boxHeight: widget.boxHeight, + boxWidth: widget.boxWidth, + url: widget.imgUrl, + height: info.image.height.toDouble(), + width: info.image.width.toDouble(), + ); + }); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + try { + imageStream?.removeListener(theImageStreamListener); + } catch (e) {} + eventCancel(); + } + + @override + Widget build(BuildContext context) { + return Listener( + onPointerDown: (PointerDownEvent event) { + // 判断当前是否已经有触摸点,如果有则忽略该触摸事件 + + // 处理单个触摸点按下的逻辑 + activePointers++; + print('按下:$activePointers'); + }, + onPointerMove: (PointerMoveEvent event) { + try { + if (activePointers != 1) return; + + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + Offset localPosition = event.localPosition; + pointsPureData = List.from(pointsPureData)..add(localPosition); + points = List.from(points) + ..add(GestureRecording(eraser: widget.graffitiSwitch.openEraser, data: localPosition)); + _eraserPosition = localPosition; + _isEraserPressed = true; + // setState(() {}); + ref.read(jobHomeWorkProvider.notifier).setVal(points); + } + } catch (e) { + toPrint(val: '进入报错'); + } + }, + onPointerUp: (PointerUpEvent event) { + // 处理单个触摸点抬起的逻辑 + activePointers--; + print('抬起:$activePointers'); + globalPosition = null; + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + pointsPureData.add(null); // 增加空点以分隔不同的线段 + points.add(GestureRecording(eraser: widget.graffitiSwitch.openEraser)); + _isEraserPressed = false; + _eraserPosition = null; + ref.read(jobHomeWorkProvider.notifier).setVal(points); + } + }, + child: RepaintBoundary( + key: widget.globalKey, + child: TheExamPaperDrawing( + imageUrl: widget.imgUrl, + boxHeight: widget.boxHeight, + openEraser: widget.graffitiSwitch.openEraser, + imageBuilder: (context, imageProvider) { + Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); + imageStream?.removeListener(theImageStreamListener); + imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(theImageStreamListener); + return imageWidget; + }, + ) + // CustomPaint( + // foregroundPainter: DrawingPainter( + // points: points, + // isErasing: widget.graffitiSwitch.openEraser, + // ), + // child: RepaintBoundary( + // child: Container( + // constraints: BoxConstraints(minHeight: widget.boxHeight), + // child: $TheCachedNetworkImage( + // (context, imageProvider) { + // Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); + + // imageStream?.removeListener(theImageStreamListener); + // imageStream = imageWidget.image.resolve(ImageConfiguration())..addListener(theImageStreamListener); + + // return imageWidget; + // }, + // imageUrl: widget.imgUrl, + // ), + // ), + // ), + // ), + ), + ); + } +} + +@hwidget +Widget $theCachedNetworkImage(ImageWidgetBuilder imageBuilder, {required String imageUrl}) { + UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use(); + + return CachedNetworkImage( + key: _useImgRefsh.imageKey.value, + fit: BoxFit.fitWidth, + width: double.infinity, + imageUrl: imageUrl, + imageBuilder: imageBuilder, + placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), + errorWidget: (context, url, error) { + return GestureDetector( + onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset('assets/images/test_paper_loading_failed.png'), + quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), + ], + ), + ); + }, + ); +} + +class DrawingPainter extends CustomPainter { + final List points; + final bool isErasing; + final bool openErasing; + + DrawingPainter({required this.points, required this.isErasing}) + : openErasing = points.isNotEmpty && isErasing, + super(); + + Paint paintBrush = Paint() + ..color = Colors.red + ..strokeCap = StrokeCap.round + ..strokeWidth = 1.5; + + Paint eraser = Paint() + ..blendMode = BlendMode.clear + ..color = Colors.transparent + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = 100; + + final emptyPaint = Paint(); + final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; + + @override + void paint(Canvas canvas, Size size) { + if (points.isNotEmpty) { + // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 + canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 + + canvas.drawColor(Colors.transparent, BlendMode.clear); + } + + for (int i = 0; i < points.length - 1; i++) { + GestureRecording item = points[i]; + GestureRecording nextItem = points[i + 1]; + Offset? offsetData = item.data; + Offset? nextOffsetData = nextItem.data; + if (offsetData != null && nextOffsetData != null) { + canvas.drawLine(offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); + } + } + + // 恢复画布状态. + if (points.isNotEmpty) canvas.restore(); + } + + // @override + // bool shouldRepaint(DrawingPainter oldDelegate) { + // List thePoints = oldDelegate.points; + // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; + // return thePoints != points; + // } + + @override + bool shouldRepaint(DrawingPainter oldDelegate) => true; +} + +/// 网络图和本地图切换 +@hwidget +Widget $localAndNetworkSwitch( + BuildContext context, { + required double boxWidth, + required double boxHeight, + required String imageUrl, // 图片 + required bool drawFlag, // 是否打开绘画操作 + required GlobalKey theglobalKey, + required GlobalKey<_ExamPaperDrawingState> examGlobalKey, + required AnnotationGraffitiSwitch graffitiSwitch, +}) { + UseLocalAndNetworkSwitch _useSwitch = UseLocalAndNetworkSwitch.use(); + print('localAndNetworkSwitch..........build..............'); + return ExamPaperDrawing( + boxWidth: boxWidth, + boxHeight: boxHeight, + imgUrl: imageUrl, + key: examGlobalKey, + globalKey: theglobalKey, + graffitiSwitch: graffitiSwitch, + points: _useSwitch.points.value, + imageLoaded: _useSwitch.imageLoaded, + pointsPureData: _useSwitch.pointsPureData.value, + decoration: const BoxDecoration(color: const Color.fromRGBO(249, 250, 254, 1)), + ); +} + +class UseLocalAndNetworkSwitch { + ValueNotifier showZoomImg; + ValueNotifier temFile; + + ValueNotifier?> points; + ValueNotifier?> pointsPureData; + ValueNotifier> imageLoaded; + + UseLocalAndNetworkSwitch._({ + required this.showZoomImg, + required this.temFile, + required this.points, + required this.pointsPureData, + required this.imageLoaded, + }); + + // 工厂构造函数 + factory UseLocalAndNetworkSwitch.use([bool defaultVal = true]) { + return UseLocalAndNetworkSwitch._( + points: useState(null), + pointsPureData: useState(null), + showZoomImg: useState(defaultVal), + temFile: useState(null), + imageLoaded: useState({}), + ); + } + + Future createTempFile( + BuildContext context, { + required GlobalKey theglobalKey, + required GlobalKey<_ExamPaperDrawingState> examGlobalKey, + }) async { + Timer? _timer; + try { + _timer = Timer(Duration(seconds: 1), () { + // 执行操作的代码 + ToastUtils.showLoading(); + }); + if (examGlobalKey.currentState?.pointsPureData.isEmpty ?? true) { + try { + temFile.value?.delete(); + } catch (e) {} + temFile.value = null; + return null; + } + + RenderRepaintBoundary? boundary = theglobalKey.currentContext!.findRenderObject() as RenderRepaintBoundary?; + if (boundary == null) return null; + ui.Image image = await boundary.toImage(pixelRatio: 3.0); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData != null) { + Directory appDocDir = await getApplicationDocumentsDirectory(); + List bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); + String filePath = '${appDocDir.path}/${Uuid().v1()}.png'; // 文件路径及名称 + File file = File(filePath); // 创建文件对象 + await file.writeAsBytes(bytes); // 将ByteData写入文件 + + temFile.value?.delete(); + temFile.value = file; // 保存临时文件 + + points.value = examGlobalKey.currentState?.points; + pointsPureData.value = examGlobalKey.currentState?.pointsPureData; + toPrint(val: '图片保存成功:'); + return temFile.value; + } + } catch (e) { + toPrint(val: '图片生成错误:${e}'); + toPrint(val: e.toString()); + ToastUtils.getFluttertoast(context: context, msg: '保存图片报错,请稍后重试'); + } finally { + _timer?.cancel(); + ToastUtils.dismiss(); + } + return null; + } +} + + + +*/ diff --git a/marking_app/lib/pages/homework_correction/components/jobPictureOverviewDrawing.dart b/marking_app/lib/pages/homework_correction/components/jobPictureOverviewDrawing.dart new file mode 100644 index 0000000..6e85f9f --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/jobPictureOverviewDrawing.dart @@ -0,0 +1,338 @@ +// 试卷绘制 +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/components/jobPictureOverview.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/utils/index.dart'; + +// 试卷绘制 +class ExamPaperDrawing extends StatefulHookConsumerWidget { + String imgUrl; + bool homework; + BoxDecoration? decoration; + AnnotationGraffitiSwitch graffitiSwitch; + List? points; + List? pointsPureData; + ValueNotifier> imageLoaded; + + GlobalKey globalKey; + // Function(String) imageCall; + ExamPaperDrawing({ + required this.imgUrl, + required this.homework, + required this.points, + required this.pointsPureData, + required this.graffitiSwitch, + required this.globalKey, + required this.imageLoaded, + this.decoration, + Key? key, + }) : super(key: key); + + @override + _ExamPaperDrawingState createState() => _ExamPaperDrawingState(); +} + +class _ExamPaperDrawingState extends ConsumerState + with EventBusMixin { + late Future _future; // 考试试卷 + + // 用于记录手指位置的变量 + late List points; + late List pointsPureData; + // 用于记录绘图结果的变量 + bool _isEraserPressed = false; // 橡皮擦按下 + Offset? _eraserPosition; // 按下位置 + Offset? globalPosition = null; // 是否正在绘制 + + Future loadImage(String url) async { + try { + Map map = widget.imageLoaded.value; + ui.Image? image = map[url]; + if (image != null) { + return image; + } + final httpClient = HttpClient(); + final request = await httpClient.getUrl(Uri.parse(url)); + final response = await request.close(); + final bytes = await consolidateHttpClientResponseBytes(response); + final codec = await ui.instantiateImageCodec(bytes); + final frame = await codec.getNextFrame(); + ui.Image theImage = frame.image; + map[url] = theImage; + return theImage; + } catch (e) { + print('请求图片报错:${e.toString()}'); + } + return null; + } + + // void _onPointerDown(DragDownDetails details) { + // if (widget.graffitiSwitch.openEraser) { + // _eraserPosition = (context.findRenderObject() as RenderBox).globalToLocal(details.globalPosition); + // _isEraserPressed = true; + // toUpState(setState, ()=>{}, mounted); + // } + // } + + @override + void initState() { + points = widget.points ?? []; + pointsPureData = widget.pointsPureData ?? []; + print('图片地址:${widget.imgUrl}'); + _future = loadImage(widget.imgUrl); + // 事件总线监听 + eventOn(callback: (BottomAnnotationSwitchCleanall item) { + if (item.previousStep) { + if (points.isEmpty) { + ToastUtils.showInfo('批注已清空'); + return; + } + var index = pointsPureData.toList().lastIndexOf(null); + if (index != -1) { + if (index + 1 == pointsPureData.length) { + pointsPureData = pointsPureData.sublist(0, index); + points.sublist(0, index); + index = pointsPureData.toList().lastIndexOf(null); + index == -1 ? -1 : index + 1; + } + if (index != -1) { + pointsPureData = pointsPureData.sublist(0, index); + points = points.sublist(0, index); + toUpState(setState, () {}, mounted); + } else { + item.cleanAll = true; + } + } else { + item.cleanAll = true; + } + } + + if (item.cleanAll) { + pointsPureData.clear(); + points.clear(); + toUpState(setState, () {}, mounted); + } + + if (item.uploadImage) { + // 图片确认按钮,生成 + if (pointsPureData.isEmpty) { + ToastUtils.showInfo('请先批注再提交'); + return; + } + // _saveImage().then((FileResult? res) { + // if (res != null) { + // widget.imageCall(res.url!); + // } + // }); + } + }); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + eventCancel(); + } + + @override + Widget build(BuildContext context) { + return MyFutureBuilder.buildFutureBuilderOfSingleInstance( + context, + _future, + (ui.Image? theImage) { + if (theImage == null) return const Center(child: Text('图片加载错误')); + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final double containerWidth = constraints.maxWidth; // 展示区域总宽度 + final double containerHeight = constraints.maxHeight; // 展示区域总宽度 + + final double imageWidth = theImage.width.toDouble(); // 图片原始宽度 + final double imageHeight = theImage.height.toDouble(); // 图片原始高度 + final double widthRatio = containerWidth / imageWidth; // + final double heightRatio = containerHeight / imageHeight; + final double scale = widthRatio > heightRatio ? heightRatio : widthRatio; + final double destWidth = imageWidth * scale; + final double destHeight = imageHeight * scale; + + final bool homework = widget.homework; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onPanUpdate: (DragUpdateDetails details) { + if (globalPosition != null) { + // 预防双指同时作用于屏幕 + double dx = globalPosition!.dx; + double dy = globalPosition!.dy; + + double dxNew = details.globalPosition.dx; + double dyNew = details.globalPosition.dy; + if ((dxNew - dx).abs() > 22 || (dyNew - dy).abs() > 22) { + return; + } + } + globalPosition = details.globalPosition; + try { + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + RenderBox renderBox = context.findRenderObject() as RenderBox; + Offset localPosition = renderBox.globalToLocal(details.globalPosition); + pointsPureData = List.from(pointsPureData)..add(localPosition); + bool annotationSwitch = ref.read(annotationGraffitiSwitchProvider).annotationSwitch; // 底部开关是否打开 + points = List.from(points) + ..add(GestureRecording( + eraser: widget.graffitiSwitch.openEraser, + data: localPosition, + annotationSwitch: annotationSwitch)); + _eraserPosition = localPosition; + _isEraserPressed = true; + setState(() {}); + } + } catch (e) { + toPrint(val: '进入报错'); + } + }, + onPanEnd: (DragEndDetails details) { + print('离开.............'); + globalPosition = null; + if (widget.graffitiSwitch.openBrush || widget.graffitiSwitch.openEraser) { + pointsPureData.add(null); // 增加空点以分隔不同的线段 + bool annotationSwitch = ref.read(annotationGraffitiSwitchProvider).annotationSwitch; // 底部开关是否打开 + points.add( + GestureRecording(eraser: widget.graffitiSwitch.openEraser, annotationSwitch: annotationSwitch)); + _isEraserPressed = false; + _eraserPosition = null; + } + }, + child: RepaintBoundary( + key: widget.globalKey, + child: CustomPaint( + isComplex: true, + willChange: true, + painter: DrawingPainter( + image: theImage, + points: points, + isErasing: widget.graffitiSwitch.openEraser, + destWidth: destWidth, + destHeight: destHeight, + imageWidth: imageWidth, + imageHeight: imageHeight, + homework: homework, + containerWidth: containerWidth, + containerHeight: containerHeight, + ), + // size: Size(homework ? containerWidth : destWidth, homework ? containerHeight : destHeight), + size: Size(containerWidth, containerHeight), + ), + ), + ); + }, + ); + }, + ); + } +} + +class DrawingPainter extends CustomPainter { + final List points; + final bool isErasing; + final ui.Image image; + final bool homework; + final double containerWidth; + final double containerHeight; + double destWidth; + double destHeight; + final double imageWidth; + final double imageHeight; + + // final Rect destRect; + // final Rect srcRect; + final bool openErasing; + DrawingPainter({ + required this.homework, + required this.points, + required this.isErasing, + required this.image, + required this.destWidth, + required this.destHeight, + required this.containerWidth, + required this.containerHeight, + required this.imageWidth, + required this.imageHeight, + }) : + // destRect = Rect.fromLTWH(0, 0,destWidth,destHeight), + // srcRect = Rect.fromLTWH(0, 0, imageWidth, imageHeight), + openErasing = points.isNotEmpty && isErasing, + super(); + + Paint paintBrush = Paint() + ..color = Colors.red + ..strokeCap = StrokeCap.round + ..strokeWidth = 1.5; + + Paint eraser = Paint() + ..blendMode = BlendMode.clear + ..color = Colors.transparent + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = 100; + + final emptyPaint = Paint(); + final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; + + @override + void paint(Canvas canvas, Size size) { + double offsetX = (size.width - destWidth) / 2; + double offsetY = (size.height - destHeight) / 2; + if (destWidth < (size.width / 2)) { + destWidth = size.width / 2; + offsetX = (size.width - destWidth) / 2; + } + + // final rect = Rect.fromCenter(center: center, width: destWidth, height: destHeight); + Rect srcRect = Rect.fromLTRB(0, 0, image.width.toDouble(), image.height.toDouble()); + // Rect destRect = Rect.fromLTRB(offsetX, offsetY, containerWidth, containerHeight); + Rect destRect = Offset(offsetX, offsetY) & Size(destWidth, destHeight); + + canvas.drawImageRect(image, srcRect, destRect, emptyPaint); + + // canvas.drawImage(image, Offset.zero, emptyPaint); + if (points.isNotEmpty) { + // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 + canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 + + canvas.drawColor(Colors.transparent, BlendMode.clear); + } + + for (int i = 0; i < points.length - 1; i++) { + GestureRecording item = points[i]; + GestureRecording nextItem = points[i + 1]; + Offset? offsetData = item.data; + Offset? nextOffsetData = nextItem.data; + if (offsetData != null && nextOffsetData != null) { + canvas.drawLine(offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); + } + } + + // 恢复画布状态. + if (points.isNotEmpty) canvas.restore(); + } + + // @override + // bool shouldRepaint(DrawingPainter oldDelegate) { + // List thePoints = oldDelegate.points; + // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; + // return thePoints != points; + // } + + @override + bool shouldRepaint(DrawingPainter oldDelegate) => true; +} diff --git a/marking_app/lib/pages/homework_correction/components/keyboardBox.dart b/marking_app/lib/pages/homework_correction/components/keyboardBox.dart new file mode 100644 index 0000000..a4a13a7 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/keyboardBox.dart @@ -0,0 +1,546 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +// import 'package:marking_app/common/model/enum/KeyboardType.dart'; +// import 'package:marking_app/common/model/event_bus/job_large_question_scoring_bus.dart'; +// import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +// import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +// import 'package:marking_app/components/PictureOverview.dart'; +// import 'package:marking_app/pages/common/event_bus_mixin.dart'; +// import 'package:marking_app/pages/homework_correction/components/jobPictureOverview.dart'; +// import 'package:marking_app/pages/homework_correction/hooks/use_data.dart'; +// import 'package:marking_app/pages/homework_correction/hooks/use_keyboard.dart'; +// import 'package:marking_app/utils/index.dart'; +// import 'package:marking_app/utils/my_text.dart'; + +// part 'keyboardBox.g.dart'; + +// // 键盘 +// class KeyboardBox extends HookWidget with EventBusMixin { +// final UseData useData; +// final DoMarkingKeyboardModel preferenceModel; +// final GlobalKey scaffoldKeyPictureOverview; +// KeyboardBox( +// {required this.scaffoldKeyPictureOverview, required this.preferenceModel, required this.useData, Key? key}) +// : super(key: key); + +// @override +// Widget build(BuildContext context) { +// UseKeyboard _useKeyboard = UseKeyboard.use(preferenceModel); +// MarkingTextQuestionJob job = useData.textQuestionState.value!; +// KeyboardType keyboard = preferenceModel.keyboard; +// bool _openAuxiliary = preferenceModel.openAuxiliary; +// SortKeyboard sort = preferenceModel.sort!; +// AnimationController controller = useAnimationController( +// initialValue: _openAuxiliary ? 144 : 72, // 设置默认值 +// duration: const Duration(milliseconds: 400), +// lowerBound: 72, +// upperBound: 144, +// ); +// useValueChanged(preferenceModel, (oldValue, oldResult) { +// _useKeyboard.init( +// job: job, +// sort: preferenceModel.sort!, +// localScoreInterval: preferenceModel.getScoreStepSize(useData.taskId, job.questionNum, 1), +// ); +// }); + +// useEffect(() { +// _useKeyboard.init( +// job: job, +// sort: preferenceModel.sort!, +// localScoreInterval: preferenceModel.getScoreStepSize(useData.taskId, job.questionNum, 1), +// ); + +// return () { +// eventCancel(); +// _useKeyboard.disBus(); +// }; +// }, []); + +// bool horizontal = preferenceModel.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 屏幕方向 +// double animationVal = controller.value.h; + +// // 已经选中辅助键盘的分数,扣除十位分数剩余分数差值 +// double dValue = _useKeyboard.tensVal.value != null +// ? job.totalScore - _useKeyboard.tensVal.value!.toDouble() +// : job.totalScore; // 差值 + +// return KeyBoardMainBox( +// call: (num? value) { +// eventFire( +// model: JobLargeQuestionScoringBus( +// ratingScore: value?.toDouble(), +// ), +// ); +// }, +// scaffoldKeyPictureOverview: scaffoldKeyPictureOverview, +// animationVal: animationVal, +// useKeyboard: _useKeyboard, +// dValue: dValue, +// horizontal: horizontal, +// job: job, +// keyboard: keyboard, +// openAuxiliary: _openAuxiliary, +// sort: sort, +// ); +// } +// } + +// class KeyBoardMainBox extends ConsumerWidget { +// final KeyboardType keyboard; +// final MarkingTextQuestionJob job; +// final SortKeyboard sort; +// final bool horizontal; +// final double animationVal; +// final UseKeyboard useKeyboard; +// final double dValue; +// final bool openAuxiliary; +// final GlobalKey scaffoldKeyPictureOverview; +// final Function(num?) call; + +// final Duration easyThrottleTime = const Duration(milliseconds: 1000); +// const KeyBoardMainBox({ +// required this.scaffoldKeyPictureOverview, +// required this.horizontal, +// required this.animationVal, +// required this.keyboard, +// required this.job, +// required this.sort, +// required this.useKeyboard, +// required this.dValue, +// required this.openAuxiliary, +// required this.call, +// super.key, +// }); + +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// return keyboard == KeyboardType.RIGHT_SELECTION +// ? $RightKeyboard( +// job: job, +// tens: [], +// sort: sort, +// horizontal: horizontal, +// animationVal: animationVal, +// easyThrottleTime: easyThrottleTime, +// combineList: useKeyboard.combineList.value, +// dValue: dValue, +// scoring: (fraction, isTens) { +// useKeyboard +// .readOver( +// scaffoldKeyPictureOverview: scaffoldKeyPictureOverview, +// context: context, +// fraction: fraction, +// job: job) +// .then((value) { +// // ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); +// call(value); +// }); +// }, +// openAuxiliary: openAuxiliary, +// singleDigitList: useKeyboard.singleDigitList.value, +// smallScoreVal: useKeyboard.smallScoreVal.value, +// tensVal: useKeyboard.tensVal.value, +// ) +// : $BottomKeyboard( +// combineList: useKeyboard.combineList.value, +// currentScore: useKeyboard.score, +// easyThrottleTime: easyThrottleTime, +// horizontal: horizontal, +// job: job, +// scoring: (fraction, isTens) { +// useKeyboard +// .readOver( +// scaffoldKeyPictureOverview: scaffoldKeyPictureOverview, +// context: context, +// fraction: fraction, +// job: job) +// .then((value) { +// // ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); +// call(value); +// // eventFire( +// // model: JobLargeQuestionScoringBus( +// // ratingScore: value?.toDouble(), +// // ), +// // ); +// }); +// }, +// sort: sort, +// ); +// } +// } + +// @swidget +// Widget $bottomKeyboard( +// BuildContext context, { +// required SortKeyboard sort, +// required List combineList, +// required bool horizontal, +// required MarkingTextQuestionJob job, +// required Duration easyThrottleTime, +// required num? currentScore, // 当前已经得分 +// required Function(num fraction, bool? isTens) scoring, +// }) { +// return Container( +// height: 62.h, +// width: double.infinity, +// padding: EdgeInsets.only(left: 4.h, right: 4.h), +// decoration: BoxDecoration( +// boxShadow: [ +// BoxShadow( +// color: const Color.fromRGBO(46, 91, 255, 0.2), +// offset: Offset(26.h, 0), //阴影y轴偏移量 +// blurRadius: 1, //阴影模糊程度 +// spreadRadius: 0.4, //阴影扩散程度 +// ) +// ], +// color: Colors.white, +// ), +// child: Row( +// children: [ +// Expanded( +// child: Container( +// padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 4.h), +// color: const Color.fromRGBO(255, 255, 255, 1), +// child: ListView( +// scrollDirection: Axis.horizontal, +// children: [ +// if (sort == SortKeyboard.FULL_AND_AERO_TOP) +// InkWell( +// onTap: () { +// easyThrottle('toMarkingVal', () => scoring(job.totalScore, false), duration: easyThrottleTime); +// }, +// child: Container( +// width: horizontal ? 28.w : 52.w, +// height: double.infinity, +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// color: const Color.fromRGBO(249, 250, 254, 1), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ...combineList.map((e) { +// String strScore = getDoubleRemoveZero(e); +// bool isCurrentSelect = currentScore == e; // 当前选中 + +// return InkWell( +// onTap: () { +// easyThrottle('toMarkingVal', () => scoring(e, false), duration: easyThrottleTime); +// }, +// child: Container( +// width: horizontal ? 28.w : 52.w, +// height: double.infinity, +// margin: EdgeInsets.only(left: 3.h), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// color: +// isCurrentSelect ? Theme.of(context).primaryColor : const Color.fromRGBO(249, 250, 254, 1), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText( +// strScore == '0' ? '零分' : strScore, +// size: 15.sp, +// color: isCurrentSelect ? Colors.white : const Color.fromRGBO(148, 163, 182, 1), +// ), +// ), +// ); +// }).toList(), +// if (sort != SortKeyboard.FULL_AND_AERO_TOP) +// InkWell( +// onTap: () { +// easyThrottle('toMarkingVal', () => scoring(job.totalScore, true), duration: easyThrottleTime); +// }, +// child: Container( +// width: horizontal ? 28.w : 52.w, +// height: double.infinity, +// margin: EdgeInsets.only(left: 3.h), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// color: const Color.fromRGBO(249, 250, 254, 1), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ], +// ), +// ), +// ) +// ], +// ), +// ); +// } + +// @swidget +// Widget $rightKeyboard( +// BuildContext context, { +// required MarkingTextQuestionJob job, +// required bool horizontal, +// required double animationVal, +// required Duration easyThrottleTime, +// required List tens, +// required List combineList, +// required List singleDigitList, +// required SortKeyboard sort, +// required bool openAuxiliary, +// required num? tensVal, +// required String? smallScoreVal, +// required double dValue, +// required Function(num fraction, bool? isTens) scoring, +// }) { +// return Container( +// width: horizontal ? animationVal : animationVal - 16.h, +// margin: EdgeInsets.only(top: 0.6.w), +// child: Row( +// children: [ +// // 辅助栏 +// SizedBox( +// width: animationVal - (horizontal ? 72.h : 72.h), +// child: Column(mainAxisSize: MainAxisSize.min, children: [ +// GestureDetector( +// onTap: () => +// easyThrottle('toMarkingVal', () => scoring(job.totalScore, false), duration: easyThrottleTime), +// child: Container( +// height: 58.h, +// alignment: Alignment.center, +// decoration: BoxDecoration( +// color: const Color.fromRGBO(4, 201, 208, 1), +// borderRadius: BorderRadius.all(Radius.circular(1.w)), +// ), +// child: quickText( +// '满分', +// size: 17.sp, +// fontWeight: FontWeight.w400, +// color: const Color.fromRGBO(255, 255, 255, 1), +// ), +// ), +// ), +// ...tens.map((e) { +// return InkWell( +// onTap: () { +// easyThrottle( +// 'toMarkingVal', +// () => scoring(e, true), +// // () { +// // _useKeyboard.readOver(fraction: e, isTens: true, job: job).then( +// // (value) => eventFire( +// // model: JobLargeQuestionScoringBus(ratingScore: value?.toDouble()), +// // ), +// // ); +// // }, +// duration: easyThrottleTime, +// ); +// }, +// child: Container( +// height: 58.h, +// margin: EdgeInsets.only(top: 1.w), +// alignment: Alignment.center, +// decoration: BoxDecoration( +// color: tensVal == e ? Colors.amber[800] : Theme.of(context).primaryColor, +// borderRadius: BorderRadius.all(Radius.circular(1.w)), +// ), +// child: quickText( +// '$e+', +// size: 17.sp, +// fontWeight: FontWeight.w500, +// color: const Color.fromRGBO(255, 255, 255, 1), +// ), +// ), +// ); +// }).toList() +// ]), +// ), +// // 小分栏 +// Expanded( +// child: Container( +// width: horizontal ? 72.h : 60.h, +// // width: 72.h, +// padding: EdgeInsets.symmetric(horizontal: 4.h, vertical: 4.h), +// decoration: BoxDecoration( +// boxShadow: [ +// BoxShadow( +// color: const Color.fromRGBO(46, 91, 255, 0.2), +// offset: Offset(0, 8.w), //阴影y轴偏移量 +// blurRadius: 1, //阴影模糊程度 +// spreadRadius: 2, //阴影扩散程度 +// ) +// ], +// color: const Color.fromRGBO(255, 255, 255, 1), +// ), +// child: ListView( +// children: [ +// if ([SortKeyboard.FULL_AND_AERO_TOP, SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER].contains(sort)) +// Material( +// child: Ink( +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// color: const Color.fromRGBO(249, 250, 254, 1), +// ), +// child: InkWell( +// splashColor: Theme.of(context).primaryColor, +// onTap: () { +// easyThrottle( +// 'toMarkingVal', +// () => scoring(job.totalScore, true), +// // () { +// // _useKeyboard.readOver(fraction: job.totalScore, isTens: true, job: job).then( +// // (value) => eventFire( +// // model: JobLargeQuestionScoringBus(ratingScore: value?.toDouble()), +// // ), +// // ); +// // }, +// duration: easyThrottleTime, +// ); +// }, +// child: Container( +// width: double.infinity, +// height: 56.h, +// alignment: Alignment.center, +// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ), +// ), +// if (SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER == sort) +// Material( +// child: Ink( +// padding: EdgeInsets.only(top: 3.h), +// decoration: BoxDecoration( +// color: const Color.fromRGBO(249, 250, 254, 1), +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: InkWell( +// splashColor: Theme.of(context).primaryColor, +// onTap: () => easyThrottle('toMarkingVal', () => scoring(job.totalScore, true), +// duration: easyThrottleTime), +// child: Container( +// width: double.infinity, +// height: 56.h, +// alignment: Alignment.center, +// child: quickText('零分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ), +// ), +// ...(!openAuxiliary && job.totalScore > 10 ? combineList : singleDigitList).map((e) { +// String strScore = getDoubleRemoveZero(e); +// bool isCurrentSelect = smallScoreVal == strScore; // 当前选中 + +// if (e == 0 && SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER == sort) { +// return Container(); +// } +// bool effective = e <= dValue; + +// return Padding( +// padding: EdgeInsets.only(top: 3.h), +// child: Material( +// child: Ink( +// decoration: BoxDecoration( +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// color: isCurrentSelect +// ? Theme.of(context).primaryColor +// : effective +// ? const Color.fromRGBO(249, 250, 254, 1) +// : Colors.grey[300], +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// width: double.infinity, +// height: 56.h, +// child: InkWell( +// splashColor: Theme.of(context).primaryColor, +// onTap: effective +// ? () { +// easyThrottle('toMarkingVal', () => scoring(e, false), +// // () { +// // _useKeyboard.readOver(fraction: e, job: job).then( +// // (value) => eventFire( +// // model: JobLargeQuestionScoringBus(ratingScore: value?.toDouble()), +// // ), +// // ); +// // }, +// duration: easyThrottleTime); +// } +// : null, +// child: Container( +// width: double.infinity, +// height: 56.h, +// alignment: Alignment.center, +// child: quickText( +// strScore == '0' ? '零分' : strScore, +// size: 15.sp, +// color: isCurrentSelect ? Colors.white : const Color.fromRGBO(148, 163, 182, 1), +// ), +// ), +// ), +// ), +// ), +// ); +// }).toList(), +// if (sort == SortKeyboard.INVERTED_ORDER) +// Material( +// child: Ink( +// padding: EdgeInsets.only(top: 3.h), +// decoration: BoxDecoration( +// color: const Color.fromRGBO(249, 250, 254, 1), +// border: Border.all( +// width: 1.h, +// color: const Color.fromRGBO(224, 230, 255, 1), +// ), +// borderRadius: BorderRadius.all(Radius.circular(2.w)), +// ), +// child: InkWell( +// splashColor: Theme.of(context).primaryColor, +// onTap: () => easyThrottle('toMarkingVal', () => scoring(job.totalScore, false), +// // () { +// // _useKeyboard.readOver(fraction: job.totalScore, job: job).then( +// // (value) => eventFire( +// // model: JobLargeQuestionScoringBus(ratingScore: value?.toDouble()), +// // ), +// // ); +// // }, +// duration: easyThrottleTime), +// child: Container( +// width: double.infinity, +// height: 56.h, +// alignment: Alignment.center, +// child: quickText('满分', size: 15.sp, color: const Color.fromRGBO(148, 163, 182, 1)), +// ), +// ), +// ), +// ), +// ], +// ), +// ), +// ), +// ], +// ), +// ); +// } diff --git a/marking_app/lib/pages/homework_correction/components/largeQuestionScoring.dart b/marking_app/lib/pages/homework_correction/components/largeQuestionScoring.dart new file mode 100644 index 0000000..6fd3df8 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/largeQuestionScoring.dart @@ -0,0 +1,56 @@ +// import 'package:dotted_border/dotted_border.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// import 'package:marking_app/common/model/event_bus/job_large_question_scoring_bus.dart'; +// import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +// import 'package:marking_app/pages/common/event_bus_mixin.dart'; + +// /// 答题得分框 +// class LargeQuestionScoring extends HookWidget with EventBusMixin { +// final MarkingTextQuestionJob job; +// LargeQuestionScoring({required this.job}); + +// @override +// Widget build(BuildContext context) { +// UseLargeQuestionScoring _useReadoverScore = UseLargeQuestionScoring.use(score: job.readoverScore); + +// useEffect(() { +// // 事件总线监听 +// eventOn(callback: (JobLargeQuestionScoringBus item) { +// if (_useReadoverScore.ratingScore.hasListeners) _useReadoverScore.ratingScore.value = item.ratingScore; +// }); +// return () { +// eventCancel(); +// }; +// }, []); + +// return DottedBorder( +// dashPattern: const [8, 4], +// strokeWidth: 2, +// padding: EdgeInsets.zero, +// color: const Color.fromRGBO(46, 91, 255, 1), +// child: Container( +// padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.h), +// alignment: Alignment.center, +// child: Text( +// _useReadoverScore.ratingScore.value?.toString() ?? '请评分', +// style: TextStyle( +// fontSize: 18.sp, +// color: const Color.fromRGBO(46, 91, 255, 1), +// ), +// ), +// ), +// ); +// } +// } + +// class UseLargeQuestionScoring { +// ValueNotifier ratingScore; // 分数 + +// UseLargeQuestionScoring._({required this.ratingScore}); + +// factory UseLargeQuestionScoring.use({double? score}) { +// return UseLargeQuestionScoring._(ratingScore: useState(score)); +// } +// } diff --git a/marking_app/lib/pages/homework_correction/components/mainContentBox.dart b/marking_app/lib/pages/homework_correction/components/mainContentBox.dart new file mode 100644 index 0000000..4f6b721 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/mainContentBox.dart @@ -0,0 +1,59 @@ +// import 'package:flutter/material.dart'; +// import 'package:hooks_riverpod/hooks_riverpod.dart'; +// import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +// import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; + +// import 'jobPictureOverview.dart'; + +// class MainContentBox extends StatefulHookConsumerWidget { +// final MarkingTextQuestionJob jobData; +// final bool isHorizontal; +// final GlobalKey scaffoldKeyPictureOverview; +// const MainContentBox( +// {required this.scaffoldKeyPictureOverview, required this.isHorizontal, required this.jobData, Key? key}) +// : super(key: key); + +// @override +// _MainContentBoxState createState() => _MainContentBoxState(); +// } + +// class _MainContentBoxState extends ConsumerState { +// late MarkingTextQuestionJob jobData; + +// @override +// void initState() { +// jobData = widget.jobData; +// super.initState(); +// } + +// @override +// void dispose() { +// super.dispose(); +// } + +// @override +// Widget build(BuildContext context) { +// bool annotationSwitch = ref.watch(annotationGraffitiSwitchProvider).annotationSwitch; +// bool magnifier = ref.watch(annotationGraffitiSwitchProvider).magnifier; + +// if (magnifier) annotationSwitch = false; + +// return LayoutBuilder( +// builder: (BuildContext context, BoxConstraints constraints) { +// final double containerWidth = constraints.maxWidth; // 展示区域总宽度 +// final double containerHeight = constraints.maxHeight; // 展示区域总宽度 + +// return JobPictureOverview( +// containerWidth: containerWidth, +// containerHeight: containerHeight, +// key: widget.scaffoldKeyPictureOverview, +// annotationsFlag: annotationSwitch, +// imageItems: jobData.studentAnswerList, +// commentImageMap: jobData.commentImageMap, +// questionImgUrl: jobData.questionImgUrl, +// taskdetailId: jobData.taskSubjectiveDetailId, +// ); +// }, +// ); +// } +// } diff --git a/marking_app/lib/pages/homework_correction/components/new_version_of_homework/bottom_annotation_switch_job.dart b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/bottom_annotation_switch_job.dart new file mode 100644 index 0000000..2109c5f --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/bottom_annotation_switch_job.dart @@ -0,0 +1,229 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/event_bus/bottom_annotation_switch_cleanall.dart'; +import 'package:marking_app/common/model/event_bus/jobs/job_do_papers_switch_operation_bus.dart'; +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/job_do_papers_switch_operation_sub_bus.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/job_notes_view_bus.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/utils/index.dart'; + +// 作业底部批注开关 +class BottomAnnotationSwitchJob extends StatefulHookConsumerWidget { + final double? maxWidth; + final bool homework; + const BottomAnnotationSwitchJob({this.maxWidth, this.homework = false, Key? key}) : super(key: key); + + @override + _BottomAnnotationSwitchJobState createState() => _BottomAnnotationSwitchJobState(); +} + +class _BottomAnnotationSwitchJobState extends ConsumerState + with SingleTickerProviderStateMixin, EventBusMixin { + JobNotesViewBus? jobNotesModel; + late RemoveListener _annotationsListener; // 批注关闭监听 + late AnimationController _animationController; // 动画 + late AnnotationGraffitiSwitch graffitiSwitch; + late DoMarkingKeyboardModel _preferenceModel; + late double upperBound; + late double lowerBound; + Color? bgc; + bool isIos = false; + + @override + void initState() { + var graffitiHander = ref.read(annotationGraffitiSwitchProvider.notifier); + if (widget.homework) { + setTimeOut(500, () { + graffitiHander.setSwitch(true); + if (!graffitiHander.state.openBrush) graffitiHander.setSwitchBrush(); + }); // 默认打开可以书写 + } + + _preferenceModel = ref.read(markingKeyboardProvider); // 偏好设置 + + _animationController = AnimationController( + vsync: this, + value: 1, // 设置默认值 + lowerBound: 0, + upperBound: 1, + duration: const Duration(milliseconds: 300), + )..addListener(toUp); + + _annotationsListener = graffitiHander.addListener((state) { + graffitiSwitch = state; + if (state.annotationSwitch) { + _animationController.forward().then((_) { + eventFire(model: JobDoPapersSwitchOperationSubBus(44.h, true)); + }); + } else { + _animationController.reverse().then((_) { + eventFire(model: JobDoPapersSwitchOperationSubBus(44.h, false)); + }); + } + }); + + // + eventOn(callback: (eventVal) { + switch (eventVal.runtimeType) { + case JobDoPapersSwitchOperationBus: // 底部功能栏操作开关 + bool flag = (eventVal as JobDoPapersSwitchOperationBus).flag; + graffitiHander.setSwitch(flag); + break; + case JobNotesViewBus: // 笔记回显模式 + var eventModel = eventVal as JobNotesViewBus; + if (eventModel.questionNo == jobNotesModel?.questionNo) { + jobNotesModel = null; // 清空笔记会先 + } else { + jobNotesModel = eventModel; // 赋值笔记回显 + } + break; + default: + } + }); + super.initState(); + } + + @override + void dispose() { + _annotationsListener(); + _animationController + ..removeListener(toUp) + ..dispose(); + eventCancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + double iconSize = 24.sp; + Color actionColor = Colors.white; + Color defaultColor = Color.fromRGBO(132, 146, 163, 1); + AnnotationGraffitiSwitch _graffitiSwitch = ref.watch(annotationGraffitiSwitchProvider); + return Container( + width: double.infinity, + height: _animationController.value * 44.h, + color: Color.fromRGBO(83, 83, 83, 1), + padding: EdgeInsets.symmetric(vertical: 1.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Expanded( + flex: 8, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + if (jobNotesModel != null) { + return AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "笔记回显提示", + subTitle: "当前正处于笔记回显", + color: Theme.of(context).primaryColor, + ).show(context); + } + easyThrottle( + 'setSwitchBrush', () => ref.read(annotationGraffitiSwitchProvider.notifier).setSwitchBrush()); + }, + child: Icon( + const IconData(0xe635, fontFamily: "AlibabaIcon"), + size: iconSize, + color: _graffitiSwitch.openBrush ? actionColor : defaultColor, + ), + ), + InkWell( + onTap: () { + if (jobNotesModel != null) { + return AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "笔记回显提示", + subTitle: "当前正处于笔记回显", + color: Theme.of(context).primaryColor, + ).show(context); + } + eventFire(model: BottomAnnotationSwitchCleanall(previousStep: true)); + }, + child: Icon(const IconData(0xe638, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), + ), + InkWell( + onTap: () { + if (jobNotesModel != null) { + return AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "笔记回显提示", + subTitle: "当前正处于笔记回显", + color: Theme.of(context).primaryColor, + ).show(context); + } + easyThrottle( + 'setSwitchMagnifier', () => ref.read(annotationGraffitiSwitchProvider.notifier).setMagnifier()); + }, + // IconData(0xe62f, fontFamily: "AlibabaIcon") + child: Icon(const IconData(0xe636, fontFamily: "AlibabaIcon"), + size: iconSize, color: _graffitiSwitch.magnifier ? actionColor : defaultColor), + ), + InkWell( + onTap: () { + if (jobNotesModel != null) { + return AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "笔记回显提示", + subTitle: "当前正处于笔记回显", + color: Theme.of(context).primaryColor, + ).show(context); + } + eventFire(model: BottomAnnotationSwitchCleanall(cleanAll: true)); + }, + child: Icon( + const IconData(0xe637, fontFamily: "AlibabaIcon"), + size: iconSize, + color: defaultColor, + ), + ), + ], + ), + ), + Expanded( + flex: 2, + child: Container( + child: InkWell( + onTap: () { + if (jobNotesModel != null) { + return AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "笔记回显提示", + subTitle: "当前正处于笔记回显", + color: Theme.of(context).primaryColor, + ).show(context); + } + eventFire(model: JobDoPapersSwitchOperationBus(false)); + }, + child: Icon( + const IconData(0xe639, fontFamily: "AlibabaIcon"), + size: iconSize, + color: _graffitiSwitch.trajectoryDisplay ? actionColor : defaultColor, + ), + ), + ), + ), + ], + ), + ); + } + + void toUp() { + toUpState(setState, () {}, mounted); + } +} diff --git a/marking_app/lib/pages/homework_correction/components/new_version_of_homework/button_floating_action.dart b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/button_floating_action.dart new file mode 100644 index 0000000..b49b0f7 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/button_floating_action.dart @@ -0,0 +1,51 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/model/event_bus/jobs/job_do_papers_switch_operation_bus.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/hooks/do_papers_job/use_swith_operation_bar.dart'; +import 'package:marking_app/utils/my_text.dart'; + +// 底部floating按钮 控制底部操作工具栏 +class ButtonFloatingAction extends HookWidget with EventBusMixin { + @override + Widget build(BuildContext context) { + UseSwithOperationBar _useSwithOperationBar = UseSwithOperationBar.use(); // 底部功能栏开关 + + // 监听底部功能栏开关 + useValueChanged(_useSwithOperationBar.operationBarSwitch.value, (_, __) { + bool newVal = _useSwithOperationBar.operationBarSwitch.value; + if (newVal) { + eventFire(model: JobDoPapersSwitchOperationBus(newVal)); + } + return null; + }); + + useEffect(() { + eventOn(callback: (JobDoPapersSwitchOperationBus val) { + if (!val.flag) _useSwithOperationBar.operationBarSwitch.value = false; + }); + return () { + eventCancel(); + }; + }, []); + + if (_useSwithOperationBar.operationBarSwitch.value) return SizedBox(); + + return FloatingActionButton( + backgroundColor: Colors.white, + tooltip: "点击打开工具栏", + child: Icon( + Icons.edit, + size: 20.sp, + color: Theme.of(context).primaryColor, + ), + elevation: 8, // Control the shadow's elevation + onPressed: () { + _useSwithOperationBar.operationBarSwitch.value = true; + }, + ); + } +} diff --git a/marking_app/lib/pages/homework_correction/components/new_version_of_homework/favorite_widget.dart b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/favorite_widget.dart new file mode 100644 index 0000000..73f3491 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/favorite_widget.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/job/job_collect_params.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +/// 自定义收藏组件 +/// isFavorite 默认是否收藏 +/// favoriteNum 默认收藏次数 +class FavoriteWidget extends StatefulWidget { + FavoriteWidget({Key? key}) : super(key: key); + + @override + _FavoriteState createState() => _FavoriteState(); +} + +class _FavoriteState extends State with EventBusMixin, CommonMixin { + Future? _future; // 考试试卷 + + @override + void initState() { + super.initState(); + eventOn(callback: (JobQuestionsSwitch eventModel) { + _future = getData(eventModel.taskId, eventModel.studentId); + toUpState(setState, () {}, mounted); + }); + } + + @override + void dispose() { + super.dispose(); + eventCancel(); + } + + Future getData(int taskId, int studentId) async { + try { + RestClient _client = await getClient(); + BaseStructureResult res = await _client.getJobCollect(taskId, studentId); + if (res.success) return JobCollectParams(taskId, studentId, res.data ?? false); + } catch (e) {} + + return null; + } + + Future toFavorite(JobCollectParams favoriteVal) async { + try { + ToastUtils.showLoading(); + RestClient _client = await getClient(); + favoriteVal.isCollect = !favoriteVal.isCollect; + BaseStructureResult res = await _client.toJobFavoriteCancel(favoriteVal); + if (res.success) { + _future = getData(favoriteVal.taskId, favoriteVal.studentId); + toUpState(setState, () {}, mounted); + } else { + ToastUtils.showError(res.message ?? '操作失败,请重试'); + } + } catch (e) { + } finally { + ToastUtils.dismiss(); + } + } + + @override + Widget build(BuildContext context) { + if (_future == null) return Container(); + + return MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _future!, (favoriteVal) { + if (favoriteVal == null) return Container(); + var _isFavorite = favoriteVal.isCollect; + return Container( + padding: EdgeInsets.symmetric(horizontal: 4.r), + child: InkWell( + onTap: () => easyThrottle('homework_review_collect_btn', () => toFavorite(favoriteVal), + duration: Duration(milliseconds: 500)), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + const IconData(0xe63c, fontFamily: "AlibabaIcon"), + size: 18.sp, + color: _isFavorite ? Color.fromRGBO(255, 172, 48, 1) : Color.fromRGBO(164, 164, 164, 1), + ), + SizedBox(width: 6.w), + ], + ), + ), + ); + }, widget: Container()); + } +} + +class JobQuestionsSwitch extends Object { + final int taskId; + final int studentId; + + JobQuestionsSwitch(this.taskId, this.studentId); +} diff --git a/marking_app/lib/pages/homework_correction/components/new_version_of_homework/homework_tasks_view_item.dart b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/homework_tasks_view_item.dart new file mode 100644 index 0000000..2bffb6a --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/new_version_of_homework/homework_tasks_view_item.dart @@ -0,0 +1,1219 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:badges/badges.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student_params.dart'; +import 'package:marking_app/common/model/job/job_task_item.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:percent_indicator/circular_percent_indicator.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:badges/badges.dart' as badges; + +part 'homework_tasks_view_item.g.dart'; + +// 作业任务单个试题视图 +class HomeworkTasksViewItem extends StatelessWidget with CommonMixin { + final bool completed; // 已完成 + final VoidCallback call; + final JobTaskItem jobTaskItem; + const HomeworkTasksViewItem({required this.jobTaskItem, required this.call, this.completed = true, super.key}); + + // 结束批阅方法 + Future endReview(List markingTasks) async { + try { + ToastUtils.showLoading(); + RestClient client = await getClient(); + BaseStructureResult res = await client.toEndReviewJob(markingTasks.map((e) => e.id).toList()); + if (!res.success) ToastUtils.showError('结束失败,请重试'); + return res.success; + } catch (e) { + ToastUtils.showError('结束失败,请重试'); + return false; + } + } + + /// @name showStudents + /// @param {List} taskId 子任务ID集合 + /// @param {String} taskName 主任务名称 + /// @param {bool} className 班级名称(不传此参数为主任务;传此参数为子任务) + /// @param {bool} submitted 是否已提交(默认未提交) + Future showStudents( + BuildContext context, + List taskIds, [ + bool? submitted = false, + String? className, + ]) async { + ToastUtils.showLoading(); + List students = []; + try { + RestClient _client = await getClient(); + BaseStructureResult> _result = await _client.getJobWithStudents( + JobConcernedWithStudentParams(taskIds, isCommit: submitted), + ); + if (!_result.success) { + return ToastUtils.showError(_result.message ?? '获取学生列表失败'); + } + if (_result.data?.isEmpty ?? true) { + return ToastUtils.showError('获取到的学生列表为空'); + } + students = _result.data!; + } catch (e) { + return ToastUtils.showError('获取学生列表失败'); + } finally { + ToastUtils.dismiss(); + } + + showModalBottomSheet( + context: context, + elevation: 10, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.r), + topRight: Radius.circular(20.r), + ), + ), + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 2.w), + child: Column( + children: [ + Container( + margin: EdgeInsets.only(top: 14.h), + child: quickText( + '${className ?? ''}${submitted! ? '已提交' : '未提交'}作业学生', + size: 18.sp, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(60, 60, 60, 1), + ), + ), + Expanded( + child: ListView( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w), + children: [ + Wrap( + spacing: 6.0, // 主轴(水平)方向间距 + runSpacing: 4.0, // 纵轴(垂直)方向间距 + alignment: WrapAlignment.spaceAround, //沿主轴方向居中 + children: students.map((e) { + return Chip( + backgroundColor: Color.fromRGBO(239, 242, 255, 1), + avatar: CircleAvatar( + backgroundColor: Colors.white, + child: quickText(e.studentName.substring(0, 1), + size: 12.sp, color: Theme.of(context).primaryColor), + ), + label: quickText(e.studentName, color: Color.fromRGBO(80, 94, 110, 1), size: 12.sp), + ); + }).toList(), + ), + ], + ), + ) + ], + ), + ); + }, + ); + } + + void oneClickReview(int taskId) async { + try { + ToastUtils.showLoading(); + RestClient _client = await getClient(); + BaseStructureResult res = await _client.toJobOneClickReview(taskId); + if (res.success) return call(); + ToastUtils.showError(res.message ?? '操作失败'); + } catch (e) { + } finally { + ToastUtils.dismiss(); + } + } + + @override + Widget build(BuildContext context) { + return completed + ? $CompletedHomeworkView(jobTaskItem: jobTaskItem, showStudentsCall: showStudents) + : $UnfinishedHomework( + jobTaskItem: jobTaskItem, + refreshCallback: call, + endReviewCallback: endReview, + oneClickReviewCallback: oneClickReview, + ); + } +} + +// 已完成 +@hwidget +Widget $completedHomeworkView(BuildContext context, + {required JobTaskItem jobTaskItem, required ShowStudentsCall showStudentsCall}) { + return Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 10.w), + margin: EdgeInsets.only(bottom: 12.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.r), + color: const Color.fromRGBO(255, 255, 255, 1), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(210, 216, 241, 1), + offset: Offset.zero, //阴影y轴偏移量 + blurRadius: 5.8, //阴影模糊程度 + spreadRadius: 0, //阴影扩散程度 + ) + ], + ), + child: Column( + children: [ + // 顶部任务名称 + Row( + children: [ + Container( + width: 32.w, + height: 18.h, + alignment: Alignment.center, + padding: EdgeInsets.only(left: 2.w), + decoration: BoxDecoration( + color: Color.fromRGBO(104, 136, 253, 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(18.r), + topRight: Radius.circular(3.r), + bottomLeft: Radius.circular(4.r), + bottomRight: Radius.circular(4.r), + ), + ), + margin: EdgeInsets.only(right: 4.w), + child: quickText('作业', color: Colors.white, size: 10.sp), + ), + quickText( + jobTaskItem.title, + size: 16.sp, + color: Color.fromRGBO(70, 70, 70, 1), + fontWeight: FontWeight.bold, + ) + ], + ), + SizedBox(height: 12.h), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + jobTaskItem.subjectName, + color: Color.fromRGBO(97, 97, 97, 1), + size: 14.sp, + fontWeight: FontWeight.w600, + ), + quickText(' / ', color: Color.fromRGBO(130, 130, 130, 1), size: 12.sp, fontWeight: FontWeight.w500), + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText('题量:', color: Color.fromRGBO(130, 130, 130, 1), size: 13.sp), + quickText( + jobTaskItem.totalCount, + color: Color.fromRGBO(97, 97, 97, 1), + size: 14.sp, + fontWeight: FontWeight.w500, + ), + ], + ), + ), + quickText(' / ', color: Color.fromRGBO(130, 130, 130, 1), size: 12.sp, fontWeight: FontWeight.w500), + quickText( + jobTaskItem.createTime.substring(0, 16), + color: Color.fromRGBO(97, 97, 97, 1), + size: 14.sp, + fontWeight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 20.h), + $CompletedHomeworkInfoBox( + segmentation: false, + showStudentsCall: showStudentsCall, + unsubmittedQuantity: jobTaskItem.studentCount - jobTaskItem.commitStudentCount, + submittedQuantity: jobTaskItem.commitStudentCount, + precision: jobTaskItem.precision / 100, + objectivePrecision: jobTaskItem.objectivePrecision / 100, + subjectivePrecision: jobTaskItem.subjectivePrecision / 100, + taskIds: jobTaskItem.markingTasks.map((e) => e.id).toList(), + ), + SizedBox(height: 10.h), + ExpansionTile( + tilePadding: EdgeInsets.only(right: 10.w, bottom: 0), + title: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // badges.Badge( + // position: badges.BadgePosition.topEnd(top: -3.r, end: -12.r), + // badgeStyle: BadgeStyle(badgeColor: Theme.of(context).primaryColor.withOpacity(0.7)), + // badgeContent: Text( + // jobTaskItem.markingTasks.length.toString(), + // style: TextStyle(color: Colors.white, fontSize: 10.sp), + // ), + // child: quickText('班级列表 ', size: 17.sp), + // ), + badges.Badge( + badgeStyle: badges.BadgeStyle( + shape: badges.BadgeShape.square, + borderRadius: BorderRadius.circular(5.r), + padding: EdgeInsets.all(2.r), + badgeGradient: badges.BadgeGradient.linear( + colors: [ + Colors.purple, + Colors.blue, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + position: badges.BadgePosition.topEnd(top: -2.r, end: -8.r), + badgeContent: Text( + jobTaskItem.markingTasks.length.toString(), + style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + child: quickText('班级列表 ', size: 17.sp), + ), + ], + ), + childrenPadding: EdgeInsets.only(top: 0.h), + children: jobTaskItem.markingTasks.map((e) { + return $CompletedHomeworkChildView( + jobTaskItem: jobTaskItem, + taskItem: e, + showStudentsCall: showStudentsCall, + ); + }).toList(), + ), + ], + ), + ); +} + +@hwidget +Widget $completedHomeworkChildView( + BuildContext context, { + required JobTaskItem jobTaskItem, + required MarkingTasks taskItem, + required ShowStudentsCall showStudentsCall, +}) { + return Column( + children: [ + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + jobTaskItem.genderName + taskItem.className, + color: Color.fromRGBO(255, 123, 1, 1), + size: 14.sp, + fontWeight: FontWeight.bold, + ), + quickText('题量:${taskItem.totalCount}', color: Color.fromRGBO(68, 68, 68, 1), size: 12.sp), + quickText(taskItem.teacherName, color: Color.fromRGBO(68, 68, 68, 1), size: 12.sp), + quickText(taskItem.finishTime?.substring(0, 16) ?? '', color: Color.fromRGBO(97, 97, 97, 1), size: 12.sp), + ], + ), + SizedBox(height: 10.h), + $CompletedHomeworkInfoBox( + precision: jobTaskItem.precision / 100, + objectivePrecision: jobTaskItem.objectivePrecision / 100, + subjectivePrecision: jobTaskItem.subjectivePrecision / 100, + submittedQuantity: taskItem.commitStudentCount, + unsubmittedQuantity: taskItem.studentCount - taskItem.commitStudentCount, + showStudentsCall: showStudentsCall, + taskIds: [taskItem.id], + className: taskItem.className, + ), + ], + ); +} + +// 已完成作业按钮 +@swidget +Widget $completedHomeworkBtn( + BuildContext context, { + required List taskIds, + required int submittedQuantity, + required int unsubmittedQuantity, + required ShowStudentsCall showStudentsCall, + String? className, +}) { + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Expanded( + child: Material( + color: Colors.white, + child: InkWell( + onTap: () { + if (unsubmittedQuantity <= 0) return ToastUtils.showInfo('没有未提交的作业'); + showStudentsCall(context, taskIds, false, className); + }, + borderRadius: BorderRadius.circular(8.r), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8.h), + decoration: BoxDecoration( + border: Border.all(color: Color.fromRGBO(104, 136, 253, 1), width: 1.r), + borderRadius: BorderRadius.circular(8.r), + ), + child: quickText('未交作业:$unsubmittedQuantity份', size: 12.sp, color: Color.fromRGBO(104, 136, 253, 1)), + ), + ), + ), + ), + SizedBox(width: 30.w), + Expanded( + child: Material( + color: Colors.white, + child: InkWell( + onTap: () async { + if (submittedQuantity <= 0) return ToastUtils.showInfo('没有已提交的作业'); + showStudentsCall(context, taskIds, true, className); + }, + borderRadius: BorderRadius.circular(8.r), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8.h), + decoration: BoxDecoration( + border: Border.all(color: Color.fromRGBO(76, 199, 147, 1), width: 1.r), + borderRadius: BorderRadius.circular(8.r), + ), + child: quickText('已交作业:$submittedQuantity份', size: 12.sp, color: Color.fromRGBO(76, 199, 147, 1)), + ), + ), + ), + ), + ], + ), + ); +} + +@swidget +Widget $completedHomeworkProgressBar( + BuildContext context, { + required String title, + required Color color, + required double percent, +}) { + var percentStr = '${getDoubleRemoveZero(percent * 100)}%'; + return Container( + margin: EdgeInsets.symmetric(vertical: 10.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + alignment: Alignment.centerRight, + child: Row( + children: [ + if (title == '正确率:') quickText('正确率', color: Colors.transparent, size: 13.sp), + quickText(title, color: Color.fromRGBO(139, 139, 139, 1), size: 13.sp), + ], + ), + ), + Expanded( + flex: 1, + child: Container( + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + boxShadow: [ + BoxShadow( + color: color, + spreadRadius: 0.6, + blurRadius: 3, + offset: Offset(0, 0), + ), + ], + ), + child: LinearPercentIndicator( + padding: EdgeInsets.zero, + animation: true, + lineHeight: 10.h, + animationDuration: 2500, + percent: percent, + center: Text( + percentStr, + style: TextStyle(color: Colors.white, fontSize: 8.sp), + ), + // linearStrokeCap: LinearStrokeCap.butt, + progressColor: color, + backgroundColor: Colors.white, + barRadius: Radius.circular(10.r), + // linearGradient: LinearGradient( + // tileMode: TileMode.mirror, + // stops: [0.0, 1.0], + // colors: [color.withOpacity(0.1), color], + // ), + ), + ), + ), + SizedBox(width: 4.w), + quickText(percentStr, size: 10.sp, color: color) + ], + ), + ), + ), + ], + ), + ); +} + +@swidget +Widget $completedHomeworkInfoBox( + BuildContext context, { + required List taskIds, + required int unsubmittedQuantity, + required int submittedQuantity, + required double objectivePrecision, // 客观题正确率 + required double subjectivePrecision, // 主观题正确率 + required double precision, // 综合正确率 + required ShowStudentsCall showStudentsCall, + bool segmentation = true, + String? className, +}) { + return Column( + children: [ + $CompletedHomeworkBtn( + taskIds: taskIds, + className: className, + unsubmittedQuantity: unsubmittedQuantity, + submittedQuantity: submittedQuantity, + showStudentsCall: showStudentsCall, + ), + SizedBox(height: 10.h), + $CompletedHomeworkProgressBar( + color: Color.fromRGBO(76, 199, 147, 1), + percent: objectivePrecision, + title: '客观题正确率:', + ), + $CompletedHomeworkProgressBar( + color: Color.fromRGBO(255, 190, 91, 1), + percent: subjectivePrecision, + title: '主观题正确率:', + ), + $CompletedHomeworkProgressBar( + color: Color.fromRGBO(166, 139, 242, 1), + percent: precision, + title: '正确率:', + ), + if (segmentation) + Container( + height: 1.h, + decoration: BoxDecoration(color: Color.fromRGBO(233, 233, 233, 1)), + ), + ], + ); +} + +// 未完成作业单个view +@swidget +Widget $unfinishedHomework( + BuildContext context, { + required JobTaskItem jobTaskItem, + required VoidCallback refreshCallback, + required EndReviewCall endReviewCallback, + required Function(int) oneClickReviewCallback, +}) { + return Stack( + alignment: const FractionalOffset(0.95, 0), + children: [ + Container( + margin: EdgeInsets.only(bottom: 16.h), + child: Column( + children: [ + SizedBox(height: 30.h), + Container( + padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 10.w), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.r), + color: const Color.fromRGBO(255, 255, 255, 1), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(210, 216, 241, 1), + offset: Offset.zero, //阴影y轴偏移量 + blurRadius: 5.8, //阴影模糊程度 + spreadRadius: 0, //阴影扩散程度 + ) + ], + ), + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 32.w, + height: 18.h, + alignment: Alignment.center, + padding: EdgeInsets.only(left: 2.w), + decoration: BoxDecoration( + color: Color.fromRGBO(104, 136, 253, 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(18.r), + topRight: Radius.circular(3.r), + bottomLeft: Radius.circular(4.r), + bottomRight: Radius.circular(4.r), + ), + ), + margin: EdgeInsets.only(right: 4.w), + child: quickText('作业', color: Colors.white, size: 10.sp), + ), + quickText( + jobTaskItem.title, + size: 16.sp, + color: Color.fromRGBO(70, 70, 70, 1), + fontWeight: FontWeight.bold, + ) + ], + ), + SizedBox(height: 22.h), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + jobTaskItem.subjectName, + color: Color.fromRGBO(97, 97, 97, 1), + size: 14.sp, + fontWeight: FontWeight.w600, + ), + quickText(' / ', + color: Color.fromRGBO(130, 130, 130, 1), size: 12.sp, fontWeight: FontWeight.w500), + Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText('题量:', color: Color.fromRGBO(130, 130, 130, 1), size: 13.sp), + quickText( + jobTaskItem.totalCount, + color: Color.fromRGBO(97, 97, 97, 1), + size: 14.sp, + fontWeight: FontWeight.w500, + ), + ], + ), + ), + quickText(' / ', + color: Color.fromRGBO(130, 130, 130, 1), size: 12.sp, fontWeight: FontWeight.w500), + quickText( + jobTaskItem.createTime.substring(0, 16), + color: Color.fromRGBO(97, 97, 97, 1), + size: 12.sp, + ), + ], + ), + // 未完成按钮 + $UnfinishedBtn( + isChild: false, + jobTaskItem: jobTaskItem, + endCallback: endReviewCallback, + refreshcall: refreshCallback, + ), + SizedBox(height: 20.h), + ExpansionTile( + tilePadding: EdgeInsets.only(right: 10.w), + title: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + badges.Badge( + badgeStyle: badges.BadgeStyle( + shape: badges.BadgeShape.square, + borderRadius: BorderRadius.circular(5.r), + padding: EdgeInsets.all(2.r), + badgeGradient: badges.BadgeGradient.linear( + colors: [Colors.purple, Colors.blue], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + position: badges.BadgePosition.topEnd(top: -2.r, end: -8.r), + badgeContent: Text( + jobTaskItem.markingTasks.length.toString(), + style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + child: quickText('班级列表 ', size: 17.sp), + ), + ], + ), + children: jobTaskItem.markingTasks.map((e) { + return $UnfinishedClassItem( + jobTaskItem: jobTaskItem, + jobTaskClassItem: e, + endReviewCallback: endReviewCallback, + refreshCallback: refreshCallback, + oneClickReviewCallback: oneClickReviewCallback, + ); + }).toList(), + ), + ], + ), + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.all(9.r), + decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white), + child: CircularPercentIndicator( + radius: 40.r, + lineWidth: 10.r, + animation: true, + percent: jobTaskItem.progressPercentage / 100, + center: Text.rich(TextSpan(children: [ + TextSpan( + text: getDoubleRemoveZero(jobTaskItem.progressPercentage, '0'), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.sp, + color: Theme.of(context).primaryColor, + ), + ), + TextSpan( + text: "%", + style: TextStyle(color: Color.fromRGBO(70, 70, 70, 1), fontSize: 12.sp, fontWeight: FontWeight.bold), + ), + ])), + circularStrokeCap: CircularStrokeCap.round, + // progressColor: Theme.of(context).primaryColor, + linearGradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + tileMode: TileMode.clamp, + stops: [0.0, 1.0], + colors: [ + Theme.of(context).primaryColor.withOpacity(0.1), + Theme.of(context).primaryColor, + ], + ), + backgroundColor: Color.fromRGBO(244, 244, 244, 1), + ), + // Container( + // child: Container( + // color: Colors.red, + // height: 40.r, + // width: 40.r, + // ) + // ), + ), + ], + ); +} + +// 未完成作业按钮 (名称和数量) +@swidget +Widget $unfinishedBtn( + BuildContext context, { + required JobTaskItem jobTaskItem, + required VoidCallback refreshcall, + required EndReviewCall endCallback, + MarkingTasks? jobTaskClassItem, + bool isChild = true, +}) { + if (!isChild) { + MarkingTasks? theCanReviewChild = jobTaskItem.markingTasks.firstWhereOrNull((e) => e.canGoReview); + + List canEndTaskChilds = jobTaskItem.markingTasks.where((e) => !e.isFinish && e.canMarking).toList(); + return Container( + margin: EdgeInsets.only(top: 22.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Expanded( + flex: 2, + child: Material( + color: theCanReviewChild != null ? Colors.white : Colors.grey[300], + borderRadius: BorderRadius.circular(30.r), + child: InkWell( + onTap: () { + MarkingTasks? markingTask = + jobTaskItem.markingTasks.firstWhereOrNull((e) => e.canMarking && e.totalCount > 0 && !e.isFinish); + if (markingTask == null) { + ToastUtils.showInfo('没有找到可以批阅的任务'); + // AchievementView( + // elevation: 0.5, + // duration: Duration(seconds: 1), + // title: "提示", + // subTitle: "没有找到可以批阅的任务", + // color: Theme.of(context).primaryColor, + // ).show(context); + return; + } + String url = + '${RouterManager.markingHomeworkDoPath}?taskId=${markingTask.id}&taskName=${Uri.encodeComponent(jobTaskItem.title)}&className=${Uri.encodeComponent(jobTaskItem.genderName + markingTask.className)}'; + RouterManager.router.navigateTo(context, url, transition: getTransition()).then((value) { + if (value != null && value == true) refreshcall(); + }); + }, + borderRadius: BorderRadius.circular(30.r), + child: Container( + height: 36.h, + padding: EdgeInsets.symmetric(vertical: 3.h), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + border: Border.all(color: Theme.of(context).primaryColor), + ), + child: quickText('批阅', color: Theme.of(context).primaryColor, size: 14.sp), + ), + ), + ), + ), + Expanded( + flex: 1, + child: SizedBox(width: 5.w), + ), + Expanded( + flex: 2, + child: Material( + color: canEndTaskChilds.isNotEmpty ? Color.fromRGBO(104, 136, 253, 1) : Colors.grey, + borderRadius: BorderRadius.circular(30.r), + child: InkWell( + onTap: () async { + List childTasks = + jobTaskItem.markingTasks.where((e) => !e.isFinish && e.canMarking).toList(); + if (childTasks.isEmpty) return ToastUtils.showInfo('没有可以结束的任务'); + + // 未完成批阅任务提示 + List incompleteTasks = childTasks.where((e) => e.finishCount < e.totalCount).toList(); + if (incompleteTasks.isNotEmpty) { + var continueFlag = await showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: Text('未完成批阅提示'), + content: Text('当前批阅任务未完成,请确认需要结束此任务?'), + actions: [ + TextButton( + child: Text('否'), + onPressed: () { + // 在这里处理删除操作 + Navigator.of(context1).pop(false); + }, + ), + TextButton( + child: Text('是'), + onPressed: () { + Navigator.of(context1).pop(true); + }, + ), + ], + ); + }, + ); + if (continueFlag == null || !continueFlag) { + return; + } + } + + showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: Text('提示'), + content: Text('确认完成以下${childTasks.map((e) => e.className).toList().join(",")}的批阅任务?'), + actions: [ + TextButton( + child: Text('否'), + onPressed: () { + // 在这里处理删除操作 + Navigator.of(context1).pop(); + }, + ), + TextButton( + child: Text('确定'), + onPressed: () async { + try { + bool result = await endCallback(childTasks); + if (result) refreshcall(); + } catch (e) { + } finally { + ToastUtils.dismiss(); + Navigator.of(context1).pop(); + } + }, + ), + ], + ); + }, + ); + }, + borderRadius: BorderRadius.circular(30.r), + child: Container( + height: 36.h, + padding: EdgeInsets.symmetric(vertical: 3.5.h), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + ), + child: quickText('结束批阅', size: 14.sp, color: Colors.white), + ), + ), + ), + ), + ], + ), + ); + } + + if (!jobTaskClassItem!.canMarking) + return Container( + alignment: Alignment.centerRight, + child: jobTaskClassItem.isFinish + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(jobTaskClassItem.finishTime?.substring(0, 16), + size: 10.sp, color: Color.fromRGBO(170, 170, 170, 1)), + SizedBox(width: 4.w), + quickText('已批阅', size: 10.sp, color: Theme.of(context).primaryColor), + ], + ) + : quickText( + '待批阅', + size: 10.sp, + color: Color.fromRGBO(255, 138, 0, 1), + ), + ); + + return Container( + child: jobTaskClassItem.isFinish + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(jobTaskClassItem.finishTime?.substring(0, 16), + size: 12.sp, color: Color.fromRGBO(170, 170, 170, 1)), + SizedBox(width: 4.w), + quickText('已批阅', size: 13.sp, color: Theme.of(context).primaryColor), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + flex: 5, + child: Material( + color: jobTaskClassItem.canGoReview ? Colors.white : Colors.grey[300], + borderRadius: BorderRadius.circular(4.r), + child: InkWell( + onTap: () { + if (jobTaskClassItem.totalCount <= 0) { + return ToastUtils.showInfo('没有找到可以批阅的任务'); + } + + if (!jobTaskClassItem.canMarking) { + return AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "此账号无法批阅该任务", + color: Theme.of(context).primaryColor, + ).show(context); + } + + if (jobTaskClassItem.isFinish) { + return AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "此批阅任务已完成", + color: Theme.of(context).primaryColor, + ).show(context); + } + + String url = + '${RouterManager.markingHomeworkDoPath}?taskId=${jobTaskClassItem.id}&taskName=${Uri.encodeComponent(jobTaskItem.title)}&className=${Uri.encodeComponent(jobTaskItem.genderName + jobTaskClassItem.className)}'; + RouterManager.router.navigateTo(context, url, transition: getTransition()).then((value) { + if (value == true) refreshcall(); + }); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 6.h), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.r), + border: Border.all(color: Theme.of(context).primaryColor), + ), + child: quickText('批阅', color: Theme.of(context).primaryColor, size: 12.sp), + ), + ), + )), + Expanded(flex: 1, child: SizedBox()), + Expanded( + flex: 5, + child: Material( + color: !jobTaskClassItem.isFinish && jobTaskClassItem.canMarking + ? Color.fromRGBO(237, 240, 255, 1) + : Colors.grey, + borderRadius: BorderRadius.circular(4.r), + child: InkWell( + onTap: () async { + if (!jobTaskClassItem.canMarking) return ToastUtils.showInfo('此任务非该账号任务'); + if (jobTaskClassItem.isFinish) return ToastUtils.showInfo('此任务已经结束'); + + // 未完成批阅任务提示 + if (jobTaskClassItem.finishCount < jobTaskClassItem.totalCount) { + var continueFlag = await showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: Text('未完成批阅提示'), + content: Text('当前批阅任务未完成,请确认需要结束此任务?'), + actions: [ + TextButton( + child: Text('否'), + onPressed: () { + // 在这里处理删除操作 + Navigator.of(context1).pop(false); + }, + ), + TextButton( + child: Text('是'), + onPressed: () { + Navigator.of(context1).pop(true); + }, + ), + ], + ); + }, + ); + if (continueFlag == null || !continueFlag) { + return; + } + } + + showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: Text('提示'), + content: Text('确认完成当前${jobTaskClassItem.className}批阅任务?'), + actions: [ + TextButton( + child: Text('否'), + onPressed: () { + // 在这里处理删除操作 + Navigator.of(context1).pop(); + }, + ), + TextButton( + child: Text('确定'), + onPressed: () async { + try { + bool result = await endCallback([jobTaskClassItem]); + if (result) refreshcall(); + } catch (e) { + } finally { + ToastUtils.dismiss(); + Navigator.of(context1).pop(); + } + }, + ), + ], + ); + }, + ); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 6.h), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.r), + border: Border.all(color: Color.fromRGBO(130, 156, 255, 1)), + ), + child: quickText('结束批阅', size: 12.sp, color: Color.fromRGBO(104, 136, 253, 1)), + ), + ), + )), + ], + ), + ); +} + +// 未完成作业单个班级view +@swidget +Widget $unfinishedClassItem( + BuildContext context, { + required JobTaskItem jobTaskItem, + required MarkingTasks jobTaskClassItem, + required VoidCallback refreshCallback, + required EndReviewCall endReviewCallback, + required Function(int) oneClickReviewCallback, +}) { + return Container( + padding: EdgeInsets.only(bottom: 14.w), + margin: EdgeInsets.only(bottom: 20.h), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Color.fromRGBO(233, 233, 233, 1), width: 1.r))), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: quickText(jobTaskItem.genderName + jobTaskClassItem.className, + color: Color.fromRGBO(255, 123, 1, 1), size: 14.sp)), + if (!jobTaskClassItem.isFinish) + Expanded( + child: Container( + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(104, 136, 253, 1), + spreadRadius: 0.5, + blurRadius: 1, + offset: Offset(0, 0), + ), + ], + ), + child: LinearPercentIndicator( + padding: EdgeInsets.zero, + animation: true, + lineHeight: 10.h, + animationDuration: 2500, + percent: jobTaskClassItem.progressPercentage / 100, + center: Text( + '${getDoubleRemoveZero(jobTaskClassItem.progressPercentage)}%', + style: TextStyle(color: Colors.white, fontSize: 8.sp), + ), + linearGradient: LinearGradient( + tileMode: TileMode.mirror, + stops: [0.0, 1.0], + colors: [ + Theme.of(context).primaryColor.withOpacity(0.1), + Theme.of(context).primaryColor, + ], + ), + // linearStrokeCap: LinearStrokeCap.butt, + // progressColor: Theme.of(context).primaryColor, + backgroundColor: Colors.white, + barRadius: Radius.circular(10.r), + ), + ), + ), + SizedBox(width: 4.w), + quickText('${getDoubleRemoveZero(jobTaskClassItem.progressPercentage)}%', + size: 10.sp, color: Color.fromRGBO(104, 136, 253, 1)) + ], + ), + ), + ), + ], + ), + SizedBox(height: 14.h), + Container( + child: Row( + children: [ + Expanded( + child: Row(children: [ + quickText('题量:${jobTaskClassItem.totalCount}'), + SizedBox(width: 20.w), + quickText(jobTaskClassItem.teacherName) + ])), + Expanded( + child: $UnfinishedBtn( + jobTaskItem: jobTaskItem, + jobTaskClassItem: jobTaskClassItem, + endCallback: endReviewCallback, + refreshcall: refreshCallback, + ), + ), + ], + ), + ), + if (jobTaskClassItem.canGoReview) SizedBox(height: 6.h), + if (jobTaskClassItem.canGoReview) + Material( + borderRadius: BorderRadius.circular(30.r), + child: InkWell( + onTap: () => easyThrottle('OneClickReview', () async { + var continueFlag = await showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: Text('一键批阅'), + content: Text('一键批阅后,默认学生答题结果全部正确,是否进行此操作?'), + actions: [ + TextButton( + child: Text('否'), + onPressed: () { + // 在这里处理删除操作 + Navigator.of(context1).pop(false); + }, + ), + TextButton( + child: Text('是'), + onPressed: () { + Navigator.of(context1).pop(true); + }, + ), + ], + ); + }, + ); + if (continueFlag == null || !continueFlag) return; + oneClickReviewCallback(jobTaskClassItem.id); + }), + borderRadius: BorderRadius.circular(30.r), + child: Container( + height: 20.h, + constraints: BoxConstraints(minWidth: 50.w, maxWidth: 60.w), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30.r), + border: Border.all(color: Color.fromRGBO(76, 199, 147, 1)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + quickText('一键批阅', color: Color.fromRGBO(76, 199, 147, 1), size: 10.sp), + Padding( + padding: EdgeInsets.only(top: 1.1.h), + child: Icon(Icons.arrow_forward_ios, size: 8.sp, color: Color.fromRGBO(76, 199, 147, 1)), + ), + ], + ), + ), + ), + ), + ], + ), + ); +} + +typedef EndReviewCall = Future Function(List markingTasks); +typedef ShowStudentsCall = Future Function( + BuildContext context, + List taskIds, [ + bool? submitted, + String? className, +]); diff --git a/marking_app/lib/pages/homework_correction/components/nextQuestionButton.dart b/marking_app/lib/pages/homework_correction/components/nextQuestionButton.dart new file mode 100644 index 0000000..7de2511 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/nextQuestionButton.dart @@ -0,0 +1,56 @@ +// import 'package:achievement_view/achievement_view.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// import 'package:marking_app/common/model/enum/job_enums.dart'; +// import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +// import 'package:marking_app/utils/index.dart'; + +// import '../hooks/use_data.dart'; + +// class NextQuestionButton extends ConsumerWidget { +// final bool isFinish; +// final UseData useData; +// const NextQuestionButton({required this.isFinish, required this.useData, super.key}); + +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// // 此处作用:打开作图绘画不能切换试题 +// // bool annotationSwitch = ref.watch(annotationGraffitiSwitchProvider).annotationSwitch; +// // if (annotationSwitch) return Container(); +// return Positioned( +// right: 2.w, +// top: ScreenUtil().setHeight(MediaQuery.of(context).size.height) / 2 * 0.86, +// child: FloatingActionButton( +// elevation: 0, +// tooltip: '点击前往下一题', +// splashColor: Theme.of(context).primaryColor, //水波纹颜色 +// backgroundColor: !isFinish ? const Color.fromRGBO(24, 32, 32, 0.04) : const Color.fromRGBO(24, 32, 32, 0.1), +// foregroundColor: Colors.white, +// onPressed: () => easyThrottle( +// 'TestQuestionSwitch', +// () => !isFinish +// ? AchievementView( +// elevation: 0.5, +// duration: Duration(seconds: 1), +// title: "提示", +// subTitle: "请批阅当前试题,再前往下一题", +// color: Theme.of(context).primaryColor, +// ).show(context) +// : toNext(context, ref), +// duration: Duration(seconds: 1)), +// child: const Icon( +// Icons.arrow_forward_ios, +// color: Colors.white, +// ), +// ), +// ); +// } + +// void toNext(BuildContext context, WidgetRef ref) { +// // ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); 此处作用是关闭作图工具 +// setTimeOut(0, () { +// useData.futureState.value = useData.getData(context, switchAction: ReviewSwitching.TO_NEXT_QUESTION); +// }); +// } +// } diff --git a/marking_app/lib/pages/homework_correction/components/previousQuestionButton.dart b/marking_app/lib/pages/homework_correction/components/previousQuestionButton.dart new file mode 100644 index 0000000..a522016 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/previousQuestionButton.dart @@ -0,0 +1,48 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// import 'package:marking_app/common/model/enum/job_enums.dart'; +// import 'package:marking_app/pages/homework_correction/hooks/use_data.dart'; +// import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +// import 'package:marking_app/utils/anti_shake_throttling.dart'; +// import 'package:marking_app/utils/index.dart'; + +// class PreviousQuestionButton extends ConsumerWidget { +// final bool noPrev; +// final UseData useData; +// const PreviousQuestionButton({required this.noPrev, required this.useData, super.key}); + +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// // 此处作用:打开作图绘画不能切换试题 +// // bool annotationSwitch = ref.watch(annotationGraffitiSwitchProvider).annotationSwitch; +// // if (annotationSwitch) return Container(); +// return Positioned( +// left: 3.w, +// top: ScreenUtil().setHeight(MediaQuery.of(context).size.height) / 2 * 0.87, +// child: FloatingActionButton( +// tooltip: '前往上一题', +// splashColor: Theme.of(context).primaryColor, //水波纹颜色 +// backgroundColor: noPrev ? const Color.fromRGBO(24, 32, 32, 0.04) : const Color.fromRGBO(24, 32, 32, 0.1), +// focusColor: Theme.of(context).primaryColor, +// elevation: 0, +// onPressed: noPrev +// ? null +// : () => easyThrottle( +// 'TestQuestionSwitch', +// () => toPrevious(context, ref), +// duration: Duration(seconds: 1), +// ), +// child: const Icon(Icons.arrow_back_ios, color: Colors.white), +// heroTag: 'other', +// ), +// ); +// } + +// void toPrevious(BuildContext context, WidgetRef ref) { +// // ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); 此处作用是关闭作图工具 +// setTimeOut(0, () { +// useData.futureState.value = useData.getData(context, switchAction: ReviewSwitching.TO_PREVIOUS_QUESTION); +// }); +// } +// } diff --git a/marking_app/lib/pages/homework_correction/components/setPreference.dart b/marking_app/lib/pages/homework_correction/components/setPreference.dart new file mode 100644 index 0000000..675a881 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/setPreference.dart @@ -0,0 +1,456 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_preference.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_screen.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:syncfusion_flutter_sliders/sliders.dart'; + +import '../hooks/use_settings.dart'; + +part 'setPreference.g.dart'; + +/// 偏好设置主页 +class SetPreference extends HookWidget { + final MarkingTextQuestionJob textQuestion; + final UsePreference usePreference; + final UseScreen useScreen; + final UseSettings useSettings; + final int taskId; + const SetPreference( + {required this.textQuestion, + required this.usePreference, + required this.useScreen, + required this.useSettings, + required this.taskId, + super.key}); + + @override + Widget build(BuildContext context) { + UseTemPreference _useTemPrefce = UseTemPreference.use(); + + useEffect(() { + _useTemPrefce.init(usePreference.preferenceState.value); + return () {}; + }, []); + + DoMarkingKeyboardModel tempPreference = _useTemPrefce.preferenceTemState.value; // 保存的偏好设置 + ScreenDirection orientation = tempPreference.screenDirection; // 当前方向 + bool isHorizontal = tempPreference.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横向 + + return Container( + height: double.infinity, + width: double.infinity, + color: const Color.fromARGB(255, 233, 232, 232), + child: Stack( + children: [ + Container( + color: const Color.fromRGBO(249, 250, 254, 1), + alignment: Alignment.center, + child: quickText( + "学而有道", + size: 170.sp, + color: Color.fromRGBO(255, 255, 255, 0.65), + ), + ), + Column( + children: [ + $HeadBox( + useTemPrefce: _useTemPrefce, + useSettings: useSettings, + usePreference: usePreference, + ), + Expanded( + child: Container( + width: double.infinity, + // color: const Color.fromARGB(255, 233, 232, 232), + padding: EdgeInsets.only(top: 12.h, left: 10.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + $KeyboardBox(orientation: orientation, useTemPrefce: _useTemPrefce), + $AutoNextQuestBox(useTemPrefce: _useTemPrefce), + // $ScoreIntervalSetBox( + // taskId: taskId, + // questionNum: textQuestion.questionNum, + // defaultNum: textQuestion.scoreInterval, + // useTemPrefce: _useTemPrefce, + // ), + $SetScoringRulesBox( + tempPreference: tempPreference, + isHorizontal: isHorizontal, + call: (SortKeyboard e) { + _useTemPrefce.preferenceTemState.value.sort = e; + _useTemPrefce.preferenceTemState.value = + DoMarkingKeyboardModel.fromJson(_useTemPrefce.preferenceTemState.value.toJson()); + }, + ), + ], + ), + ), + ) + ], + ) + ], + ), + ); + } +} + +// 获取键盘区域 +@swidget +Widget $keyboardBox( + BuildContext context, { + required ScreenDirection orientation, + required UseTemPreference useTemPrefce, +}) { + bool isHorizontal = orientation == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横向 + DoMarkingKeyboardModel tempPreference = useTemPrefce.preferenceTemState.value; + KeyboardType keyboard = tempPreference.keyboard; + int keyboardIndex = keyboard.index; + + List keyboardTypes = + KeyboardType.values.where((e) => e != KeyboardType.INPUT_TYPE).map((e) { + bool isCurrent = keyboard == e; // 当前使用的键盘是不是当前这个键盘 + bool isInput = e == KeyboardType.INPUT_TYPE; + + DoMarkingKeyboardModel item = DoMarkingKeyboardModel( + keyboard: e, + open: isInput ? tempPreference.open : true, // 只有输入型键盘可以开启关闭 + guidePageDisplay: isInput ? tempPreference.guidePageDisplay : false, // 只有输入型键盘有引导页 + ); + + if (isCurrent) { + item.sort = tempPreference.sort; + item.autoSubmitToNextQuestion = tempPreference.autoSubmitToNextQuestion; + } + return TheDoMarkingKeyboardModel(item, isCurrent); + }).toList(); + + selectKeyboardPreferences(TheDoMarkingKeyboardModel e, bool isInputAndHor) { + if (keyboard == e.model.keyboard || isInputAndHor) return; + tempPreference.keyboard = e.model.keyboard; + DoMarkingKeyboardModel model = e.model; + if (model.keyboard != KeyboardType.INPUT_TYPE) { + // 不重置,若后面需要重置再打开 + // scoreInterval = model.getScoreStepSize(widget.markingUserId, widget.questionNum, widget.defaultScoreInterval); + tempPreference.sort = model.sort; + } + useTemPrefce.preferenceTemState.value = DoMarkingKeyboardModel.fromJson(tempPreference.toJson()); + } + + return SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText( + '键盘选择', + color: const Color.fromRGBO(45, 56, 76, 1), + size: 14.sp, + fontWeight: FontWeight.w500, + ), + Wrap( + children: keyboardTypes.map((e) { + TheDoMarkingKeyboardModel item = e; + bool isInputAndHor = !isHorizontal && item.model.keyboard == KeyboardType.INPUT_TYPE; // 横屏和输入键盘 + return Padding( + padding: EdgeInsets.only(right: 36.w, left: isHorizontal ? 0 : 5.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8.w, + margin: EdgeInsets.only(right: isHorizontal ? 3.w : 8.w), + child: Checkbox( + tristate: isInputAndHor, // 三态复选框,默认 false,当设置为 true 时,设置 value = null,复选框中间会变成破折号(-) + activeColor: Theme.of(context).primaryColor, + checkColor: Colors.white, + value: isInputAndHor ? null : keyboard == e.model.keyboard, + onChanged: (value) => selectKeyboardPreferences(e, isInputAndHor), + ), + ), + InkWell( + onTap: () => selectKeyboardPreferences(e, isInputAndHor), + child: quickText( + item.title, + color: const Color.fromRGBO(80, 87, 103, 1), + size: 14.sp, + ), + ), + ], + ), + ); + }).toList(), + ), + SizedBox(height: 30.h), + ], + ), + ); +} + +// 自动跳转下一题 +@swidget +Widget $autoNextQuestBox(BuildContext context, {required UseTemPreference useTemPrefce}) { + DoMarkingKeyboardModel preferenceTem = useTemPrefce.preferenceTemState.value; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(preferenceTem.keyboard == KeyboardType.INPUT_TYPE ? '自动跳转下一题' : '自动提交', + size: 14.sp, color: const Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.bold), + Transform.scale( + scale: 0.7, + alignment: Alignment.centerLeft, + child: CupertinoSwitch( + value: preferenceTem.autoSubmitToNextQuestion, + activeColor: Theme.of(context).primaryColor, + trackColor: const Color.fromRGBO(148, 163, 182, 1), + onChanged: (value) { + preferenceTem.autoSubmitToNextQuestion = value; + useTemPrefce.preferenceTemState.value = DoMarkingKeyboardModel.fromJson(preferenceTem.toJson()); + }, + ), + ), + ], + ); +} + +// 给分规则区域 +@swidget +Widget $setScoringRulesBox(BuildContext context, + {required bool isHorizontal, + required DoMarkingKeyboardModel tempPreference, + required Function(SortKeyboard) call}) { + if (tempPreference.keyboard == KeyboardType.INPUT_TYPE) { + return Container(); + } + + SortKeyboard? sortType = tempPreference.sort; + return Container( + margin: EdgeInsets.only(top: 30.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText( + '设置常用给分', + size: 14.sp, + color: const Color.fromRGBO(45, 56, 76, 1), + fontWeight: FontWeight.bold, + ), + Wrap( + children: SortKeyboard.values.map((e) { + String title; + switch (e) { + case SortKeyboard.INVERTED_ORDER: + title = '按倒序排列'; + break; + case SortKeyboard.FULL_AND_AERO_TOP: + title = '顺序排列满分零分置顶'; + break; + case SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER: + title = '按倒序排列满分零分置顶'; + break; + } + return Padding( + padding: EdgeInsets.only(right: 30.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8.w, + margin: EdgeInsets.only(right: isHorizontal ? 3.w : 8.w, left: isHorizontal ? 0 : 5.w), + child: Checkbox( + activeColor: Theme.of(context).primaryColor, + checkColor: Colors.white, + value: e == sortType, + onChanged: (value) => call(e), + ), + ), + InkWell( + onTap: () => call(e), + child: quickText( + title, + color: const Color.fromRGBO(80, 87, 103, 1), + size: 14.sp, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); +} + +// 分值间隔设置 +@swidget +Widget $scoreIntervalSetBox( + BuildContext context, { + required UseTemPreference useTemPrefce, + required int taskId, + required String questionNum, + required double defaultNum, + double interval = 1, + double min = 0.5, + double? questTotalScore, +}) { + DoMarkingKeyboardModel preferenceTem = useTemPrefce.preferenceTemState.value; + + double scoreInterval = preferenceTem.getScoreStepSize(taskId, questionNum, defaultNum); + + // preferenceTem + KeyboardType keyboard = preferenceTem.keyboard; + if (keyboard == KeyboardType.INPUT_TYPE) { + return Container(); + } + + bool isHorizontal = preferenceTem.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否是横向 + + return Container( + margin: EdgeInsets.only(top: 30.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "分值间隔", + subTitle: "设定当前题目的分数按钮的间隔分值", + color: Theme.of(context).primaryColor, + ).show(context); + }, + child: Stack( + alignment: const FractionalOffset(1, 0), + children: [ + quickText( + '分值间隔设置 ', + size: 14.sp, + color: const Color.fromRGBO(45, 56, 76, 1), + fontWeight: FontWeight.bold, + ), + Tooltip( + message: '这是一个提示', + child: Icon(Icons.help, color: Colors.black, size: 18.sp), + ), + ], + ), + ), + SfSlider( + onChanged: (value) { + int flVal = value.floor(); + double differenceVal = value - flVal; + if (differenceVal != 0) { + value = differenceVal > 0.5 ? value.ceil().toDouble() : flVal + 0.5; + } + if (questTotalScore != null && value > questTotalScore) { + value = questTotalScore; + AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + icon: Icon( + Icons.warning_amber, + color: Colors.yellow, + size: 40.sp, + ), + title: "提示", + subTitle: "设定分值间隔不得大于当前分试题总分", + textStyleTitle: TextStyle(color: Colors.yellow), + color: Theme.of(context).primaryColor) + .show(context); + } + // toUpState(setState, () => scoreInterval = value, mounted); + }, + showTicks: true, + showLabels: true, + enableTooltip: true, + //值 + value: scoreInterval, + //滑块划过的颜色 + activeColor: Theme.of(context).primaryColor, + //滑块未划过的颜色 + inactiveColor: Colors.grey[400], + min: min, + max: questTotalScore ?? 5, + interval: interval, + ), + ], + ), + ); +} + +// 顶部区域 +@swidget +Widget $headBox( + BuildContext context, { + required UseTemPreference useTemPrefce, + required UsePreference usePreference, + required UseSettings useSettings, +}) { + return Container( + height: 46.h, + color: const Color.fromRGBO(255, 255, 255, 1), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 4.w), + InkWell( + onTap: () => useSettings.switchPreference(false), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: Icon(Icons.arrow_back_ios, size: 24.sp), + ), + ), + SizedBox(width: 4.w), + quickText('作业批改操作设置', size: 17.sp), + Expanded( + child: Container( + padding: EdgeInsets.only(right: 12.w), + alignment: Alignment.centerRight, + child: InkWell( + onTap: () async { + await usePreference.savePreference(tempModel: useTemPrefce.preferenceTemState.value); + useSettings.switchPreference(false); + }, + child: quickText('保存', size: 18.sp, color: Theme.of(context).primaryColor), + ), + ), + ) + ], + ), + ); +} + +class TheDoMarkingKeyboardModel { + final DoMarkingKeyboardModel model; + bool current; + late String title; + + TheDoMarkingKeyboardModel(this.model, this.current) { + switch (model.keyboard) { + case KeyboardType.INPUT_TYPE: + title = '输入型键盘'; + break; + case KeyboardType.RIGHT_SELECTION: + title = '右侧选择型键盘'; + break; + case KeyboardType.BOTTOM_SELECTION: + title = '底部选择型键盘'; + break; + } + } +} diff --git a/marking_app/lib/pages/homework_correction/components/theExamPaperDrawing.dart b/marking_app/lib/pages/homework_correction/components/theExamPaperDrawing.dart new file mode 100644 index 0000000..6dfa0fb --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/theExamPaperDrawing.dart @@ -0,0 +1,189 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/do_papers_job_custom_paint_size_bus.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_provider.dart'; +import 'package:marking_app/utils/my_text.dart'; + +part 'theExamPaperDrawing.g.dart'; + +class TheExamPaperDrawing extends ConsumerWidget { + final double boxHeight; + final double boxWidth; + final bool openEraser; + final String imageUrl; + final String? commentImageUrl; + final bool showAnnotations; + TheExamPaperDrawing( + {required this.imageUrl, + required this.commentImageUrl, + required this.showAnnotations, + required this.boxHeight, + required this.boxWidth, + required this.openEraser, + super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + var points = ref.watch(jobHomeWorkProvider); + if (showAnnotations && points.isNotEmpty) { + return CustomPaint( + foregroundPainter: DrawingPainter(points: points, isErasing: openEraser), + child: RepaintBoundary( + child: Container( + color: Colors.transparent, + alignment: Alignment.center, + constraints: BoxConstraints(minWidth: boxWidth), + child: $TheCachedNetworkImage( + (context, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth), + imageUrl: commentImageUrl!, + ), + ), + ), + ); + } + return CustomPaint( + painter: DrawingPainter(points: points, isErasing: openEraser), + // child: CustomPaintChildSize(boxWidth: boxWidth), + child: Container( + color: Colors.transparent, + constraints: BoxConstraints(minWidth: boxWidth), + child: CustomPaintChildSize(boxWidth: boxWidth), + ), + ); + } +} + +class CustomPaintChildSize extends StatefulWidget { + final double boxWidth; + const CustomPaintChildSize({required this.boxWidth, super.key}); + + @override + State createState() => _CustomPaintChildSizeState(); +} + +class _CustomPaintChildSizeState extends State with EventBusMixin { + double? theHeight; + + @override + void initState() { + super.initState(); + eventOn(callback: (DoPapersJobCustomPaintSizeBus val) { + theHeight = val.theHeight; + setState(() {}); + }); + } + + @override + void dispose() { + super.dispose(); + eventCancel(); + } + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.transparent, + height: theHeight, + width: widget.boxWidth, + ); + } +} + +class DrawingPainter extends CustomPainter { + final List points; + final bool isErasing; + final bool openErasing; + + DrawingPainter({required this.points, required this.isErasing}) + : openErasing = points.isNotEmpty && isErasing, + super(); + + Paint paintBrush = Paint() + ..color = Colors.red + ..strokeCap = StrokeCap.round + ..strokeWidth = 0.6.sp; + + // Paint eraser = Paint() + // ..blendMode = BlendMode.clear + // ..color = Colors.transparent + // ..style = PaintingStyle.stroke + // ..strokeCap = StrokeCap.round + // ..strokeWidth = 100; + + // final emptyPaint = Paint(); + // final emptyPaintWithWidth = Paint()..strokeWidth = 60.0; + + @override + void paint(Canvas canvas, Size size) { + /** + if (points.isNotEmpty) { + // canvas.saveLayer(destRect, emptyPaintWithWidth); // 只绘制图片大小区域 + canvas.saveLayer(Rect.largest, emptyPaintWithWidth); // 整个视图区域 + + canvas.drawColor(Colors.transparent, BlendMode.clear); + } */ + + if (points.length > 1) { + for (int i = 0; i < points.length - 1; i++) { + GestureRecording item = points[i]; + GestureRecording nextItem = points[i + 1]; + Offset? offsetData = item.data; + Offset? nextOffsetData = nextItem.data; + if (offsetData != null && nextOffsetData != null) { + // canvas.drawLine( + // offsetData, nextOffsetData, !item.eraser ? paintBrush : eraser); + + canvas.drawLine(offsetData, nextOffsetData, paintBrush); + } + } + } + // canvas.drawPoints(PointMode.polygon, offsetPoints, paintBrush); + // 恢复画布状态. + // if (points.isNotEmpty) canvas.restore(); + } + + // @override + // bool shouldRepaint(DrawingPainter oldDelegate) { + // List thePoints = oldDelegate.points; + // // var flag = oldDelegate.points != points || oldDelegate.isErasing != isErasing; + // return thePoints != points; + // } + + @override + bool shouldRepaint(DrawingPainter oldDelegate) => true; +} + +@hwidget +Widget $theCachedNetworkImage(ImageWidgetBuilder imageBuilder, {required String imageUrl}) { + UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use(); + + return CachedNetworkImage( + key: _useImgRefsh.imageKey.value, + fit: BoxFit.fitWidth, + width: double.infinity, + imageUrl: imageUrl, + imageBuilder: imageBuilder, + placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), + errorWidget: (context, url, error) { + return GestureDetector( + onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset('assets/images/test_paper_loading_failed.png'), + quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), + ], + ), + ); + }, + ); +} diff --git a/marking_app/lib/pages/homework_correction/components/trajectoryView.dart b/marking_app/lib/pages/homework_correction/components/trajectoryView.dart new file mode 100644 index 0000000..03f0936 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/components/trajectoryView.dart @@ -0,0 +1,317 @@ +import 'dart:async'; + +import 'package:async/async.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:marking_app/common/model/job/job_note_taking_trajectory.dart'; +import 'package:marking_app/common/model/job/test_questions_image_info.dart'; +import 'package:marking_app/pages/homework_correction/hooks/use_cached_img_refresh.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_trajectory_provider.dart'; +import 'package:marking_app/utils/my_text.dart'; + +part 'trajectoryView.g.dart'; + +class TrajectoryView extends StatefulHookConsumerWidget { + final double boxWidth; + final double boxHeight; + final String questionNumber; + final JobNoteTakingTrajectory trajectory; + const TrajectoryView( + {required this.trajectory, + required this.questionNumber, + required this.boxHeight, + required this.boxWidth, + super.key}); + + @override + TrajectoryViewState createState() => TrajectoryViewState(); +} + +class TrajectoryViewState extends ConsumerState { + String? oldQuestionNumber; + ImageStream? imageStream; // 图片监听数据 + TestQuestionsImageInfo? imagInfoModel; // 试题图片数据 + late ImageStreamListener theImageStreamListener; + dynamic delayedFuture; + List? delayedCalls; + + @override + void initState() { + print('初始化笔记回显页面.....................'); + super.initState(); + oldQuestionNumber = widget.questionNumber; + print('进入页面.......21211'); + + theImageStreamListener = ImageStreamListener((ImageInfo info, bool _) { + if (imagInfoModel != null && widget.questionNumber == oldQuestionNumber) return; + oldQuestionNumber = widget.questionNumber; + imagInfoModel = TestQuestionsImageInfo( + // 获取图片的宽高 + boxHeight: widget.boxHeight, + boxWidth: widget.boxWidth, + url: widget.trajectory.paperPicture, + height: info.image.height.toDouble(), + width: info.image.width.toDouble(), + ); + getCalculatedSize(widget.questionNumber); + }); + imageStream?.removeListener(theImageStreamListener); + } + + ddd() async { + await Future.doWhile(() async { + return false; + }); + } + + @override + void dispose() { + print('销毁笔记回显页面.....................'); + super.dispose(); + try { + imageStream?.removeListener(theImageStreamListener); + } catch (e) {} + } + + // ignore: slash_for_doc_comments + /** + Future zhixinCall( + List trajectorys, + List? nextStrokesData, + GestureRecording theRecording, + GestureRecording nextRecording, + ) async { + var duration1 = Duration(milliseconds: theRecording.usageTime!); + var duration2 = nextRecording.usageTime != null ? Duration(milliseconds: nextRecording.usageTime!) : null; + var differenceInMilliseconds = 0; + if (duration2 != null) differenceInMilliseconds = (duration2 - duration1).inMilliseconds; + await Future.delayed(Duration(milliseconds: differenceInMilliseconds)); + if (mounted) { + ref.read(jobDrawingTrajectoryProvider.notifier).setVal(trajectorys..add(theRecording)); + } + if (duration2 == null && nextStrokesData != null) { + // 此时最后一个笔画完成了 停止时间是在下一个笔画开始时间之差 + Lattices minLattices = nextStrokesData.reduce((e1, e2) => e1.time < e2.time ? e1 : e2); + differenceInMilliseconds = (Duration(milliseconds: minLattices.time.toInt()) - duration1).inMilliseconds; + // 加此句代码就是每一笔的停顿时间 ,不加就是每一笔不停顿 + await Future.delayed(Duration(milliseconds: differenceInMilliseconds)); // 一个笔画完成进行下一个笔画的等待时间 + } + } + */ + + // 计算尺寸 + Future getCalculatedSize(String questionNumber) async { + if (imagInfoModel == null) return; + double width = imagInfoModel!.width; + double height = imagInfoModel!.height; + double imageScale = width / height; + double imageScaleFixed = double.parse(imageScale.toStringAsFixed(2)); + + double boxWidth = imagInfoModel!.boxWidth; + double boxHeight = imagInfoModel!.boxHeight; + double boxScale = boxWidth / boxHeight; + double boxScaleFixed = double.parse(boxScale.toStringAsFixed(2)); + + double theScaleDifference = boxScaleFixed - imageScaleFixed; // 相差 + + if (imagInfoModel!.scaleHeight! > boxHeight) { + // 默认比例计算为宽铺满屏幕,但是宽铺满屏幕高要超出,所以为全屏展示完整图片就高为基准 缩放图片 重置倍数 + imagInfoModel!.scale = imagInfoModel!.boxHeight / height; // 缩放倍数 + imagInfoModel!.scaleWidth = imagInfoModel!.scale! * width; + imagInfoModel!.scaleHeight = imagInfoModel!.scale! * height; + getCalculatedSize(questionNumber); + } else if (imagInfoModel!.scaleHeight! < boxHeight) { + // 默认计算模式 + } else {} + + double spacingHeight = 0; + double spacingWidth = 0; + + if (theScaleDifference > 0) { + // 容器大于图片宽高比例 则图片将以高度为基准进行拉伸,以适应容器的高度。 + if (imagInfoModel!.scaleWidth! < imagInfoModel!.boxWidth) { + spacingWidth = (imagInfoModel!.boxWidth - imagInfoModel!.scaleWidth!) / 2; + } + } else if (theScaleDifference < 0) { + // 容器小于图片宽高比例, 则图片将以宽度为基准进行拉伸,以适应容器的宽度。 + if (imagInfoModel!.scaleHeight! < imagInfoModel!.boxHeight) { + spacingHeight = (imagInfoModel!.boxHeight - imagInfoModel!.scaleHeight!) / 2; + } + } else { + // 容器等于图片比例 图片会拉升或者缩小图片铺满整个容器 此时图片展示宽高就等于容器宽高 + } + + var trajectory = widget.trajectory; + List lattices = trajectory.lattices; + if (lattices.isNotEmpty) { + Map> map = new Map.fromIterable(lattices, + key: (key) => key.stroke, + value: (value) { + return lattices.where((item) => item.stroke == value.stroke).toList() + ..sort((a, b) => a.time.compareTo(b.time)); + }); + List trajectorys = []; + List keys = map.keys.toList(); + for (var i = 0; i < keys.length; i++) { + int theKey = keys[i]; + int? nextKey = i + 1 < keys.length ? keys[i + 1] : null; + List strokesData = map[theKey]!; + List? nextStrokesData = nextKey != null ? map[nextKey]! : null; + + List newTrajectoryData = strokesData.map((e) { + double theX = e.x * imagInfoModel!.scale!; + double theY = e.y * imagInfoModel!.scale! + spacingHeight; + + return GestureRecording( + eraser: false, data: Offset(theX, theY), usageTime: e.time.toInt(), annotationSwitch: false); + }).toList() + ..add(GestureRecording(eraser: false, annotationSwitch: false)); + // 原始轨迹展示 + for (var j = 0; j < newTrajectoryData.length; j++) { + GestureRecording theRecording = newTrajectoryData[j]; + GestureRecording? nextRecording = j + 1 < newTrajectoryData.length ? newTrajectoryData[j + 1] : null; + + if (nextRecording == null) { + // 这个是此个笔画最后一个动作 + // 笔画最后一个动作是data 是null 用时 是null + if (mounted) { + trajectorys = List.from(trajectorys)..add(theRecording); + ref.read(jobDrawingTrajectoryProvider.notifier).setVal(trajectorys); + } + } else { + var duration1 = Duration(milliseconds: theRecording.usageTime!); + var duration2 = nextRecording.usageTime != null ? Duration(milliseconds: nextRecording.usageTime!) : null; + var differenceInMilliseconds = 0; + if (duration2 != null) differenceInMilliseconds = (duration2 - duration1).inMilliseconds; + + var zhixinCall = () async { + if (mounted) { + print('执行添加笔画${i},${j}'); + trajectorys = List.from(trajectorys)..add(theRecording); + ref.read(jobDrawingTrajectoryProvider.notifier).setVal(trajectorys); + } + if (duration2 == null && nextStrokesData != null) { + // 此时最后一个笔画完成了 停止时间是在下一个笔画开始时间之差 + Lattices minLattices = nextStrokesData.reduce((e1, e2) => e1.time < e2.time ? e1 : e2); + differenceInMilliseconds = + (Duration(milliseconds: minLattices.time.toInt()) - duration1).inMilliseconds; + await Future.delayed(Duration(milliseconds: differenceInMilliseconds)); // 一个笔画完成进行下一个笔画的等待时间 + } + }; + await Future.delayed(Duration(milliseconds: differenceInMilliseconds)); + // 加 await 就是每一笔的停顿时间 ,不加就是每一笔不停顿 + zhixinCall(); + /** + Timer t = Timer(Duration(milliseconds: differenceInMilliseconds), () { + + }); + if (delayedCalls == null) { + delayedCalls = []; + } + delayedCalls?.add(t); + + Timer t = Timer(Duration(milliseconds: differenceInMilliseconds), () => zhixinCall()); + add(t); // 添加到这里 用于切换试题后销毁未执行的任务 + */ + } + } + } + } + } + + @override + Widget build(BuildContext context) { + List points = ref.watch(jobDrawingTrajectoryProvider); + + return Container( + width: widget.boxWidth, + height: widget.boxHeight, + alignment: Alignment.center, + child: CustomPaint( + foregroundPainter: DrawingPainter(points: points), + size: Size(widget.boxWidth, widget.boxHeight), + child: RepaintBoundary( + child: Container( + constraints: BoxConstraints(minHeight: widget.boxHeight), + child: $TheCachedNetworkImage( + (context, imageProvider) { + Image imageWidget = Image(image: imageProvider, fit: BoxFit.contain); + + if (imagInfoModel == null || widget.questionNumber != oldQuestionNumber) { + imageStream?.removeListener(theImageStreamListener); + // 视图中展示图片的尺寸计算获取 + imageStream = imageWidget.image.resolve(ImageConfiguration()); + imageStream?.addListener(theImageStreamListener); + } + + return imageWidget; + }, + imageUrl: widget.trajectory.paperPicture, + ), + ), + ), + ), + ); + } +} + +class DrawingPainter extends CustomPainter { + final List points; + + DrawingPainter({required this.points}) : super(); + + Paint paintBrush = Paint() + ..color = Colors.black + ..strokeCap = StrokeCap.round + ..strokeWidth = 0.5.sp; + + @override + void paint(Canvas canvas, Size size) { + if (points.length > 1) { + for (int i = 0; i < points.length - 1; i++) { + GestureRecording item = points[i]; + GestureRecording nextItem = points[i + 1]; + Offset? offsetData = item.data; + Offset? nextOffsetData = nextItem.data; + if (offsetData != null && nextOffsetData != null) { + canvas.drawLine(offsetData, nextOffsetData, paintBrush); + } + } + } + } + + @override + bool shouldRepaint(DrawingPainter oldDelegate) => true; +} + +@hwidget +Widget $theCachedNetworkImage(ImageWidgetBuilder imageBuilder, {required String imageUrl}) { + UseCachedImgRefresh _useImgRefsh = UseCachedImgRefresh.use(); + + return CachedNetworkImage( + key: _useImgRefsh.imageKey.value, + fit: BoxFit.contain, + imageUrl: imageUrl, + imageBuilder: imageBuilder, + placeholder: (context, url) => Center(child: SpinKitWave(color: Theme.of(context).primaryColor, size: 50.r)), + errorWidget: (context, url, error) { + return GestureDetector( + onTap: () => (_useImgRefsh.imageKey.value = UniqueKey()), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset('assets/images/test_paper_loading_failed.png'), + quickText('加载失败,点击重试', color: Color.fromRGBO(148, 163, 182, 1), size: 12.sp), + ], + ), + ); + }, + ); +} diff --git a/marking_app/lib/pages/homework_correction/do_papers_job.dart b/marking_app/lib/pages/homework_correction/do_papers_job.dart new file mode 100644 index 0000000..0283d18 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/do_papers_job.dart @@ -0,0 +1,843 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/event_bus/jobs/job_do_papers_student_bus.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student.dart'; +import 'package:marking_app/common/model/job/job_page_tab.dart'; +import 'package:marking_app/common/model/job/job_review_submission.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job_tab_params.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/components/new_version_of_homework/bottom_annotation_switch_job.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/marking_text_question_job_tab_params_bus.dart'; +import 'package:marking_app/pages/homework_correction/providers/drawing_provider.dart'; +import 'package:marking_app/utils/index.dart'; + +// utils +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +// HOOKS +import 'components/jobPictureOverview.dart'; +import 'components/new_version_of_homework/favorite_widget.dart'; +import 'eventBus/do_papers_job_refresh_bus.dart'; +import 'eventBus/job_check_switching_question_tab_bus.dart'; +import 'eventBus/job_notes_view_bus.dart'; +import 'hooks/do_papers_job/use_do_paper_scoring.dart'; +import 'hooks/do_papers_job/use_switch_student_and_type.dart'; + +// COMPONENTS +import 'components/new_version_of_homework/button_floating_action.dart'; +import 'providers/do_job_obtain_grading_data_provider.dart'; + +part 'do_papers_job.g.dart'; + +// 作业批阅 +class DoPapersJob extends HookWidget with EventBusMixin { + final int taskId; // 任务ID + final String taskName; + final String className; // 班级ID + DoPapersJob({required this.taskId, required this.taskName, required this.className, super.key}); + + bool refresh = false; + @override + Widget build(BuildContext context) { + useEffect(() { + // SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + WidgetsFlutterBinding.ensureInitialized(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + eventOn(callback: (DoPapersJobRefreshBus eventModel) { + refresh = eventModel.flagRefresh; + }); + return () { + eventCancel(); + }; + }, []); + return Scaffold( + appBar: AppBar( + // titleSpacing: 0, + leading: IconButton(icon: Icon(Icons.arrow_back_ios), onPressed: () => Navigator.of(context).pop(refresh)), + iconTheme: IconThemeData(color: Colors.black), + title: quickText(taskName + '($className)'), + backgroundColor: Colors.white, + elevation: 0, + actions: [ + FavoriteWidget(), + // Container( + // padding: EdgeInsets.only(right: 4.w), + // alignment: Alignment.center, + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [Icon(Icons.lightbulb_outline, size: 12.sp), SizedBox(width: 1.w), quickText('查看原卷')], + // ), + // ), + ], + ), + body: SafeArea( + child: Column( + children: [ + // 下拉切换 + $DropdownBoxSwitchStudentsOrTypeView( + taskId: taskId, + exitCallback: () => Navigator.of(context).pop(refresh), + ), + SizedBox(height: 1.h), + Expanded(child: ExamPaperAndScoringView(taskId: taskId)), + $BottomOperationBar() + ], + ), + ), + floatingActionButton: ButtonFloatingAction(), + floatingActionButtonLocation: FloatingActionButtonLocation.startFloat, + ); + } +} + +// 切换下拉框 (学生和试卷状态) +@hwidget +Widget $dropdownBoxSwitchStudentsOrTypeView(BuildContext context, + {required Function() exitCallback, required int taskId}) { + UseSwitchStudentAndType _useSwitchStudentAndType = UseSwitchStudentAndType.use(); // 学生和试卷状态 + + // 当前tab改变时 + useValueChanged(_useSwitchStudentAndType.currentTab.value, (_, __) { + var thePageIndex = _useSwitchStudentAndType.currentTab.value?.pageIndex; + JobDoPapersStudentBus? studentBus = _useSwitchStudentAndType.studentBusInfo.value; + if (studentBus != null && studentBus.pageIndex == thePageIndex) return; // 获取试卷详情数据 定位到对应学生的数据 无需再通知详情获取数据 + if (thePageIndex != null) { + int indexLocated = _useSwitchStudentAndType.tabs.value.indexWhere((element) => element.pageIndex == thePageIndex); + _useSwitchStudentAndType.eventFire( + model: MarkingTextQuestionJobTabParamsBus(taskId, thePageIndex, + nextPageIndex: _useSwitchStudentAndType.getNextPageIndex(indexLocated), + previousPageIndex: _useSwitchStudentAndType.getPreviousPageIndex(indexLocated))); + } + }); + useValueChanged(_useSwitchStudentAndType.currentStudent.value, (_, __) { + var studentId = _useSwitchStudentAndType.currentStudent.value?.studentId; + JobDoPapersStudentBus? studentBus = _useSwitchStudentAndType.studentBusInfo.value; + if (studentBus != null && studentBus.studentId == studentId) return; // 获取试卷详情数据 定位到对应学生的数据 无需再通知详情获取数据 + + var thePageIndex = _useSwitchStudentAndType.currentTab.value?.pageIndex; + if (thePageIndex != null) // 通知详情数据 + _useSwitchStudentAndType.eventFire( + model: MarkingTextQuestionJobTabParamsBus(taskId, thePageIndex, + studentId: studentId, + nextPageIndex: _useSwitchStudentAndType.getNextPageIndex(thePageIndex), + previousPageIndex: _useSwitchStudentAndType.getPreviousPageIndex(thePageIndex)), + ); + }); + + useEffect(() { + _useSwitchStudentAndType.getData(taskId: taskId); + _useSwitchStudentAndType.eventOn( + callback: (val) { + switch (val.runtimeType) { + case JobDoPapersStudentBus: + var studentInfo = val as JobDoPapersStudentBus; + _useSwitchStudentAndType.studentBusInfo.value = studentInfo; // 赋值历史记录 + var selectedStudent = _useSwitchStudentAndType.studentData.value + .firstWhereOrNull((element) => element.studentId == studentInfo.studentId); + if (selectedStudent == null) { + // 当前学生集合中没有此学生 + selectedStudent = JobConcernedWithStudent.fromJson(studentInfo.toJson()); + _useSwitchStudentAndType.studentData.value = [ + ..._useSwitchStudentAndType.studentData.value, + selectedStudent + ]; + _useSwitchStudentAndType.getDataForStudents(taskId: taskId); + } + _useSwitchStudentAndType.currentStudent.value = selectedStudent; + print('是否是优先批阅:${studentInfo.isFirst}'); + _useSwitchStudentAndType.isFirst.value = studentInfo.isFirst; + _useSwitchStudentAndType.currentTab.value = + _useSwitchStudentAndType.tabs.value.firstWhere((e) => e.pageIndex == studentInfo.pageIndex); + print('是否是优先批阅1111:${_useSwitchStudentAndType.isFirst}'); + break; + case JobCheckSwitchingQuestionTabBus: + // 检查切换试题体型页码 + _useSwitchStudentAndType.refreshQuestionTypeData(context, taskId: taskId, exitCallback: exitCallback); + break; + case MarkingTextQuestionJobTabParams: // 切换试题通知 + var moldeParams = val as MarkingTextQuestionJobTabParams; + _useSwitchStudentAndType.currentTab.value = + _useSwitchStudentAndType.tabs.value.firstWhere((element) => moldeParams.pageIndex == element.pageIndex); + break; + default: + } + }, + ); + return () { + _useSwitchStudentAndType.eventCancel(); + }; + }, []); + + print('加载的是否优先批阅:${_useSwitchStudentAndType.isFirst}'); + return Container( + padding: EdgeInsets.only(bottom: 2.r, left: 12.r, right: 12.r), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(2.w, 2.h), //阴影y轴偏移量 + blurRadius: 14, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: Row( + children: [ + Expanded( + flex: 7, + child: Container( + padding: EdgeInsets.only(left: 10.w), + decoration: BoxDecoration( + color: Color.fromRGBO(244, 244, 244, 1), + borderRadius: BorderRadius.circular(4.r), + ), + child: DropdownButton( + isExpanded: true, + underline: Container(), + padding: EdgeInsets.only(right: 4.w), + icon: Icon(Icons.keyboard_arrow_down_rounded), + value: _useSwitchStudentAndType.currentTab.value?.pageIndex, + hint: Text('请选择作业页码'), // 锚点的显示文本 + onTap: () { + print('点击选择作业页码.............'); + // 打开的时候请求刷新学生 + }, + items: _useSwitchStudentAndType.tabs.value.map((e) { + return DropdownMenuItem( + child: quickText('${e.pageIndex}页', color: Color.fromRGBO(79, 79, 79, 1), size: 14.sp), + value: e.pageIndex, + ); + }).toList(), + onChanged: (value) { + _useSwitchStudentAndType.currentTab.value = + _useSwitchStudentAndType.tabs.value.firstWhere((element) => element.pageIndex == value); + }, + ), + ), + ), + Expanded(flex: 1, child: SizedBox()), + Expanded( + flex: 7, + child: Container( + padding: EdgeInsets.only(left: 10.w), + decoration: BoxDecoration( + color: Color.fromRGBO(244, 244, 244, 1), + borderRadius: BorderRadius.circular(4.r), + ), + child: DropdownButton( + onTap: () { + // 打开的时候请求刷新学生 + _useSwitchStudentAndType.getDataForStudents(taskId: taskId); + }, + padding: EdgeInsets.only(right: 4.w), + icon: Icon(Icons.keyboard_arrow_down_rounded), + value: _useSwitchStudentAndType.currentStudent.value?.studentId, + underline: Container(), + isExpanded: true, + items: _useSwitchStudentAndType.studentData.value.map((e) { + return DropdownMenuItem( + child: quickText(e.studentName, color: Color.fromRGBO(79, 79, 79, 1), size: 14.sp), + value: e.studentId, + ); + }).toList(), + hint: Text('请选择学生'), // 锚点的显示文本 + onChanged: (value) { + JobConcernedWithStudent? currentStudent = _useSwitchStudentAndType.currentStudent.value; + if (currentStudent?.studentId == value) return; + _useSwitchStudentAndType.studentBusInfo.value = null; // 置空BUS通知记录 + _useSwitchStudentAndType.currentStudent.value = _useSwitchStudentAndType.studentData.value + .firstWhereOrNull((element) => element.studentId == value); + }, + ), + ), + ), + Expanded(flex: 1, child: SizedBox()), + Expanded( + flex: 3, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Stack( + alignment: const FractionalOffset(0.52, 0.24), + children: [ + Icon( + const IconData(0xe63d, fontFamily: "AlibabaIcon"), + size: 12.sp, + color: _useSwitchStudentAndType.isFirst.value + ? Color.fromRGBO(76, 199, 147, 1) + : Color.fromRGBO(164, 164, 164, 1), + ), + quickText('优先', size: 4.sp, color: Colors.white), + ], + ), + SizedBox(width: 1.2.w), + quickText( + '优先批阅', + size: 10.sp, + color: _useSwitchStudentAndType.isFirst.value + ? Color.fromRGBO(76, 199, 147, 1) + : Color.fromRGBO(164, 164, 164, 1), + ), + ], + ), + ), + ], + ), + ); +} + +// 试卷和打分键盘栏 +class ExamPaperAndScoringView extends StatefulHookConsumerWidget { + final int taskId; + const ExamPaperAndScoringView({required this.taskId, super.key}); + + @override + _EexamPaperAndScoringViewState createState() => _EexamPaperAndScoringViewState(); +} + +class _EexamPaperAndScoringViewState extends ConsumerState with EventBusMixin, CommonMixin { + final GlobalKey scaffoldKeyPictureOverview = GlobalKey(); + Future? _future; // 考试试卷 + bool firstLoading = true; + late RemoveListener _doJobObtainGradingDataProviderListener; // 批注关闭监听 + + @override + void initState() { + _doJobObtainGradingDataProviderListener = ref.read(doJobObtainGradingDataProvider.notifier).addListener((state) { + if (state == null) return; + _future = _getData(state); + + toUpState(setState, () {}, mounted); + }, fireImmediately: false); + eventOn(callback: (eventVal) { + switch (eventVal.runtimeType) { + case MarkingTextQuestionJobTabParamsBus: + MarkingTextQuestionJobTabParamsBus theEventVal = eventVal as MarkingTextQuestionJobTabParamsBus; + ref.read(doJobObtainGradingDataProvider.notifier).setVal(MarkingTextQuestionJobTabParams( + pageIndex: theEventVal.pageIndex, + taskId: theEventVal.taskId, + studentId: theEventVal.studentId, + previousPageIndex: theEventVal.previousPageIndex, + nextPageIndex: theEventVal.nextPageIndex, + )); + break; + default: + } + }); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + try { + _doJobObtainGradingDataProviderListener(); + } catch (e) {} + eventCancel(); + } + + Future _getData(MarkingTextQuestionJobTabParams params) async { + try { + RestClient _client = await getClient(); + BaseStructureResult _result = await _client.getJobTabOfExam(params); + if (_result.success) { + var jobDetails = _result.data; + if (jobDetails != null) { + jobDetails.previousPageIndex = params.previousPageIndex; + jobDetails.nextPageIndex = params.nextPageIndex; + // 触发学生下拉选中 + try { + // 清空已有数据 + ref.read(jobHomeWorkProvider.notifier).setVal([]); + } catch (e) {} + + eventFire(model: JobQuestionsSwitch(jobDetails.taskId, jobDetails.studentId)); + eventFire( + model: JobDoPapersStudentBus( + jobDetails.studentId, jobDetails.studentName, jobDetails.pageIndex, jobDetails.isFirst)); + } + return jobDetails; + } + } catch (e) { + print(e); + } + + return null; + } + + /// 跳转试题 + void switchTestQuestions({required MarkingTextQuestionJob jobData, bool toNextQuestion = true}) { + if (toNextQuestion) { + // 下一题 + var model = MarkingTextQuestionJobTabParams( + // 默认当前题型下标和当前题型下下一题没有下一题paperId为0 + taskId: widget.taskId, + paperId: jobData.nextId, + pageIndex: jobData.pageIndex, + nextPageIndex: jobData.nextPageIndex, + previousPageIndex: jobData.previousPageIndex, + ); + if (model.paperId == 0 && jobData.nextPageIndex != null) { + // 切换题型页面 + model.paperId = null; + model.pageIndex = jobData.nextPageIndex!; + return eventFire(model: model); + } + return ref.read(doJobObtainGradingDataProvider.notifier).setVal(model); + } + + var model = MarkingTextQuestionJobTabParams( + taskId: widget.taskId, + pageIndex: jobData.pageIndex, + paperId: jobData.prevId, + nextPageIndex: jobData.nextPageIndex, + previousPageIndex: jobData.previousPageIndex, + ); + if (model.paperId == 0 && jobData.previousPageIndex != null) { + model.paperId = null; + model.pageIndex = jobData.previousPageIndex!; + return eventFire(model: model); + } + + ref.read(doJobObtainGradingDataProvider.notifier).setVal(model); + } + + @override + Widget build(BuildContext context) { + if (_future == null) return Center(child: MyFutureBuilder.getBldLoad(context)); + + return MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _future!, (jobData) { + List questions = jobData?.questions ?? []; + + bool canNormalPrevious = + jobData != null && (jobData.prevId != 0 || jobData.previousPageIndex != null); //是否可以正常点击切换上一题 + bool canNormalNext = jobData != null && (jobData.nextId != 0 || jobData.nextPageIndex != null); //是否可以正常点击切换下一题 + + return Stack( + children: [ + Container( + child: Row(children: [ + Expanded( + flex: 7, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final double containerWidth = constraints.maxWidth; // 展示区域总宽度 + final double containerHeight = constraints.maxHeight; // 展示区域总宽度 + + if (jobData == null) return Container(); // 没有试题展示缩略图 + + print('容器宽度:${containerWidth};容器高度:${containerHeight}'); + return Stack( + children: [ + JobPictureOverview( + jobData: jobData, + key: scaffoldKeyPictureOverview, + containerWidth: containerWidth, + containerHeight: containerHeight, + ), + if (canNormalPrevious) + Positioned( + left: 3.w, + top: (containerHeight / 2) - 20.h, + child: FloatingActionButton( + heroTag: '点击前往上一题', + tooltip: '点击前往上一题', + focusColor: Theme.of(context).primaryColor, + backgroundColor: const Color.fromRGBO(24, 32, 32, 0.1), + elevation: 6.r, + onPressed: () => easyThrottle('TestQuestionSwitch', + () => switchTestQuestions(jobData: jobData, toNextQuestion: false)), + child: Icon(Icons.arrow_back_ios, color: Colors.white, size: 22.sp), + ), + ), + // 下一题 按钮 + if (canNormalNext) + Positioned( + right: 2.w, + top: (containerHeight / 2) - 20.h, + child: FloatingActionButton( + heroTag: '点击前往下一题', + tooltip: '点击前往下一题', + elevation: 6.r, + backgroundColor: const Color.fromRGBO(24, 32, 32, 0.1), + onPressed: () => + easyThrottle('TestQuestionSwitch', () => switchTestQuestions(jobData: jobData)), + child: Icon(Icons.arrow_forward_ios, color: Colors.white, size: 22.sp), + ), + ), + ], + ); + }, + ), + ), + Expanded( + flex: 2, + child: Container( + padding: EdgeInsets.symmetric(vertical: 14.h), + decoration: BoxDecoration( + color: Color.fromRGBO(159, 159, 159, 0.97), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(0, 8.w), //阴影y轴偏移量 + blurRadius: 1, //阴影模糊程度 + spreadRadius: 2, //阴影扩散程度 + ) + ], + ), + child: (jobData?.questions ?? []).isEmpty + ? null + : $ExamPaperAndScoringKeyboardView( + data: jobData!, + questions: questions, + toNextQuestionCall: () => switchTestQuestions(jobData: jobData), + switchQuestionTypes: () { + eventFire(model: JobCheckSwitchingQuestionTabBus(jobData.pageIndex)); + }, + viewHomeworkNotes: (JobReviewQuestions subJobQuestion) { + var _theBusModel = JobNotesViewBus( + taskId: jobData.taskId, + paperId: jobData.paperId, + questionNo: subJobQuestion.questionNo); + eventFire(model: _theBusModel); + }, + scaffoldKeyPictureOverview: scaffoldKeyPictureOverview, + ), + ), + ), + ]), + ), + // TODO 试题详情 错误提示 + if (jobData == null) + $examPaperAndScoringErrorPromptView(() { + var params = ref.read(doJobObtainGradingDataProvider); + if (params == null) return; + setState(() => (_future = _getData(params))); + }) + ], + ); + }); + } +} + +// 右侧键盘 +@hwidget +Widget $examPaperAndScoringKeyboardView( + BuildContext context, { + required MarkingTextQuestionJob data, + required List questions, + required void Function() toNextQuestionCall, + required void Function() switchQuestionTypes, + required void Function(JobReviewQuestions subJobQuestion) viewHomeworkNotes, // 查看笔记 + required GlobalKey scaffoldKeyPictureOverview, +}) { + var _useDoScoring = UseDoPaperScoring.use(data: data); + + /// 提交 + Future toSubmit() async { + _useDoScoring.submitForReview(context, scaffoldKeyPictureOverview).then((value) { + if (!value) return; + + _useDoScoring.eventFire(model: DoPapersJobRefreshBus(flagRefresh: true)); + var nextId = data.nextId; + if (nextId == 0) { + // 没有下一道题了 判断是否需要跳转其他体型页面 + switchQuestionTypes(); + return; + } + // 有下一道题 自动跳转下一道题 + toNextQuestionCall(); + }); + } + + useEffect(() { + _useDoScoring.eventOn(callback: (JobNotesViewBus busModel) { + JobNotesViewBus? oldBusModel = _useDoScoring.useStateBusData.value; + if (oldBusModel != null) { + if (oldBusModel.questionNo == busModel.questionNo) { + _useDoScoring.useStateBusData.value = null; + return; + } + } + _useDoScoring.useStateBusData.value = busModel; + }); + return () { + _useDoScoring.eventCancel(); + }; + }, []); + + return Column( + children: [ + Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.r), + child: $MaterialBtn( + bgc: Colors.white, + borderRadius: BorderRadius.circular(2.r), + onTap: () => easyThrottle('homework_review_submission_callback', () { + _useDoScoring.goToScoringShortcut(enumMode: ToScoringShortcut.ALL_PAIRS); + // toSubmit(); + }, duration: Duration(milliseconds: 500)), + child: Container( + width: double.infinity, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8.r), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(2.r)), + child: quickText( + '全对', + size: 12.sp, + color: Color.fromRGBO(81, 81, 81, 1), + fontWeight: FontWeight.bold, + ), + ), + ), + ), + SizedBox(height: 7.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.r), + child: $MaterialBtn( + bgc: Colors.white, + borderRadius: BorderRadius.circular(2.r), + onTap: () => easyThrottle('homework_review_submission_callback', () { + _useDoScoring.goToScoringShortcut(enumMode: ToScoringShortcut.ALL_WRONG); + // toSubmit(); + }, duration: Duration(milliseconds: 500)), + child: Container( + width: double.infinity, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8.r), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(2.r)), + child: quickText( + '全错', + size: 12.sp, + color: Color.fromRGBO(81, 81, 81, 1), + fontWeight: FontWeight.bold, + ), + ), + ), + ), + SizedBox(height: 7.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.r), + child: $MaterialBtn( + bgc: Colors.transparent, + borderRadius: BorderRadius.circular(2.r), + onTap: () => _useDoScoring.goToScoringShortcut(enumMode: ToScoringShortcut.CANCELLATION), + child: Container( + width: double.infinity, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8.r), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + border: Border.all( + width: 1.r, + color: Colors.white, + ), + ), + child: quickText( + '取消', + size: 12.sp, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: 10.h), + Expanded( + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: questions.length, + itemBuilder: (BuildContext context, int index) { + Questions question = questions[index]; + + JobReviewQuestions subJobQuestion = _useDoScoring.questionsMap.value[question.questionNo]!; + + JobNotesViewBus? currentNotesQuestion = _useDoScoring.useStateBusData.value; + Color textColor = Colors.white; + if (currentNotesQuestion?.questionNo == question.questionNo) textColor = Color.fromRGBO(255, 159, 15, 1); + + return ListTile( + onTap: () {}, + contentPadding: EdgeInsets.zero, + title: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 16.h), + $MaterialBtn( + onTap: () => easyThrottle('view_homework_notes', () => viewHomeworkNotes(subJobQuestion)), + bgc: Color.fromRGBO(88, 87, 87, 1), + child: Container( + width: double.infinity, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8.h), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(width: 0.2.r, color: Colors.white)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(question.questionNo, size: 16.sp, color: textColor, fontWeight: FontWeight.bold), + Padding( + padding: EdgeInsets.only(bottom: 1.h), + child: quickText('题', size: 11.sp, color: textColor), + ), + ], + )), + ), + Container( + child: Row( + children: [ + Expanded( + child: $MaterialBtn( + bgc: Color.fromRGBO(237, 237, 237, 1), + onTap: () => _useDoScoring.goToScoringShortcut( + scoring: ScoringMethodEnum.CORRECT, + questionNo: question.questionNo, + ), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.r), + alignment: Alignment.center, + child: quickText( + '对', + color: subJobQuestion.score == 2 + ? Color.fromRGBO(255, 152, 0, 1) + : Color.fromRGBO(114, 114, 114, 1), + fontWeight: FontWeight.bold, + size: 12.sp, + ), + ), + ), + ), + SizedBox(width: 0.2.w), + Expanded( + child: $MaterialBtn( + onTap: () => _useDoScoring.goToScoringShortcut( + scoring: ScoringMethodEnum.CORRECT_HALF, + questionNo: question.questionNo, + ), + bgc: Color.fromRGBO(237, 237, 237, 1), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.r), + alignment: Alignment.center, + child: quickText( + '半', + color: subJobQuestion.score == 1 + ? Color.fromRGBO(255, 152, 0, 1) + : Color.fromRGBO(114, 114, 114, 1), + fontWeight: FontWeight.bold, + size: 12.sp, + ), + ), + ), + ), + SizedBox(width: 0.2.w), + Expanded( + child: $MaterialBtn( + onTap: () => _useDoScoring.goToScoringShortcut( + scoring: ScoringMethodEnum.WRONG, + questionNo: question.questionNo, + ), + bgc: Color.fromRGBO(237, 237, 237, 1), + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.r), + alignment: Alignment.center, + child: quickText( + '错', + color: subJobQuestion.score == 0 + ? Color.fromRGBO(255, 152, 0, 1) + : Color.fromRGBO(114, 114, 114, 1), + fontWeight: FontWeight.bold, + size: 12.sp, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + if (questions.isNotEmpty) + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.r), + child: $MaterialBtn( + bgc: Colors.white, + splashColor: Theme.of(context).primaryColor.withOpacity(0.8), + borderRadius: BorderRadius.circular(2.r), + onTap: () => easyThrottle('homework_review_submission_main_callback', () => toSubmit(), + duration: Duration(seconds: 1)), + child: Container( + width: double.infinity, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 10.r), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + border: Border.all(width: 1.r, color: Colors.white), + ), + child: quickText( + '提交', + size: 12.sp, + color: Color.fromRGBO(55, 55, 55, 1), + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); +} + +@swidget +Widget $materialBtn( + {required Widget child, Color? bgc, Color? splashColor, GestureTapCallback? onTap, BorderRadius? borderRadius}) { + return Material( + color: bgc, + borderRadius: borderRadius, + child: InkWell(onTap: onTap, splashColor: splashColor, child: child), + ); +} + +// 底部操作栏 +@hwidget +Widget $bottomOperationBar() { + return OrientationBuilder( + builder: (BuildContext context, Orientation orientation) { + return BottomAnnotationSwitchJob(maxWidth: double.infinity, homework: true); + }, + ); +} + +// 试题详情 错误提示 +@hwidget +Widget $examPaperAndScoringErrorPromptView(Function call) { + return Container( + alignment: Alignment.center, + child: ElevatedButton(child: quickText('请求失败,点击重新请求', color: Colors.white), onPressed: () => call()), + ); +} diff --git a/marking_app/lib/pages/homework_correction/eventBus/do_papers_job_custom_paint_size_bus.dart b/marking_app/lib/pages/homework_correction/eventBus/do_papers_job_custom_paint_size_bus.dart new file mode 100644 index 0000000..696a699 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/eventBus/do_papers_job_custom_paint_size_bus.dart @@ -0,0 +1,14 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'do_papers_job_custom_paint_size_bus.g.dart'; + +@JsonSerializable() +class DoPapersJobCustomPaintSizeBus extends Object { + double theHeight; + DoPapersJobCustomPaintSizeBus(this.theHeight); + + factory DoPapersJobCustomPaintSizeBus.fromJson(Map srcJson) => + _$DoPapersJobCustomPaintSizeBusFromJson(srcJson); + + Map toJson() => _$DoPapersJobCustomPaintSizeBusToJson(this); +} diff --git a/marking_app/lib/pages/homework_correction/eventBus/do_papers_job_refresh_bus.dart b/marking_app/lib/pages/homework_correction/eventBus/do_papers_job_refresh_bus.dart new file mode 100644 index 0000000..0a2aede --- /dev/null +++ b/marking_app/lib/pages/homework_correction/eventBus/do_papers_job_refresh_bus.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'do_papers_job_refresh_bus.g.dart'; + +@JsonSerializable() +class DoPapersJobRefreshBus extends Object { + @JsonKey(name: 'flagRefresh') + bool flagRefresh; + + DoPapersJobRefreshBus({required this.flagRefresh}); + + factory DoPapersJobRefreshBus.fromJson(Map srcJson) => _$DoPapersJobRefreshBusFromJson(srcJson); + + Map toJson() => _$DoPapersJobRefreshBusToJson(this); +} diff --git a/marking_app/lib/pages/homework_correction/eventBus/job_check_switching_question_tab_bus.dart b/marking_app/lib/pages/homework_correction/eventBus/job_check_switching_question_tab_bus.dart new file mode 100644 index 0000000..3a103f6 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/eventBus/job_check_switching_question_tab_bus.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_check_switching_question_tab_bus.g.dart'; + +@JsonSerializable() +class JobCheckSwitchingQuestionTabBus extends Object { + @JsonKey(name: 'pageIndex') + int pageIndex; + + JobCheckSwitchingQuestionTabBus( + this.pageIndex, + ); + + factory JobCheckSwitchingQuestionTabBus.fromJson(Map srcJson) => + _$JobCheckSwitchingQuestionTabBusFromJson(srcJson); + + Map toJson() => _$JobCheckSwitchingQuestionTabBusToJson(this); +} diff --git a/marking_app/lib/pages/homework_correction/eventBus/job_do_papers_switch_operation_sub_bus.dart b/marking_app/lib/pages/homework_correction/eventBus/job_do_papers_switch_operation_sub_bus.dart new file mode 100644 index 0000000..28b44d3 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/eventBus/job_do_papers_switch_operation_sub_bus.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_do_papers_switch_operation_sub_bus.g.dart'; + +@JsonSerializable() +class JobDoPapersSwitchOperationSubBus extends Object { + @JsonKey(name: 'height') + double height; + + @JsonKey(name: 'open') + bool open; + + JobDoPapersSwitchOperationSubBus( + this.height, + this.open, + ); + + factory JobDoPapersSwitchOperationSubBus.fromJson(Map srcJson) => + _$JobDoPapersSwitchOperationSubBusFromJson(srcJson); + + Map toJson() => _$JobDoPapersSwitchOperationSubBusToJson(this); +} diff --git a/marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.dart b/marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.dart new file mode 100644 index 0000000..cef1452 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_notes_view_bus.g.dart'; + +@JsonSerializable() +class JobNotesViewBus extends Object { + int taskId; + + int paperId; + + String questionNo; + + JobNotesViewBus({ + required this.taskId, + required this.paperId, + required this.questionNo, + }); + + factory JobNotesViewBus.fromJson(Map srcJson) => _$JobNotesViewBusFromJson(srcJson); + + Map toJson() => _$JobNotesViewBusToJson(this); +} diff --git a/marking_app/lib/pages/homework_correction/eventBus/marking_text_question_job_tab_params_bus.dart b/marking_app/lib/pages/homework_correction/eventBus/marking_text_question_job_tab_params_bus.dart new file mode 100644 index 0000000..d3feb60 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/eventBus/marking_text_question_job_tab_params_bus.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'marking_text_question_job_tab_params_bus.g.dart'; + +@JsonSerializable() +class MarkingTextQuestionJobTabParamsBus extends Object { + int taskId; + + int? nextPageIndex; // 下一个页面 + int? previousPageIndex; // 上一个页面 + int pageIndex; + + int? paperId; + + int? studentId; + + MarkingTextQuestionJobTabParamsBus(this.taskId, this.pageIndex, + {this.paperId, this.studentId, this.nextPageIndex, this.previousPageIndex}); + + factory MarkingTextQuestionJobTabParamsBus.fromJson(Map srcJson) => + _$MarkingTextQuestionJobTabParamsBusFromJson(srcJson); + + Map toJson() => _$MarkingTextQuestionJobTabParamsBusToJson(this); +} diff --git a/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_do_paper_scoring.dart b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_do_paper_scoring.dart new file mode 100644 index 0000000..1e0a5f7 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_do_paper_scoring.dart @@ -0,0 +1,158 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/job/job_review_submission.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/components/jobPictureOverview.dart'; +import 'package:marking_app/pages/homework_correction/eventBus/job_notes_view_bus.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +class UseDoPaperScoring with CommonMixin, EventBusMixin { + ValueNotifier submittedData; // 待提交数据 + ValueNotifier useStateBusData; // 待提交数据 + ValueNotifier> questionsMap; // 待提交数据 + + MarkingTextQuestionJob? questionData; + + UseDoPaperScoring._( + {required this.questionData, + required this.useStateBusData, + required this.questionsMap, + required this.submittedData}); + + // 工厂构造函数 + factory UseDoPaperScoring.use({required MarkingTextQuestionJob data}) { + var submittedData = useState(JobReviewSubmission( + paperId: data.paperId, + score: 0, + questions: + data.questions.map((e) => JobReviewQuestions(questionNo: e.questionNo, score: e.score?.toInt())).toList(), + )); + List questions = submittedData.value.questions; + + var theQuestionsMap = useState>(Map.fromIterable( + questions, + key: (key) => key.questionNo, + value: (value) => questions.firstWhere((element) => element.questionNo == value.questionNo), + )); + + var useStateBusData = useState(null); + + return UseDoPaperScoring._( + questionData: data, + submittedData: submittedData, + questionsMap: theQuestionsMap, + useStateBusData: useStateBusData, + ); + } + + // 打分快捷方式 + void goToScoringShortcut( + {ScoringMethodEnum scoring = ScoringMethodEnum.WRONG, String? questionNo, ToScoringShortcut? enumMode}) { + if (enumMode != null && questionNo == null) { + // 题号为空 视为快捷打分方式 + switch (enumMode) { + case ToScoringShortcut.ALL_PAIRS: // 全对 + case ToScoringShortcut.ALL_WRONG: // 全错 + var theScore = ToScoringShortcut.ALL_WRONG == enumMode ? 0 : 2; + submittedData.value.score = submittedData.value.questions.length * theScore; // 当前页总分 + submittedData.value.questions.forEach((element) { + // 每题得分 + element.score = theScore; + }); + break; + default: // 取消 + submittedData.value.score = 0; // 当前页总分 + submittedData.value.questions.forEach((element) { + // 每题得分 + element.score = null; + }); + } + } else if (questionNo != null) { + // 单道题打分 + JobReviewQuestions question = + submittedData.value.questions.firstWhere((element) => element.questionNo == questionNo); + question.score = scoring.index; + } + List questions = submittedData.value.questions; + questionsMap.value = Map.fromIterable( + questions, + key: (key) => key.questionNo, + value: (value) => questions.firstWhere((element) => element.questionNo == value.questionNo), + ); + submittedData.value.score = + questions.map((e) => e.score).where((e) => e != null).reduce((value, element) => value! + element!); + } + + // 提交批阅 + Future submitForReview( + BuildContext context, GlobalKey scaffoldKeyPictureOverview) async { + ToastUtils.showLoading(); + try { + JobReviewQuestions? question = submittedData.value.questions.firstWhereOrNull((e) => e.score == null); + if (question != null) { + // 不为空就是还有没有批阅打分的试题 + AchievementView( + elevation: 5, + duration: Duration(seconds: 1), + title: "提交提示", + subTitle: "请批阅试题:${question.questionNo}题,并完成打分再提交", + color: Theme.of(context).primaryColor, + ).show(context); + return false; + } + + // 上传批注图片 + // FileResult? res = await pictureOverviewKey.currentState?.saveImage(); + FileResult? res = await scaffoldKeyPictureOverview.currentState?.saveImage(); + if (res != null && res.url != null) + submittedData.value.commentImageUrl = res.url; + else if ((scaffoldKeyPictureOverview.currentState?.showAnnotations ?? false) && + (scaffoldKeyPictureOverview.currentState?.widget.jobData.commentImageUrl != null)) { + submittedData.value.commentImageUrl = scaffoldKeyPictureOverview.currentState?.widget.jobData.commentImageUrl; + } + + /// 得分提交 + RestClient _client = await getClient(); + BaseStructureResult _result = await _client.toSubmitMarking(submittedData.value); + if (!_result.success) { + // 提交错误报错 + ToastUtils.showError(_result.message ?? '提交错误'); + return false; + } + // FToast().init(context).showToast( + // child: Container( + // decoration: BoxDecoration(color: Colors.transparent), + // child: quickText(submittedData.value.score.toString(), color: Colors.red, size: 140.sp), + // ), + // gravity: ToastGravity.BOTTOM, + // toastDuration: Duration(seconds: 1), + // ); + return true; + } catch (e) { + return false; + } finally { + ToastUtils.dismiss(); + } + } +} + +// 打分快捷方式 +enum ToScoringShortcut { + ALL_PAIRS, // 全对 + ALL_WRONG, // 全错 + CANCELLATION, // 取消 +} + +// 得分方式 +enum ScoringMethodEnum { + WRONG, // 错 + CORRECT_HALF, // 半对 + CORRECT, // 正确 +} diff --git a/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_do_refreshcall_list.dart b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_do_refreshcall_list.dart new file mode 100644 index 0000000..a5b39a2 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_do_refreshcall_list.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/job/job_review_submission.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; + +class UseDoPaperScoring with CommonMixin { + ValueNotifier submittedData; // 待提交数据 + ValueNotifier> questionsMap; // 待提交数据 + + MarkingTextQuestionJob? questionData; + + UseDoPaperScoring._({required this.questionData, required this.questionsMap, required this.submittedData}); + + // 工厂构造函数 + factory UseDoPaperScoring.use({required MarkingTextQuestionJob data}) { + var submittedData = useState(JobReviewSubmission( + paperId: data.paperId, + score: 0, + questions: + data.questions.map((e) => JobReviewQuestions(questionNo: e.questionNo, score: e.score?.toInt())).toList(), + )); + List questions = submittedData.value.questions; + + var theQuestionsMap = useState>(Map.fromIterable( + questions, + key: (key) => key.questionNo, + value: (value) => questions.firstWhere((element) => element.questionNo == value.questionNo), + )); + + return UseDoPaperScoring._( + questionData: data, + submittedData: submittedData, + questionsMap: theQuestionsMap, + ); + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_switch_student_and_type.dart b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_switch_student_and_type.dart new file mode 100644 index 0000000..3203178 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_switch_student_and_type.dart @@ -0,0 +1,178 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/event_bus/jobs/job_do_papers_student_bus.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student_params.dart'; +import 'package:marking_app/common/model/job/job_page_tab.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +class UseSwitchStudentAndType with CommonMixin, EventBusMixin { + ValueNotifier exitPromptFlag; // 退出提示 + ValueNotifier isFirst; + ValueNotifier> studentData; // 学生集合数据 + ValueNotifier> tabs; // 学生集合数据 + + ValueNotifier> examStatusData; // 试卷状态数据 + + ValueNotifier currentStudent; // 当前学生 + ValueNotifier currentExamStatus; // 当前状态 + ValueNotifier currentTab; // 当前tab页码 + + ValueNotifier studentBusInfo; // 接收到值后重新赋值当前选中学生和当前选中题型页码 + + UseSwitchStudentAndType._({ + required this.studentData, + required this.currentStudent, + required this.tabs, + required this.examStatusData, + required this.currentExamStatus, + required this.currentTab, + required this.studentBusInfo, + required this.exitPromptFlag, + required this.isFirst, + }); + + // 工厂构造函数 + factory UseSwitchStudentAndType.use() { + var examStatusData = useState([ExamStatus(0, '未完成'), ExamStatus(1, '已完成')]); + var currentExamStatus = useState(examStatusData.value[0]); + var studentData = useState>([]); + var currentStudent = useState(null); + + var tabList = useState>([]); + var currentTab = useState(null); + + var theStudentBus = useState(null); + + return UseSwitchStudentAndType._( + examStatusData: examStatusData, + currentExamStatus: currentExamStatus, + currentStudent: currentStudent, + studentData: studentData, + tabs: tabList, + currentTab: currentTab, + studentBusInfo: theStudentBus, + exitPromptFlag: useState(false), + isFirst: useState(false), + ); + } + + // 获取学生集合 + getDataForStudents({required int taskId}) async { + RestClient client = await getClient(); + BaseStructureResult> result = await client.getJobWithStudents( + JobConcernedWithStudentParams([taskId], isCommit: true), + ); + if (result.success && result.data != null) studentData.value = result.data!; + } + + Future?> getDataForTestpaper({required int taskId, bool synchronization = true}) async { + RestClient client = await getClient(); + BaseStructureResult> result = await client.getJobOfTabs(taskId); + if (result.success && result.data != null) { + if (synchronization) { + tabs.value = result.data!; + currentTab.value = tabs.value.firstWhere((e) => e.finishCount < e.total, orElse: () => tabs.value[0]); + } + } else { + // TODO 获取数据失败 之后如何处理 + ToastUtils.showError(result.message ?? '获取体型数据失败'); + } + return result.data; + } + + getData({required int taskId}) { + getDataForStudents(taskId: taskId); + getDataForTestpaper(taskId: taskId); + } + + /// 属性tab类型数据 + Future refreshQuestionTypeData(BuildContext context, + {required int taskId, required Function() exitCallback}) async { + List? tabDatas = await getDataForTestpaper(taskId: taskId, synchronization: false); + if (tabDatas?.isNotEmpty ?? false) { + JobPageTab? tabJob = tabDatas!.firstWhereOrNull((e) => e.finishCount < e.total); + if (tabJob == null && !exitPromptFlag.value) { + var exitFlag = await showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: quickText('完成批阅提示'), + content: Text('暂无更多待批阅项,是否返回主页?'), + actions: [ + TextButton(child: Text('否'), onPressed: () => Navigator.of(context1).pop(false)), + TextButton(child: Text('是'), onPressed: () => Navigator.of(context1).pop(true)), + ], + ); + }, + ); + if (exitFlag == null || !exitFlag) { + exitPromptFlag.value = true; + return tabJob; // 不跳转 + } + + exitCallback(); + } else { + // 跳转到对于位置 + /** + * + var continueFlag = await showDialog( + context: context, + builder: (BuildContext context1) { + return AlertDialog( + title: quickText('页码跳转提示'), + content: Text('当前页:${currentTab.value?.pageIndex} 已批阅完成,是否跳转到页码:${tabJob.pageIndex}继续批阅?'), + actions: [ + TextButton( + child: Text('是'), + onPressed: () { + Navigator.of(context1).pop(true); + }, + ), + TextButton( + child: Text('否'), + onPressed: () { + // 在这里处理删除操作 + Navigator.of(context1).pop(false); + }, + ), + ], + ); + }, + ); + if (continueFlag == null || !continueFlag) return tabJob; // 不跳转 + */ + tabs.value = tabDatas; + currentTab.value = tabJob; + return tabJob; + } + } + return null; + } + + /// 当前tab下一个tab的pageIndex + int? getNextPageIndex(int? indexLocated) { + if (indexLocated == null) indexLocated = currentTab.value?.pageIndex; + if (indexLocated == null) return null; + return indexLocated == tabs.value.length - 1 ? null : tabs.value[indexLocated + 1].pageIndex; + } + + /// 当前tab上一个tab的pageIndex + int? getPreviousPageIndex(int? indexLocated) { + if (indexLocated == null) indexLocated = currentTab.value?.pageIndex; + if (indexLocated == null) return null; + return indexLocated == 0 ? null : tabs.value[indexLocated - 1].pageIndex; + } +} + +class ExamStatus extends Object { + int id; + String name; + ExamStatus(this.id, this.name); +} diff --git a/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_swith_operation_bar.dart b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_swith_operation_bar.dart new file mode 100644 index 0000000..ccf1163 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/do_papers_job/use_swith_operation_bar.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +// 底部操作功能栏 开关 +class UseSwithOperationBar { + ValueNotifier operationBarSwitch; + + UseSwithOperationBar._({required this.operationBarSwitch}); + + // 工厂构造函数 + factory UseSwithOperationBar.use({bool defaultFlag = true}) { + return UseSwithOperationBar._(operationBarSwitch: useState(defaultFlag)); + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_answer_for_question.dart b/marking_app/lib/pages/homework_correction/hooks/use_answer_for_question.dart new file mode 100644 index 0000000..4d9e95c --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_answer_for_question.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +class UseAnswerForQuestion with CommonMixin { + ValueNotifier showAnswer; + String? answerStr; + + UseAnswerForQuestion._({required this.showAnswer}); + + // 工厂构造函数 + factory UseAnswerForQuestion.use() { + return UseAnswerForQuestion._(showAnswer: useState(false)); + } + + getData({required String questionNum, required int taskId}) async { + this.answerStr = null; + RestClient client = await getClient(); + BaseStructureResult result = await client.getAnswer(taskId, questionNum); + if (result.success) { + this.answerStr = result.data; + toPrint(val: this.answerStr); + } + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_cached_img_refresh.dart b/marking_app/lib/pages/homework_correction/hooks/use_cached_img_refresh.dart new file mode 100644 index 0000000..f33db4f --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_cached_img_refresh.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/model/marking/switch_keyboard_to_reload_images.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; + +// CachedNetworkImage 重新加载图片 +class UseCachedImgRefresh with EventBusMixinSub { + ValueNotifier refreshNumber; + ValueNotifier imageKey; + + UseCachedImgRefresh._({required this.refreshNumber, required this.imageKey}); + + // 工厂构造函数 + factory UseCachedImgRefresh.use() { + return UseCachedImgRefresh._(refreshNumber: useState(0), imageKey: useState(UniqueKey())); + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_keyboard.dart b/marking_app/lib/pages/homework_correction/hooks/use_keyboard.dart new file mode 100644 index 0000000..35a9f72 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_keyboard.dart @@ -0,0 +1,284 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/event_bus/job_currenttab_finish_addtion.dart'; +import 'package:marking_app/common/model/event_bus/job_submit_bus.dart'; +import 'package:marking_app/common/model/event_bus/job_question_switch_bus.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job_params.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/components/PictureOverview.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/homework_correction/components/jobPictureOverview.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +class UseKeyboard + with + CommonMixin, + EventBusMixin, + EventBusMixinSub, + EventBusMixinSubLast { + ValueNotifier tensVal; // 当前选中十位分值 + ValueNotifier smallScoreVal; // 当前选中个位分值 + num? score; + + ValueNotifier> tensList; // 十位数分值 ==> 集合 + ValueNotifier> singleDigitList; // 打开辅助键盘的个位数集合 ==》 个位数分值集合 + ValueNotifier> combineList; // 关闭辅助键盘后的小键盘 ==》 混合十位和个位数分值集合 + DoMarkingKeyboardModel preferenceModel; + + UseKeyboard._({ + required this.tensVal, + required this.smallScoreVal, + required this.tensList, + required this.singleDigitList, + required this.combineList, + required this.preferenceModel, + }); + + // 工厂构造函数 + factory UseKeyboard.use(DoMarkingKeyboardModel preferenceModel) { + final tens = useState>([]); + final singleDigit = useState>([]); + final combine = useState>([]); + final _tensVal = useState(null); + final _smallScoreVal = useState(null); + + return UseKeyboard._( + tensVal: _tensVal, + smallScoreVal: _smallScoreVal, + tensList: tens, + singleDigitList: singleDigit, + combineList: combine, + preferenceModel: preferenceModel, + ); + } + + init({required SortKeyboard sort, required MarkingTextQuestionJob job, double? localScoreInterval}) { + initKeyboardSuper( + sort: sort, + // totalScore: job.totalScore, + // scoreInterval: job.scoreInterval, + totalScore: 1, + scoreInterval: 1, + localScoreInterval: localScoreInterval); + } + + /// 键盘计算数据 + /// @params totalScore 当前试题总分 + /// @params scoreInterval 当前试题系统默认指定步长 + /// @params localScoreInterval 本地设置分值步长 + /// @params sort 键盘排序方式 + void initKeyboardSuper({ + required double totalScore, + required SortKeyboard sort, + double scoreInterval = 1, + double? localScoreInterval, + }) { + if (localScoreInterval != null && localScoreInterval != 0 && localScoreInterval != scoreInterval) { + scoreInterval = localScoreInterval; + } + + /// 没有子题 + // double totalScore = widget.hasSubtopic + // ? widget.data.subQuestionDetailList[widget.subtopicIndex].subQuestionScore + // : widget.totalScore; + + if (scoreInterval == 0) scoreInterval = 1; + initKeyboard(totalScore, scoreInterval, sort); + } + + /// 初始化键盘 + /// @params {double} totalScore 总分 + /// @params {double} scoreInterval 小分步长 + void initKeyboard(double totalScore, double scoreInterval, SortKeyboard sort) { + /** --- 十位计算 --- */ + List tens = []; + int floorScore = totalScore.floor(); // 总分向下取整 + int singleDigit = floorScore; // 个位小数 + int numsLength = singleDigit; + if (floorScore > 10) { + singleDigit = 9; + for (var i = 10; i < floorScore; i += 10) { + tens.add(i); + } + } + + /** --- 混合集合 --- */ + numsLength = ((numsLength - (numsLength % scoreInterval)) / scoreInterval).floor(); + List subNums = List.generate(numsLength + 1, (e) => e * scoreInterval); // +1 添加满分 小分键盘 + if (numsLength * scoreInterval < totalScore) subNums.add(totalScore); // 加一个总分 + + /** --- 个位集合 --- */ + + List nums = (jsonDecode(jsonEncode(subNums)) as List).cast(); // 辅助键盘的小分键盘 + if (sort != SortKeyboard.FULL_AND_AERO_TOP) { + if (totalScore > 19) { + nums = nums.reversed.toList(); + } else { + nums = nums.reversed.where((e) => e <= 10).toList(); + } + subNums = subNums.reversed.toList(); + } + + tensList.value = tens; + singleDigitList.value = nums; + combineList.value = subNums; + + // // 计算关闭辅助键盘后的小键盘 + // if (floorScore > 10) { + // int a = scoreInterval == 0.5 ? floorScore * 2 : floorScore; + // subNums = List.generate(a+1, (e) => e * scoreInterval); // 小分键盘 + // } + } + + /// 批阅 + Future readOver({ + required GlobalKey scaffoldKeyPictureOverview, + required BuildContext context, + required num fraction, + required MarkingTextQuestionJob job, + bool isTens = false, + bool automatic = true, + }) async { + EasyLoading.show(status: 'loading...'); + + try { + // double totalScore = job.totalScore; + double totalScore = 1; + bool fullMarks = totalScore == fraction; + // 十位 + if (isTens) { + bool cleanScore = tensVal.value == fraction; + if (!cleanScore) { + tensVal.value = fraction; + } else { + tensVal.value = null; + smallScoreVal.value = null; + } + score = tensVal.value; + return score; + } + + num? oldTensVal = tensVal.value; + String? oldSmallscoreVal = smallScoreVal.value; + + double theScore = fraction.toDouble(); + if (fullMarks) { + // 满分 + tensVal.value = null; + smallScoreVal.value = null; + } else if (tensVal.value != null) { + // 有十位数 + theScore = (tensVal.value! + fraction).toDouble(); + } + + if (theScore > totalScore) { + return ToastUtils.showError('评分已大于总分,请重新评分'); + } + + if (preferenceModel.autoSubmitToNextQuestion) { + bool submitFlag = await toSubmitMarking( + scaffoldKeyPictureOverview, + context, + // TODO 数据控制 commentImageUrl + MarkingTextQuestionJobParams( + score: theScore, + taskSubjectiveDetailId: 1, + // taskSubjectiveDetailId: job.taskSubjectiveDetailId, + // commentImageUrl: job.commentImageUrl, + ), + ); + if (!submitFlag) { + // 提交失败 恢复旧数据 + tensVal.value = oldTensVal; + smallScoreVal.value = oldSmallscoreVal; + return oldTensVal ?? (oldSmallscoreVal != null ? num.parse(oldSmallscoreVal) : null); + } + + // 提交成功需要跳转下一题 + Future.delayed(Duration.zero, () => eventFire(model: JobQuestionSwitchBus())); + } + + smallScoreVal.value = fraction.toString(); + score = theScore; + return score; + } catch (e) { + toPrint(val: e.toString()); + } finally { + EasyLoading.dismiss(); + } + return null; + } + + Future toSubmitMarking(GlobalKey scaffoldKeyPictureOverview, BuildContext context, + MarkingTextQuestionJobParams param, + {bool loading = false}) async { + if (loading) EasyLoading.show(status: 'loading...'); + try { + RestClient client = await getClient(); + + // final pictureOverviewState = Provider.of(context); + + // final secondView = Get.find(); + + // 上传批注图片 + // FileResult? res = await pictureOverviewKey.currentState?.saveImage(); + FileResult? res = await scaffoldKeyPictureOverview.currentState?.saveImage(); + if (res != null && res.url != null) param.commentImageUrl = res.url; + // TODO + // BaseStructureResult result = await client.toSubmitMarking(param); + // if (!result.success) { + // return ToastUtils.showError('提交失败,请重新打分'); + // } + // eventFireSubLast(model: JobCurrenttabFinishAddtion()); // 当前试题提交后 增加当前tab已经批阅完成数量 + // // ToastUtils.showSuccess('得分:' + getDoubleRemoveZero(param.score), duration: Duration(milliseconds: 500)); + // Fluttertoast.showToast( + // msg: '得分${getDoubleRemoveZero(param.score)}', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.BOTTOM, + // fontSize: 16.sp, + // ); + // return result.success; + } catch (e) { + print(e); + } finally { + if (loading) EasyLoading.dismiss(); + } + return false; + } + + // initBus(BuildContext context) { + // eventOnSub(callback: (JobSubmitBus _model) async { + // if (score == null) return; + // bool success = await toSubmitMarking( + // scaffoldKeyPictureOverview, + // context, + // MarkingTextQuestionJobParams( + // score: score!.toDouble(), + // taskSubjectiveDetailId: _model.taskSubjectiveDetailId, + // ), + // loading: true, + // ); + // if (success) { + // // 提交成功需要跳转下一题 + // Future.delayed(Duration.zero, () => eventFire(model: JobQuestionSwitchBus())); + // } + // }); + // } + + disBus() { + eventCancel(); + eventCancelSub(); + eventCancelSubLast(); + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_preference.dart b/marking_app/lib/pages/homework_correction/hooks/use_preference.dart new file mode 100644 index 0000000..478a545 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_preference.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/utils/fast_data.dart'; +import 'package:marking_app/utils/index.dart'; + +/// 偏好设置主页 +class UsePreference { + ValueNotifier preferenceState; + + UsePreference._({required this.preferenceState}); + + // 工厂构造函数 + factory UsePreference.use({DoMarkingKeyboardModel? moel}) { + return UsePreference._(preferenceState: useState(moel ?? DoMarkingKeyboardModel())); + } + + /// 初始化偏好设置 + Future initPreference() async { + DoMarkingKeyboardModel? localModel = await FastData.getInstance().getPreferencesForWork(); + toPrint(val: '初始化偏好设置' + (localModel != null).toString()); + if (localModel != null) { + preferenceState.value = localModel; + } + return preferenceState.value; + } + + /// 保存偏好设置 + Future savePreference({required DoMarkingKeyboardModel tempModel}) async { + preferenceState.value = tempModel; + bool flag = await FastData.getInstance().setPreferencesForWork(tempModel); + toPrint(val: flag); + } +} + +/// 临时偏好设置 +class UseTemPreference { + ValueNotifier preferenceTemState; + + UseTemPreference._({required this.preferenceTemState}); + + // 工厂构造函数 + factory UseTemPreference.use() => UseTemPreference._(preferenceTemState: useState(DoMarkingKeyboardModel())); + + // 初始化 + init(DoMarkingKeyboardModel model) { + preferenceTemState.value = DoMarkingKeyboardModel.fromJson(model.toJson()); + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_screen.dart b/marking_app/lib/pages/homework_correction/hooks/use_screen.dart new file mode 100644 index 0000000..cbbb9e4 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_screen.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; + +class UseScreen { + ValueNotifier orientationState; // 当前屏幕方向 + + UseScreen._({required this.orientationState}); + + factory UseScreen.use(BuildContext context) { + ScreenDirection theOrientation = MediaQuery.of(context).orientation == Orientation.landscape + ? ScreenDirection.HORIZONTAL_SCREEN + : ScreenDirection.VERTICAL_SCREEN; + + return UseScreen._(orientationState: useState(theOrientation)); + } + + init(ScreenDirection direction) { + SystemChrome.setPreferredOrientations(direction != ScreenDirection.VERTICAL_SCREEN + ? [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight] + : [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + orientationState.value = direction; + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); + } + + dis() { + ScreenDirection orientation = orientationState.value; + if (orientation == ScreenDirection.HORIZONTAL_SCREEN) { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); // 强制竖屏 + } + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]); + } + + switchDirection() { + ScreenDirection orientation = orientationState.value; + if (orientation == ScreenDirection.HORIZONTAL_SCREEN) { + // 目前是横屏,切换竖屏 + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + } else { + SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); + } + + orientationState.value = orientation == ScreenDirection.HORIZONTAL_SCREEN + ? ScreenDirection.VERTICAL_SCREEN + : ScreenDirection.HORIZONTAL_SCREEN; + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_settings.dart b/marking_app/lib/pages/homework_correction/hooks/use_settings.dart new file mode 100644 index 0000000..d9553bf --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_settings.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class UseSettings { + ValueNotifier settingState; // 设置主页 + ValueNotifier preferenceSetState; // 偏好设置 + + UseSettings._({required this.settingState, required this.preferenceSetState}); + + factory UseSettings.use() { + return UseSettings._(settingState: useState(false), preferenceSetState: useState(false)); + } + + switchSetmain(bool val) { + if (val) preferenceSetState.value = false; + settingState.value = val; + } + + switchPreference(bool val) { + if (val) settingState.value = false; + preferenceSetState.value = val; + } +} diff --git a/marking_app/lib/pages/homework_correction/hooks/use_tab.dart b/marking_app/lib/pages/homework_correction/hooks/use_tab.dart new file mode 100644 index 0000000..f114224 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/hooks/use_tab.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; + +class UseTab { + ValueNotifier> tabs; // tab列表 + ValueNotifier currentTab; // 当前tab + + UseTab._({required this.tabs, required this.currentTab}); + + // 工厂构造函数 + factory UseTab.use() { + final tabs = useState>([]); + final tab = useState(null); + return UseTab._(tabs: tabs, currentTab: tab); + } +} diff --git a/marking_app/lib/pages/homework_correction/index.dart b/marking_app/lib/pages/homework_correction/index.dart new file mode 100644 index 0000000..adcdf12 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/index.dart @@ -0,0 +1,330 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-05 16:42:44 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 16:15:03 + * @FilePath: \marking_app\lib\pages\marking\index.dart + * @Description: 阅卷主页 + */ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/model/job/job_task_item.dart'; +import 'package:marking_app/pages/homework_correction/components/new_version_of_homework/homework_tasks_view_item.dart'; +import 'package:marking_app/provider/review_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/marking/marking_list_params.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; + +part 'index.g.dart'; + +// 作业主页 +class HomeworkCorrection extends StatefulHookConsumerWidget { + const HomeworkCorrection({Key? key}) : super(key: key); + + @override + _HomeworkCorrectionState createState() => _HomeworkCorrectionState(); +} + +class _HomeworkCorrectionState extends ConsumerState + with + CommonMixin, + SingleTickerProviderStateMixin, + RefreshDataHandle, + AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + + /* Tab控制器 */ + late TabController _tabController; + int _tabIndex = 0; + bool completedToRefresh = true; + + /* 待阅卷 */ + late final EasyRefreshController _refreshController1; + final MarkingListParams params1 = MarkingListParams( + isFinish: false, + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + pageType: 0, + ); + List markingDatas1 = []; + + /* 已阅完 */ + late final EasyRefreshController _refreshController2; + RemoveListener? _currentTaskIdListener; + final MarkingListParams params2 = MarkingListParams( + isFinish: true, + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + pageType: 0, + ); + List markingDatas2 = []; + + /* 发起请求 */ + Future> getData( + EasyRefreshController controller, + MarkingListParams params, { + bool isReFresh = false, + }) async { + RestClient client = await getClient(); + BasePageData? results = await toRefreshData( + controller, + api: client.getJobsByPage, + params: params, + isReFresh: isReFresh, + context: context, + ); + if (results != null) return results.items; + return []; + } + + @override + void initState() { + _tabController = TabController( + length: 2, + vsync: this, + ); + _refreshController1 = EasyRefreshController(); + _refreshController2 = EasyRefreshController(); + + Future.delayed(Duration.zero, () { + //在这里处理页面 + _currentTaskIdListener = ref.watch(currentTaskIdProvider.notifier).addListener((state) { + int? taskId = state.taskId; + + if (taskId != null && state.refresh) { + _refreshController1.callRefresh(); + _refreshController2.callRefresh(); + } + }); + }); + super.initState(); + } + + @override + void dispose() { + if (_currentTaskIdListener != null) { + _currentTaskIdListener!(); + } + _tabController.dispose(); + _refreshController1.dispose(); + _refreshController2.dispose(); + super.dispose(); + } + + /// 刷新方法 + Future onMyRefresh(EasyRefreshController controller, MarkingListParams params, int tab) async { + params.page = RequestConfig.basePage.page; + List lists = await getData(controller, params, isReFresh: true); + try { + tab == 1 ? (markingDatas1 = lists) : (markingDatas2 = lists); + } catch (e) {} + toUpState(setState, () {}, mounted); + } + + /// 加载方法 + Future onMyLoad(EasyRefreshController controller, MarkingListParams params, int tab) async { + params.page++; + List lists = await getData(controller, params); + if (lists.isNotEmpty) { + tab == 1 ? markingDatas1.addAll(lists) : markingDatas2.addAll(lists); + toUpState(setState, () {}, mounted); + } + } + + @override + Widget build(BuildContext context) { + super.build(context); //调用super.build(返回值始终返回null,应将其忽略) + + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.white, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: Color.fromRGBO(244, 244, 244, 1), + body: OrientationBuilder( + builder: (BuildContext context, Orientation orientation) { + return Column( + children: [ + Container( + color: Colors.white, + margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + padding: EdgeInsets.only(bottom: 9.h, top: 4.h), + child: Row( + children: [ + Expanded( + flex: 1, + child: SizedBox(), + ), + Expanded( + flex: 4, + child: Container( + padding: EdgeInsets.symmetric(vertical: 2.h), + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color.fromRGBO(243, 243, 243, 1), + borderRadius: BorderRadius.circular(8.r), + ), + child: TabBar( + padding: EdgeInsets.zero, + indicatorPadding: EdgeInsets.zero, + indicatorWeight: 0, + labelPadding: EdgeInsets.symmetric(horizontal: 2.w), + controller: _tabController, + unselectedLabelStyle: TextStyle( + fontSize: 14.sp, + color: const Color.fromRGBO(69, 83, 100, 1), + ), + labelStyle: TextStyle( + fontSize: 14.sp, + color: const Color.fromRGBO(104, 136, 253, 1), + ), + // labelColor: const Color.fromRGBO(45, 56, 76, 1), + indicator: const BoxDecoration(), + onTap: (index) { + setState(() { + _tabIndex = index; + if (index == 1 && completedToRefresh) { + // 已阅卷 + _refreshController2.callRefresh(); + completedToRefresh = false; + } + }); + }, + tabs: [ + Tab( + iconMargin: EdgeInsets.zero, + height: 34.h, + child: Container( + width: 140.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: _tabIndex == 0 ? const Color.fromRGBO(255, 255, 255, 1) : null, + borderRadius: BorderRadius.all(Radius.circular(8.r)), + ), + child: quickText( + '待批阅', + size: 14.sp, + color: _tabIndex == 0 + ? Theme.of(context).primaryColor + : const Color.fromRGBO(80, 94, 110, 1), + fontWeight: _tabIndex == 0 ? FontWeight.bold : null, + ), + ), + ), + Tab( + iconMargin: EdgeInsets.zero, + height: 34.h, + child: Container( + width: 140.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: _tabIndex == 1 ? const Color.fromRGBO(255, 255, 255, 1) : null, + borderRadius: BorderRadius.all(Radius.circular(8.r)), + ), + child: quickText( + '已批阅', + size: 14.sp, + color: _tabIndex == 1 + ? Theme.of(context).primaryColor + : const Color.fromRGBO(80, 94, 110, 1), + fontWeight: _tabIndex == 1 ? FontWeight.bold : null, + ), + ), + ), + ], + ), + ), + ), + Expanded( + flex: 1, + child: SizedBox(), + ), + ], + ), + ), + Expanded( + child: IndexedStack( + index: _tabIndex, + children: [ + $EasyRefresh( + controller: _refreshController1, + params: params1, + tab: 1, + data: markingDatas1, + onLoad: onMyLoad, + onRefresh: onMyRefresh, + ), + $EasyRefresh( + controller: _refreshController2, + params: params2, + tab: 2, + data: markingDatas2, + onLoad: onMyLoad, + onRefresh: onMyRefresh, + ), + ], + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +/// 已阅卷 +/// OnRefreshCallback? onRefresh +/// +@swidget +Widget $easyRefresh({ + required EasyRefreshController controller, + required Future Function(EasyRefreshController controller, MarkingListParams params, int tab) onRefresh, + required Future Function(EasyRefreshController controller, MarkingListParams params, int tab) onLoad, + required MarkingListParams params, + required List data, + required int tab, +}) { + bool completed = tab == 2; // 是否是待批阅 + return EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: data.isEmpty ? const MyEmptyWidget() : null, + controller: controller, + header: MaterialHeader(), + footer: TaurusFooter(), + child: ListView.builder( + padding: EdgeInsets.only(top: 11.h, bottom: 10.h, left: 16.w, right: 16.w), + itemBuilder: (context, index) { + return HomeworkTasksViewItem( + completed: completed, + jobTaskItem: data[index], + call: () => controller.callRefresh(), + ); + }, + itemCount: data.length, + ), + onRefresh: () => onRefresh(controller, params, tab), + onLoad: () => onLoad(controller, params, tab), + ); +} diff --git a/marking_app/lib/pages/homework_correction/index_old.dart b/marking_app/lib/pages/homework_correction/index_old.dart new file mode 100644 index 0000000..63f67bf --- /dev/null +++ b/marking_app/lib/pages/homework_correction/index_old.dart @@ -0,0 +1,246 @@ +// /* +// * @Author: wangyang 1147192855@qq.com +// * @Date: 2022-07-05 16:42:44 +// * @LastEditors: wangyang 1147192855@qq.com +// * @LastEditTime: 2022-09-27 16:15:03 +// * @FilePath: \marking_app\lib\pages\marking\index.dart +// * @Description: 阅卷主页 +// */ + +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +// import 'package:marking_app/common/mixin/common.dart'; +// import 'package:flutter_easyrefresh/easy_refresh.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// import 'package:hooks_riverpod/hooks_riverpod.dart'; +// import 'package:marking_app/common/config/request_config.dart'; +// import 'package:marking_app/provider/review_provider.dart'; +// import 'package:marking_app/utils/index.dart'; +// import 'package:marking_app/utils/request/rest_client.dart'; +// import 'package:marking_app/common/model/common/base_page_data.dart'; +// import 'package:marking_app/common/model/marking/marking_item.dart'; +// import 'package:marking_app/common/model/marking/marking_list_params.dart'; +// import 'package:marking_app/components/TestPaperItem.dart'; +// import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +// import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; + +// part 'index.g.dart'; + +// // 作业主页 +// class HomeworkCorrection extends StatefulHookConsumerWidget { +// const HomeworkCorrection({Key? key}) : super(key: key); + +// @override +// _HomeworkCorrectionState createState() => _HomeworkCorrectionState(); +// } + +// class _HomeworkCorrectionState extends ConsumerState +// with +// CommonMixin, +// SingleTickerProviderStateMixin, +// RefreshDataHandle, +// AutomaticKeepAliveClientMixin { +// @override +// bool get wantKeepAlive => true; + +// /* Tab控制器 */ +// late TabController _tabController; +// int _tabIndex = 0; +// bool completedToRefresh = true; + +// /* 待阅卷 */ +// late final EasyRefreshController _refreshController1; +// final MarkingListParams params1 = MarkingListParams( +// isFinish: false, +// page: RequestConfig.basePage.page, +// limit: RequestConfig.basePage.limit, +// pageType: 0, +// ); +// List markingDatas1 = []; + +// /* 已阅完 */ +// late final EasyRefreshController _refreshController2; +// RemoveListener? _currentTaskIdListener; +// final MarkingListParams params2 = MarkingListParams( +// isFinish: true, +// page: RequestConfig.basePage.page, +// limit: RequestConfig.basePage.limit, +// pageType: 0, +// ); +// List markingDatas2 = []; + +// /* 发起请求 */ +// Future> getData( +// EasyRefreshController controller, +// MarkingListParams params, { +// bool isReFresh = false, +// }) async { +// RestClient client = await getClient(); +// BasePageData? results = await toRefreshData( +// controller, +// api: client.getJobsByPage, +// params: params, +// isReFresh: isReFresh, +// context: context, +// ); +// if (results != null) return results.items; +// return []; +// } + +// @override +// void initState() { +// _tabController = TabController(length: 2, vsync: this); +// _refreshController1 = EasyRefreshController(); +// _refreshController2 = EasyRefreshController(); + +// Future.delayed(Duration.zero, () { +// //在这里处理页面 +// _currentTaskIdListener = ref.watch(currentTaskIdProvider.notifier).addListener((state) { +// int? taskId = state.taskId; + +// if (taskId != null && state.refresh) { +// _refreshController1.callRefresh(); +// _refreshController2.callRefresh(); +// } +// }); +// }); +// super.initState(); +// } + +// @override +// void dispose() { +// if (_currentTaskIdListener != null) { +// _currentTaskIdListener!(); +// } +// _tabController.dispose(); +// _refreshController1.dispose(); +// _refreshController2.dispose(); +// super.dispose(); +// } + +// /// 刷新方法 +// Future onMyRefresh(EasyRefreshController controller, MarkingListParams params, int tab) async { +// params.page = RequestConfig.basePage.page; +// List lists = await getData(controller, params, isReFresh: true); +// try { +// tab == 1 ? (markingDatas1 = lists) : (markingDatas2 = lists); +// } catch (e) {} +// toUpState(setState, () {}, mounted); +// } + +// /// 加载方法 +// Future onMyLoad(EasyRefreshController controller, MarkingListParams params, int tab) async { +// params.page++; +// List lists = await getData(controller, params); +// if (lists.isNotEmpty) { +// tab == 1 ? markingDatas1.addAll(lists) : markingDatas2.addAll(lists); +// toUpState(setState, () {}, mounted); +// } +// } + +// @override +// Widget build(BuildContext context) { +// super.build(context); //调用super.build(返回值始终返回null,应将其忽略) + +// return AnnotatedRegion( +// value: const SystemUiOverlayStyle( +// systemNavigationBarColor: Color(0xFF000000), +// systemNavigationBarDividerColor: null, +// statusBarColor: Colors.transparent, +// systemNavigationBarIconBrightness: Brightness.light, +// statusBarIconBrightness: Brightness.dark, +// statusBarBrightness: Brightness.light, +// ), +// child: Scaffold( +// body: Column( +// children: [ +// Padding( +// padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 4.h), +// child: TabBar( +// controller: _tabController, +// unselectedLabelStyle: TextStyle(fontSize: 14.sp), +// labelStyle: TextStyle( +// fontSize: 16.sp, fontWeight: FontWeight.bold, color: const Color.fromRGBO(148, 163, 182, 1)), +// labelColor: const Color.fromRGBO(45, 56, 76, 1), +// indicatorSize: TabBarIndicatorSize.label, +// onTap: (index) { +// setState(() { +// _tabIndex = index; +// if (index == 1 && completedToRefresh) { +// // 已阅卷 +// _refreshController2.callRefresh(); +// completedToRefresh = false; +// } +// }); +// }, +// tabs: const [Tab(text: '待批阅'), Tab(text: '已阅完')], +// ), +// ), +// Expanded( +// child: IndexedStack( +// index: _tabIndex, +// children: [ +// $EasyRefresh( +// controller: _refreshController1, +// params: params1, +// tab: 1, +// data: markingDatas1, +// onLoad: onMyLoad, +// onRefresh: onMyRefresh, +// ), +// $EasyRefresh( +// controller: _refreshController2, +// params: params2, +// tab: 2, +// data: markingDatas2, +// onLoad: onMyLoad, +// onRefresh: onMyRefresh, +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } + +// /// 已阅卷 +// /// OnRefreshCallback? onRefresh +// /// +// @swidget +// Widget $easyRefresh({ +// required EasyRefreshController controller, +// required Future Function(EasyRefreshController controller, MarkingListParams params, int tab) onRefresh, +// required Future Function(EasyRefreshController controller, MarkingListParams params, int tab) onLoad, +// required MarkingListParams params, +// required List data, +// required int tab, +// }) { +// return EasyRefresh( +// firstRefresh: true, +// taskIndependence: true, +// enableControlFinishLoad: true, +// enableControlFinishRefresh: true, +// emptyWidget: data.isEmpty ? const MyEmptyWidget() : null, +// controller: controller, +// header: MaterialHeader(), +// footer: TaurusFooter(), +// child: ListView.builder( +// padding: EdgeInsets.only(top: 4.h, bottom: 10.h), +// itemBuilder: (context, index) => TestPaperItem( +// markingItem: data[index], +// isHomeworkCorrection: true, +// call: () { +// print('开始批改...................................'); +// controller.callRefresh(); +// }, +// ), +// itemCount: data.length, +// ), +// onRefresh: () => onRefresh(controller, params, tab), +// onLoad: () => onLoad(controller, params, tab), +// ); +// } diff --git a/marking_app/lib/pages/homework_correction/providers/do_job_obtain_grading_data_provider.dart b/marking_app/lib/pages/homework_correction/providers/do_job_obtain_grading_data_provider.dart new file mode 100644 index 0000000..05ce493 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/providers/do_job_obtain_grading_data_provider.dart @@ -0,0 +1,16 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job_tab_params.dart'; + +// 获取阅卷数据监听参数发起请求 +final doJobObtainGradingDataProvider = + StateNotifierProvider( + (ref) => DoJobObtainGradingDataProviderHandle(null)); + +class DoJobObtainGradingDataProviderHandle extends StateNotifier with CommonMixin { + DoJobObtainGradingDataProviderHandle(MarkingTextQuestionJobTabParams? val) : super(val); + + setVal(MarkingTextQuestionJobTabParams? val) { + state = val; + } +} diff --git a/marking_app/lib/pages/homework_correction/providers/drawing_provider.dart b/marking_app/lib/pages/homework_correction/providers/drawing_provider.dart new file mode 100644 index 0000000..a3143d4 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/providers/drawing_provider.dart @@ -0,0 +1,36 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; + +// 批注轨迹 +final jobHomeWorkProvider = + StateNotifierProvider>((ref) => JobHomeWorkProviderHandle([])); + +class JobHomeWorkProviderHandle extends StateNotifier> with CommonMixin { + JobHomeWorkProviderHandle(List progress) : super(progress); + + setVal(List val) { + state = val; + } +} + +// 批注轨迹ID +final jobHomeWorkIdProvider = + StateNotifierProvider((ref) => JobHomeWorkIdProviderHandle(-1)); + +class JobHomeWorkIdProviderHandle extends StateNotifier with CommonMixin { + JobHomeWorkIdProviderHandle(int taskdetailId) : super(taskdetailId); + + setVal(int val) { + state = val; + } +} diff --git a/marking_app/lib/pages/homework_correction/providers/drawing_trajectory_provider.dart b/marking_app/lib/pages/homework_correction/providers/drawing_trajectory_provider.dart new file mode 100644 index 0000000..ae895d0 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/providers/drawing_trajectory_provider.dart @@ -0,0 +1,24 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'package:marking_app/common/model/job/gesture_recording.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; + +// 回显批注轨迹 +final jobDrawingTrajectoryProvider = StateNotifierProvider>( + (ref) => JobDrawingTrajectoryProviderHandle([])); + +class JobDrawingTrajectoryProviderHandle extends StateNotifier> with CommonMixin { + JobDrawingTrajectoryProviderHandle(List progress) : super(progress); + + setVal(List val) { + state = val; + } +} diff --git a/marking_app/lib/pages/homework_correction/review_job.dart b/marking_app/lib/pages/homework_correction/review_job.dart new file mode 100644 index 0000000..f03ece9 --- /dev/null +++ b/marking_app/lib/pages/homework_correction/review_job.dart @@ -0,0 +1,673 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-15 09:20:43 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-29 10:17:22 + * @FilePath: \marking_app\lib\pages\marking\review.dart + * @Description: 回评页面 + */ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/model/job/review_again_list_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; +import 'package:marking_app/common/model/review/additional_conditions_for_review.dart'; +import 'package:marking_app/utils/drawer_util/auto_drawer.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/review/review_item.dart'; +import 'package:marking_app/common/model/review/review_tab.dart'; +import 'package:marking_app/components/review_item_view.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; +import 'package:marking_app/utils/index.dart'; + +class ReviewJob extends StatefulWidget { + final int examSubjectId; + final int markingUserId; + final String examName; + const ReviewJob(this.markingUserId, this.examSubjectId, this.examName, {Key? key}) : super(key: key); + + @override + State createState() => _ReviewJobState(); +} + +class _ReviewJobState extends State with CommonMixin, SingleTickerProviderStateMixin { + final GlobalKey<_TheBodyBoxState> _key = GlobalKey<_TheBodyBoxState>(); + late final AdditionalConditionsForReview otherConditions = AdditionalConditionsForReview(); // 赛选条件 + late final int markingUserId; + late final String examName; + + late Future> _future; + late final TabController? _tabController; + + /* 请求获取回评tab */ + Future> getTabs() async { + RestClient client = await getClient(); + // BaseStructureResult> result = await client.getJobOfTabs(markingUserId); + // if (result.success && result.data != null) { + // int allNUmber = + // result.data!.isEmpty ? 0 : result.data!.map((e) => e.finishCount).reduce((value, element) => value + element); + // List data = [ + // ReviewTab(allNUmber, '全部'), + // ...result.data!.map((e) => ReviewTab(e.finishCount, e.questionNum)) + // ]; + // return data; + // } + return []; + } + + @override + void initState() { + markingUserId = widget.markingUserId; + examName = widget.examName; + + _future = getTabs()..then((value) => _tabController = TabController(length: value.length, vsync: this)); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _tabController?.dispose(); + } + + // 抽屉关闭回调 + void autoDrawerSwitch(bool val) {} + + // 筛选条件回调 + Future conditionsDrawerCall({required bool reset, CollationItem? item, String? testId}) async { + if (reset || (item == null && testId == null)) { + // 筛选条件重置 + otherConditions.cleanCollation().whenComplete(() => _key.currentState?.setingOtherParams()); + return; + } + + // 同步数据 + otherConditions.synchronizationInstance(item, testId); + _key.currentState?.setingOtherParams(otherParam: otherConditions); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.dark, + ), + child: Container( + width: ScreenUtil().scaleWidth, + height: ScreenUtil().scaleHeight, + color: Colors.white, + child: Stack( + children: [ + const ReviewHeadBox(), + Scaffold( + backgroundColor: Colors.transparent, + appBar: PreferredSize( + preferredSize: Size.fromHeight(40.h), + child: AppBar( + title: quickText( + '回评', + size: 18.sp, + color: Colors.white, + ), + elevation: 0, + // actions: [ + // Container( + // height: double.infinity, + // padding: EdgeInsets.only(right: 6.w), + // alignment: Alignment.center, + // child: InkWell( + // onTap: () { + // _key.currentState!.seeAbnormal(); + // }, + // child: Text( + // '查看异常', + // style: TextStyle(fontSize: 14.sp, color: Colors.white), + // ), + // ), + // ) + // ], + backgroundColor: Colors.transparent, + ), + ), + body: MyFutureBuilder.buildFutureBuilder( + context, + _future, + (List data) { + return TheBodyBox( + data, + _tabController!, + markingUserId, + widget.examSubjectId, + key: _key, + ); + }, + ), + floatingActionButton: Builder(builder: (context) { + return FloatingActionButton( + tooltip: '筛选条件', + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + onPressed: () { + Scaffold.of(context).openEndDrawer(); //打开右边抽屉 + }, + child: Icon(Icons.filter_alt_outlined, size: 24.sp), + ); + }), + drawerEdgeDragWidth: 0.0, + // 右侧抽屉 + endDrawer: AutoDrawer( + callback: autoDrawerSwitch, + widthPercent: 0.72, + child: ConditionsDrawer( + condition: otherConditions, + callback: conditionsDrawerCall, + ), + ), + ), + ], + ), + ), + ); + } +} + +class TheBodyBox extends StatefulWidget { + final List data; + final TabController tabController; + final int examSubjectId; + final int markingUserId; + final Function? call; + const TheBodyBox(this.data, this.tabController, this.markingUserId, this.examSubjectId, {this.call, Key? key}) + : super(key: key); + + @override + State createState() => _TheBodyBoxState(); +} + +class _TheBodyBoxState extends State + with CommonMixin, RefreshDataHandle { + late final int markingUserId; + late final TabController tabController; + int _tabIndex = 0; + late final List reviewTabs; + late final List tabs = []; + late final List refreshCotlrs = []; + late final List refreshParams = []; + late final List> refreshDatas = []; + late final List tabRefresh = []; + + @override + void initState() { + reviewTabs = widget.data; + tabController = widget.tabController; + markingUserId = widget.markingUserId; + + disRefreshCotlrs(); + for (ReviewTab e in reviewTabs) { + String questionNum = e.questionNum; + tabs.add(Tab(text: questionNum == '全部' ? '全部' : '$questionNum题(${e.questionCount})')); + refreshCotlrs.add(EasyRefreshController()); + refreshParams.add(ReviewAgainListParams( + taskId: markingUserId, + questionNo: questionNum == '全部' ? null : questionNum, + page: 1, + limit: 10, + )); + refreshDatas.add([]); + tabRefresh.add(false); + } + if (tabRefresh.isNotEmpty) { + tabRefresh[0] = true; + } + + super.initState(); + } + + /* 发起请求 */ + Future?> toGetPageData(EasyRefreshController controller, ReviewAgainListParams params, + {bool isReFresh = false}) async { + RestClient client = await getClient(); + BasePageData? results = await toRefreshData( + controller, + api: client.toGoreviewAgainPage, + params: params, + isReFresh: isReFresh, + context: context, + ); + if (results != null) return results.items; + } + + // 查看异常 + void seeAbnormal() { + ReviewTab tab = reviewTabs[_tabIndex]; + String thePath = + '${RouterManager.markingReviewAbnormalPath}?markingId=$markingUserId&examSubjectId=${widget.examSubjectId}&name=${Uri.encodeComponent(tab.questionNum)}&questionNum=${Uri.encodeComponent(tab.questionNum == '全部' ? '' : tab.questionNum)}'; + + RouterManager.router.navigateTo( + context, + thePath, + transition: getTransition(), + ); + } + + // 设置其他赛选参数 + void setingOtherParams({AdditionalConditionsForReview? otherParam}) { + // 同步参数、初始化上拉下拉插件 + if (otherParam == null) { + for (var param in refreshParams) { + param.order = null; + param.sortName = null; + param.keyword = null; + } + refreshCotlrs[_tabIndex].callRefresh(); + return; + } + + int? markingTimeOrderType = otherParam.markingTimeOrderType; + int? scoreOrderType = otherParam.scoreOrderType; + + String? questionIdStr = otherParam.questionId; + + refreshParams.asMap().keys.forEach((index) { + ReviewAgainListParams param = refreshParams[index]; + + param.keyword = questionIdStr; + if (markingTimeOrderType != null) { + param.sortName = 'finishTime'; + param.order = otherParam.orderItem!.rule.name; + } + + if (scoreOrderType != null) { + param.sortName = 'score'; + param.order = otherParam.orderItem!.rule.name; + } + tabRefresh[index] = false; + }); + refreshCotlrs[_tabIndex].callRefresh(); + } + + @override + void dispose() { + super.dispose(); + disRefreshCotlrs(); + } + + // 清空上拉加载下拉刷新工具 + void disRefreshCotlrs() { + tabs.clear(); + if (refreshCotlrs.isEmpty) return; + for (var e in refreshCotlrs) { + e.dispose(); + } + refreshCotlrs.clear(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 10.h), + child: Align( + alignment: Alignment.centerLeft, + child: TabBar( + isScrollable: true, + controller: tabController, + indicatorColor: Colors.white, + unselectedLabelStyle: TextStyle(fontSize: 14.sp, color: const Color.fromRGBO(173, 191, 255, 1)), + labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + labelColor: Colors.white, + indicatorSize: TabBarIndicatorSize.label, + onTap: (index) { + bool flagTbRsh = tabRefresh[index]; + setState(() { + _tabIndex = index; + if (!flagTbRsh) { + refreshCotlrs[index].callRefresh(); + tabRefresh[index] = true; + } + }); + }, + tabs: tabs, + ), + ), + ), + Expanded( + child: IndexedStack( + index: _tabIndex, + children: [ + ...tabs.asMap().keys.map((theIdx) { + EasyRefreshController controller = refreshCotlrs[theIdx]; + + ReviewAgainListParams param = refreshParams[theIdx]; + List reviewItems = refreshDatas[theIdx]; + return EasyRefresh( + firstRefresh: theIdx == 0, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + header: MaterialHeader(), + footer: TaurusFooter(), + emptyWidget: reviewItems.isEmpty ? const MyEmptyWidget() : null, + controller: controller, + child: ListView.builder( + padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h), + itemBuilder: (context, index) => ReviewItemView( + markingUserId, reviewItems[index], widget.examSubjectId, + isHomeworkCorrection: true), + itemCount: reviewItems.length, + ), + onRefresh: () async { + param.page = 1; + List? data = await toGetPageData(controller, param, isReFresh: true); + if (data != null) { + reviewItems.clear(); + setState(() => reviewItems.addAll(data)); + } + }, + onLoad: () async { + param.page += 1; + List? data = await toGetPageData(controller, param); + if (data != null && data.isNotEmpty) { + setState(() => reviewItems.addAll(data)); + return; + } + param.page -= 1; + }, + ); + }).toList() + ], + ), + ), + ], + ); + } +} + +// 赛选条件抽屉 +class ConditionsDrawer extends StatefulWidget { + final AdditionalConditionsForReview condition; + final CollationCallback callback; + const ConditionsDrawer({required this.callback, required this.condition, Key? key}) : super(key: key); + @override + State createState() => _ConditionsDrawerState(); +} + +class _ConditionsDrawerState extends State { + late StreamSubscription keyboardSubscription; + late TextEditingController _inputController; + late FocusNode _inputFocusNode; + + int collationIndex = -1; // 排序方式选中下标 + + @override + void initState() { + super.initState(); + AdditionalConditionsForReview condition = widget.condition; + + CollationItem? orderItem = condition.orderItem; + if (orderItem != null) { + toUpState( + setState, () => collationIndex = AdditionalConditionsForReview.collationArr.indexOf(orderItem), mounted); + } + + _inputController = TextEditingController(text: condition.questionId); + _inputFocusNode = FocusNode()..addListener(_theSetState); + + // 软键盘监听 + keyboardSubscription = KeyboardVisibilityController().onChange.listen((bool visible) { + debugPrint('Keyboard visibility update. Is visible: $visible'); + if (!visible) _inputFocusNode.unfocus(); + }); + } + + @override + void dispose() { + super.dispose(); + keyboardSubscription.cancel(); + _inputController.dispose(); + _inputFocusNode + ..removeListener(_theSetState) + ..dispose(); + } + + void _theSetState({VoidCallback? call}) { + toUpState(setState, () { + if (call != null) call(); + }, mounted); + } + + // 设置排序规则 + void choiceOrderField(CollationItem item, int index) { + if (_inputFocusNode.hasFocus) { + _inputFocusNode.unfocus(); + } + _theSetState(call: () => collationIndex = index); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 270.w, + color: Colors.white, + child: Column( + children: [ + Expanded( + child: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.only( + left: 20.w, + right: 30.h, + bottom: 30.h, + top: MediaQuery.of(context).padding.top + 20.h, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + getPaperIDInput(), + getCollation(), + ], + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 20.h, + ), + child: getBtn(), + ), + ], + ), + ); + } + + // 确认、重置按钮 + Widget getBtn() { + return Row( + children: [ + Expanded( + child: MaterialButton( + height: 40.h, + splashColor: Colors.pink, + // color: Colors.blueGrey, + color: Colors.grey[100], + textColor: const Color.fromRGBO(102, 102, 102, 1), + minWidth: MediaQuery.of(context).size.width / 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: const Text( + '重置', + ), + onPressed: () { + if (_inputFocusNode.hasFocus) { + _inputFocusNode.unfocus(); + } + widget.callback(reset: false).then((_) => Navigator.pop(context)); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + flex: 2, + child: MaterialButton( + height: 40.h, + splashColor: Colors.pink, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: const Text( + '确定', + ), + onPressed: () { + if (_inputFocusNode.hasFocus) { + _inputFocusNode.unfocus(); + } + widget + .callback( + reset: false, + item: collationIndex > -1 ? AdditionalConditionsForReview.collationArr[collationIndex] : null, + testId: _inputController.text) + .then((_) => Navigator.pop(context)); + }), + ), + ], + ); + } + + // 试题ID输入框 + Widget getPaperIDInput() { + return Container( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText('学生名字', size: 18.sp), + SizedBox(height: 10.h), + TextField( + controller: _inputController, + focusNode: _inputFocusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], //数字,只能是整数 + style: TextStyle(color: Colors.black45, fontSize: 14.sp), + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.grey[300]!), + ), + hintText: '请输入参考学生名字', + hintStyle: TextStyle(color: Colors.grey[300], fontSize: 14.sp), + suffixIcon: !_inputFocusNode.hasFocus + ? null + : IconButton( + icon: Icon(Icons.highlight_off, size: 14.sp, color: Colors.grey), + onPressed: () { + _inputController.clear(); + }, + ), + ), + ) + ], + ), + ); + } + + // 排序规则 + Widget getCollation() { + return Container( + margin: EdgeInsets.only(top: 50.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + quickText('排序规则', size: 18.sp), + SizedBox(height: 20.h), + ...AdditionalConditionsForReview.collationArr.asMap().keys.map((iIndex) { + CollationItem item = AdditionalConditionsForReview.collationArr[iIndex]; + bool flag = iIndex == collationIndex; + + return Container( + margin: EdgeInsets.symmetric(vertical: 6.h), + width: double.infinity, + height: 34.h, + alignment: Alignment.center, + decoration: BoxDecoration( + color: flag ? Colors.blue[50] : Colors.grey[100], + borderRadius: BorderRadius.all(Radius.circular(4.sp)), + ), + child: Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => choiceOrderField(item, iIndex), + child: quickText( + item.title, + size: 14.sp, + color: flag ? Theme.of(context).primaryColor : Colors.black54, + ), + ), + ) + ], + ), + ); + + // return Container( + // margin: EdgeInsets.symmetric(vertical: 6.h), + // width: double.infinity, + // height: 34.h, + // alignment: Alignment.center, + // decoration: BoxDecoration( + // color: flag ? Colors.blue[50] : Colors.grey[200], + // borderRadius: BorderRadius.all(Radius.circular(4.sp)) + // ), + // child: quickText(item.title, + // size: 15.sp, color: flag ? Theme.of(context).primaryColor : Colors.black54), + // ); + }).toList() + ], + ), + ); + } +} + +// 底层头部区域 +class ReviewHeadBox extends StatelessWidget { + const ReviewHeadBox({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: double.infinity, + child: Column( + children: [ + Container( + height: 200.h, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/personal_bgi.png'), + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Container( + color: const Color.fromRGBO(248, 248, 248, 1), + )) + ], + ), + ); + } +} diff --git a/marking_app/lib/pages/login/index.dart b/marking_app/lib/pages/login/index.dart new file mode 100644 index 0000000..a1655f3 --- /dev/null +++ b/marking_app/lib/pages/login/index.dart @@ -0,0 +1,448 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-06 10:37:36 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-26 17:24:33 + * @FilePath: \marking_app\lib\pages\login\index.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'dart:convert'; +import 'dart:io'; +import 'package:dio/dio.dart'; + +import 'package:dio/adapter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/utils/const_text.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/user/user_info.dart'; +import 'package:marking_app/common/model/user/user_login.dart'; +import 'package:marking_app/common/model/user/user_login_params.dart'; +import 'package:marking_app/provider/user_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; + +class TheLogin extends StatefulHookConsumerWidget { + const TheLogin({Key? key}) : super(key: key); + + @override + _TheLoginState createState() => _TheLoginState(); +} + +class _TheLoginState extends ConsumerState with CommonMixin { + late Dio dio; + late RestClient client; + + //添加证书 + setHttpsPEM() async { + (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { + client.badCertificateCallback = (X509Certificate cert, String host, int port) { + return true; + }; + }; + } + + //文本输入框控制器 + late final TextEditingController _userNameController; + late final TextEditingController _passwordController; + + late final FocusNode _pwdFocus; // 密码 + late final FocusNode _theFocus; + + bool keepPwd = false; // 记住密码 + bool readAgreement = true; // 阅读协议 + bool canLogin = true; + bool hasNameVal = false; + bool _isShowPwd = true; + + void _showPassword() { + setState(() { + _isShowPwd = !_isShowPwd; + }); + } + + @override + void initState() { + Future(() { + // 延迟更新 Provider + ref.read(userTokenProvider.notifier).clean(); // 进入登录页先清空信息 + }); + + super.initState(); + dio = Dio( + BaseOptions( + contentType: "application/json", + connectTimeout: 8000, + receiveTimeout: 8000, + ), + ); + dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); //添加日志 + + setHttpsPEM(); + client = RestClient(dio, baseUrl: RequestConfig().loginBaseUrl); + + _userNameController = TextEditingController()..addListener(userNameListener); + _passwordController = TextEditingController(); + _pwdFocus = FocusNode(); + _theFocus = FocusNode(); + FastData.getInstance().getUserPwd().then((value) { + if (value == null || value == '') return; + Map valMap = json.decode(value); + setState(() { + _passwordController.text = valMap['pwd']; + _userNameController.text = valMap['account']; + keepPwd = true; + }); + }); + } + + void userNameListener() { + String userName = _userNameController.text; + int useNameLength = userName.length; + bool hasNameValNew = useNameLength > 0; + if (hasNameValNew != hasNameVal) toUpState(setState, () => hasNameVal = hasNameValNew, mounted); + const isProd = bool.fromEnvironment('dart.vm.product'); + if (!isProd && useNameLength == 11) { + _passwordController.text = userName.substring(useNameLength - 6); + } + } + + @override + void dispose() { + super.dispose(); + _userNameController + ..removeListener(userNameListener) + ..dispose(); + _passwordController.dispose(); + _pwdFocus.dispose(); + _theFocus.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + FocusScope.of(context).requestFocus(_theFocus); + }, + child: AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.dark, + ), + child: Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Container( + width: double.infinity, + height: double.infinity, + alignment: Alignment.center, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/login_bgi.png'), + fit: BoxFit.fill, // 完全填充 + ), + ), + child: SingleChildScrollView( + child: Column( + children: [ + Container( + width: 86.w, + height: 86.w, + alignment: Alignment.center, + child: SizedBox( + height: 86.w, + width: 86.w, + child: Image.asset('assets/images/logo.png', fit: BoxFit.cover), + ), + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h), + padding: EdgeInsets.only(top: 34.h, bottom: 16.h, left: 22.w, right: 22.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(width: 1.w, color: Colors.white), + borderRadius: BorderRadius.all(Radius.circular(10.w)), + boxShadow: const [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset.zero, //阴影y轴偏移量 + blurRadius: 100, //阴影模糊程度 + spreadRadius: 100, //阴影扩散程度 + ) + ], + ), + child: Column(children: [ + TextField( + controller: _userNameController, + maxLines: 1, + maxLength: 20, + textInputAction: TextInputAction.next, + onEditingComplete: () { + FocusScope.of(context).requestFocus(_pwdFocus); + }, + style: TextStyle( + color: const Color.fromRGBO(80, 87, 103, 1), + fontSize: 15.sp, + ), + decoration: InputDecoration( + hintText: "请输入账号", + hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)), + labelText: "账号", + labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + suffixIcon: !hasNameVal + ? null + : Transform.translate( + offset: Offset(10, 10), // 根据原始组件的padding值来设置偏移量 + child: IconButton( + alignment: Alignment.center, + padding: EdgeInsets.zero, + icon: Icon( + Icons.highlight_off_sharp, + color: Colors.grey, + size: 16.r, + ), + onPressed: () { + _userNameController.clear(); // 清空文本框内容 + _passwordController.clear(); + }, + ), + ), + ), + ), + TextField( + focusNode: _pwdFocus, + controller: _passwordController, + keyboardType: TextInputType.number, + maxLines: 1, + obscureText: _isShowPwd, //隐藏密码显示 + textInputAction: TextInputAction.go, + onSubmitted: (val) => toLogin(), + style: TextStyle( + color: const Color.fromRGBO(80, 87, 103, 1), + fontSize: 15.sp, + ), + decoration: InputDecoration( + hintText: "请输入密码", + suffix: GestureDetector( + onTap: _showPassword, + child: Icon( + Icons.remove_red_eye, + color: !_isShowPwd ? Theme.of(context).primaryColor : Colors.grey, + ), + ), + hintStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(153, 153, 153, 1)), + labelText: "密码", + isDense: true, + labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + ), + ), + SizedBox( + height: 12.h, + ), + Row( + children: [ + Container( + width: 30.w, + padding: EdgeInsets.only(right: 10.w), + child: Checkbox( + activeColor: Theme.of(context).primaryColor, + checkColor: Colors.white, + value: keepPwd, + onChanged: (value) { + FocusScope.of(context).requestFocus(_pwdFocus); + FocusScope.of(context).requestFocus(_theFocus); + setState(() { + keepPwd = value ?? false; + }); + }, + ), + ), + InkWell( + onTap: () { + FocusScope.of(context).requestFocus(_pwdFocus); + FocusScope.of(context).requestFocus(_theFocus); + setState(() => keepPwd = !keepPwd); + }, + child: Text( + '记住密码', + style: TextStyle( + fontSize: 14.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ), + ), + ], + ), + InkWell( + onTap: toLogin, + child: Container( + margin: EdgeInsets.symmetric(vertical: 10.h), + decoration: BoxDecoration( + color: canLogin ? const Color.fromRGBO(9, 105, 246, 1) : Colors.grey, + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.5), + offset: Offset(6.w, 10.h), //阴影y轴偏移量 + blurRadius: 14, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + borderRadius: BorderRadius.all( + Radius.circular(8.w), + ), + ), + alignment: Alignment.center, + width: double.infinity, + height: 50.h, + child: Text( + '登 录', + style: TextStyle(fontSize: 16.sp, color: Colors.white), + ), + ), + ), + Row( + children: [ + Container( + width: 30.w, + padding: EdgeInsets.only(right: 10.w), + child: Checkbox( + activeColor: Colors.deepOrangeAccent, + checkColor: Colors.white, + value: readAgreement, + onChanged: (value) { + FocusScope.of(context).requestFocus(_pwdFocus); + FocusScope.of(context).requestFocus(_theFocus); + setState(() { + readAgreement = value ?? false; + }); + }, + ), + ), + InkWell( + onTap: () { + RouterManager.router.navigateTo( + context, + '${RouterManager.agreementPath}?type=${AGREEMENT_KEY.USER_AGREEMENT.name}', + transition: getTransition(), + ); + }, + child: quickText('请仔细阅读', size: 13.sp), + ), + InkWell( + onTap: () { + RouterManager.router.navigateTo( + context, + '${RouterManager.agreementPath}?type=${AGREEMENT_KEY.USER_AGREEMENT.name}', + transition: getTransition(), + ); + }, + child: const Text( + '《用户协议》', + style: TextStyle(color: Colors.deepOrangeAccent), + ), + ), + ], + ), + ]), + ) + ], + ), + )), + ), + ), + ); + } + + // 前往登录 + void toLogin() async { + if (!canLogin) return; + + setState(() => canLogin = false); + + void toMsg(msg) { + ToastUtils.showError(msg); + setState(() => canLogin = true); + } + + FocusScope.of(context).requestFocus(_theFocus); + + String userName = _userNameController.text.trim(); + String userPwd = _passwordController.text.trim(); + if (userName == '') return toMsg('请填写用户账号'); + if (userPwd == '') return toMsg('请填写密码再试'); + if (!readAgreement) return toMsg('请阅读用户协议'); + + String userPwdMd5 = CommonUtils.generateMD5(userPwd); + EasyLoading.show(status: 'loading...'); + try { + BaseStructureResult resultData = await client.toLogin(UserLoginParams(userName, userPwdMd5)); + UserLogin? userData = + resultData.code == 200 && resultData.data != null ? UserLogin.fromJson(resultData.data) : null; + if (resultData.code != 200 || userData?.accessToken == null || userData?.accessToken == '') { + return toMsg(resultData.message ?? '登录失败,请重试'); + } + + FastData fastData = FastData.getInstance(); + fastData.setToken(userData!.accessToken); + + BaseStructureResult userRes = await client.getUserInfo('Bearer ${userData.accessToken}'); + if (userRes.code != 200 || userRes.data == null) { + throw Exception('登录失败,请重试'); + } + + fastData.setUser(userRes.data!).then((value) { + // 记住密码 + if (keepPwd) { + fastData.setUserPwdAndAccount({'pwd': userPwd, 'account': userName}); + } + // 更新 + ref.read(userProvider.notifier).initUserInfo(); + ref.read(userTokenProvider.notifier).initToken(); + + // 跳转登录页 + RouterManager.router.navigateTo(context, RouterManager.root, clearStack: true, transition: getTransition()); + }); + } catch (e) { + toPrint(val: e.toString()); + String? msg; + if (e is DioError) { + DioErrorType errorType = e.type; + switch (errorType) { + case DioErrorType.connectTimeout: + msg = '连接超时,请检查网络再重试'; + break; + case DioErrorType.sendTimeout: + msg = '发送时间超时,请重试'; + break; + case DioErrorType.receiveTimeout: + msg = '接收数据超时,请重试'; + break; + case DioErrorType.response: + // TODO: Handle this case. + break; + case DioErrorType.cancel: + // TODO: Handle this case. + break; + case DioErrorType.other: + // TODO: Handle this case. + break; + } + } + FastData.getInstance().cleanShared(); + return toMsg(msg ?? '登录失败,请重试'); + } finally { + EasyLoading.dismiss(); + } + } +} diff --git a/marking_app/lib/pages/mainPage.dart b/marking_app/lib/pages/mainPage.dart new file mode 100644 index 0000000..b211b96 --- /dev/null +++ b/marking_app/lib/pages/mainPage.dart @@ -0,0 +1,217 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-06 13:44:45 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 18:22:42 + * @FilePath: \marking_app\lib\pages\TheMainPage.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/user/user_info.dart'; +import 'package:marking_app/pages/homework_correction/index.dart'; +import 'package:marking_app/pages/reports/index.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/provider/upload_file_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/app_upgrade/UpdateDialog.dart'; +import 'package:marking_app/utils/app_upgrade/model/UpdateAppEvent.dart'; +import 'package:marking_app/common/model/sys/system_version.dart'; +import 'package:marking_app/pages/home/index.dart'; +import 'package:marking_app/pages/marking/index.dart'; +import 'package:marking_app/provider/user_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:package_info/package_info.dart'; + +class TheMainPage extends StatefulHookConsumerWidget { + const TheMainPage({Key? key}) : super(key: key); + + @override + TheMainPageState createState() => TheMainPageState(); +} + +class TheMainPageState extends ConsumerState with CommonMixin { + DateTime? lastPopTime; + late final FocusNode _otherFocusNode; + late final PageController _pageController; + late RemoveListener _userListener; + Timer? _timer; + bool showUpgrade = false; + + final List _bodyList = [ + const TheHomePage(), + const TheMarking(), + const HomeworkCorrection(), + const TheReport() + ]; + int tabIndex = 0; + + /// 获取项目 icon + Widget getItemIcon(String icon) { + return Image.asset( + icon, + fit: BoxFit.contain, + width: 24.w, + height: 24.h, + ); + } + + @override + void initState() { + _pageController = PageController(initialPage: tabIndex); + _otherFocusNode = FocusNode(); + // 由于本项目必须登录才能浏览,所以APP升级校验在登录后 + _userListener = ref.read(userProvider.notifier).addListener((state) { + if (state.id != '0' && state.id != '') { + if (!showUpgrade) { + getAppUpgrade(state); + } + _timer = Timer.periodic(Duration(seconds: 40), (e) { + if (!showUpgrade) getAppUpgrade(state); + }); + } + }); + ref.read(uploadFileProvider.notifier).initConfig(); + ref.read(userProvider.notifier).initUserInfo(); // 获取本地用户信息 + ref.read(userTokenProvider.notifier).initToken().then((value) { + if (!value) { + return toLoginPage(context); + } + ref.read(userReportProvider.notifier).initUserReport(); // 初始化报告页面用户身份职位 + }); // 定值token + // 初始化偏好设置 + ref.read(markingKeyboardProvider.notifier).initMarkingKeyboard(); + super.initState(); + } + + @override + void dispose() { + _userListener(); + _pageController.dispose(); + _otherFocusNode.dispose(); + _timer?.cancel(); + super.dispose(); + } + + void getAppUpgrade(UserInfo user) async { + try { + showUpgrade = true; + if (user.loginName == '18888888888') return; + // 获取设备信息 + String deviceInfo; + int deviceType; + if (Platform.isAndroid) { + deviceInfo = "android"; + deviceType = 1; + } else if (Platform.isIOS) { + deviceInfo = "ios"; + deviceType = 2; + } else { + return; + } + + RestClient client = await getClient(); + BaseStructureResult result = await client.getLatestVersion(deviceType); + if (result.code == 200 && result.data != null) { + //获取当前版本 + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + //获取当前版本 + String localVersion = packageInfo.version; + String appName = packageInfo.appName; //应用名称 + String packageName = packageInfo.packageName; //包名称 + // String buildNumber = packageInfo.buildNumber; //小版本号 + + SystemVersion data = result.data!; + Map json = { + 'downloadPath': data.apkUrl, + 'version': data.version, + 'systemType': deviceType, + 'description': data.description + }; + UpdateAppEvent updateAppEvent = + UpdateAppEvent.fromJson(json, localVersion, deviceInfo, appName, packageName, typeName: 'systemType'); + if (updateAppEvent.upgrade) { + await UpdateDialog.showUpdateDialog(context, updateAppEvent); + } + } + } catch (e) { + } finally { + showUpgrade = false; + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + // 触摸收起键盘 + FocusScope.of(context).requestFocus(_otherFocusNode); + }, + child: WillPopScope( + child: Scaffold( + body: PageView( + controller: _pageController, + physics: const BouncingScrollPhysics(), + onPageChanged: (index) => toUpState(setState, () => tabIndex = index, mounted), + children: _bodyList, + ), + bottomNavigationBar: BottomNavigationBar( + items: [ + BottomNavigationBarItem( + label: '首页', + icon: getItemIcon('assets/images/ic_home_normal.png'), + activeIcon: getItemIcon('assets/images/ic_home_press.png'), + ), + BottomNavigationBarItem( + label: '阅卷', + icon: getItemIcon('assets/images/ic_marking_normal.png'), + activeIcon: getItemIcon('assets/images/ic_marking_press.png'), + ), + BottomNavigationBarItem( + label: '作业', + icon: getItemIcon('assets/images/ic_work_normal.png'), + activeIcon: getItemIcon('assets/images/ic_work_press.png'), + ), + BottomNavigationBarItem( + label: '报告', + icon: getItemIcon('assets/images/ic_report_normal.png'), + activeIcon: getItemIcon('assets/images/ic_report_press.png'), + ), + // BottomNavigationBarItem( + // label: '我的', + // icon: getItemIcon('assets/images/ic_mine_normal.png'), + // activeIcon: getItemIcon('assets/images/ic_mine_press.png'), + // ), + ], + //设置显示的模式 + type: BottomNavigationBarType.fixed, + //设置当前的索引 + currentIndex: tabIndex, + //tabBottom的点击监听 + onTap: (index) => _pageController.jumpToPage(index), + ), + ), + onWillPop: () async { + if (lastPopTime == null || DateTime.now().difference(lastPopTime!) > const Duration(seconds: 1)) { + lastPopTime = DateTime.now(); + ToastUtils.getFluttertoast(context: context, msg: '连续两次返回就退出'); + return Future.value(false); + } else { + lastPopTime = DateTime.now(); + // 退出app + return Future.value(true); + } + }, + ), + ); + } +} diff --git a/marking_app/lib/pages/marking/do_papers.dart b/marking_app/lib/pages/marking/do_papers.dart new file mode 100644 index 0000000..7b191aa --- /dev/null +++ b/marking_app/lib/pages/marking/do_papers.dart @@ -0,0 +1,2835 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-22 09:43:24 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-29 10:18:53 + * @FilePath: \marking_app\lib\pages\marking\marking_papers.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:async'; +import 'dart:convert'; + +import 'package:awesome_dialog/awesome_dialog.dart'; +import 'package:achievement_view/achievement_view.dart'; +import 'package:collection/collection.dart'; +import 'package:dotted_border/dotted_border.dart'; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; +import 'package:marking_app/common/model/marking/current_review_task.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/common/model/marking/keyboard_assist_event.dart'; +import 'package:marking_app/common/model/marking/marking_tag_single_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab_step_size.dart'; +import 'package:marking_app/common/model/marking/marking_zoom.dart'; +import 'package:marking_app/common/model/marking/submit_exam_abnormal_params.dart'; +import 'package:marking_app/common/model/marking/submit_exam_params.dart'; +import 'package:marking_app/common/model/marking/submit_exam_small_params.dart'; +import 'package:marking_app/common/model/marking/switch_keyboard_to_reload_images.dart'; + +import 'package:marking_app/components/PictureOverview.dart'; +import 'package:marking_app/components/marking/marking_keyboard_switch.dart'; +import 'package:marking_app/components/marking/marking_question_type_drawer.dart'; +import 'package:marking_app/components/marking/marking_seting.dart'; +import 'package:marking_app/components/marking/marking_seting_main.dart'; +import 'package:marking_app/components/marking/review_records_view.dart'; +import 'package:marking_app/components/marking/selectable_keyboard_bottom.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/pages/marking/hooks/use_zoomImage_history_utils.dart'; +import 'package:marking_app/pages/marking/provider/rating_progress_provider.dart'; +import 'package:marking_app/provider/annotation_graffiti_switch_provider.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/provider/review_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/image/gallery_example_item_model.dart'; +import 'package:marking_app/utils/image/image_utils.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/components/marking/Input_keyboard_guide_page.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:percent_indicator/percent_indicator.dart'; +import 'package:wakelock/wakelock.dart'; + +import 'hooks/use_abnormal.dart'; + +part 'do_papers.g.dart'; + +class DoPapers extends StatefulHookConsumerWidget { + final int markingUserId; + final MarkingListType? markingtype; + int markingUserDetailId; // 回评需要 其他不需要 + final int examSubjectId; + final int pageOper; // 0:下一题,1:上一题,2:当前题 + final bool isReview; // false:正常 true:回评 + final bool exceptional; // 是否是异常处理 + final String? questionNum; + + DoPapers(this.markingUserId, + {required this.examSubjectId, + this.markingtype, + this.questionNum, + this.markingUserDetailId = 0, + this.pageOper = 2, + this.exceptional = false, + required this.isReview, + Key? key}) + : super(key: key); + + @override + _MarkingPapersState createState() => _MarkingPapersState(); +} + +class _MarkingPapersState extends ConsumerState + with CommonMixin, EventBusMixin, EventBusMixinSub { + final GlobalKey scaffoldKey = GlobalKey(); + + double imageScale = 1; + Offset? imagePosition; + late bool firstComeIn = true; // 是否第一次进入 + MarkingTextQuestion? currentQuestion; // 当前试题 + MarkingTextQuestionTab? _currentTab; // 当前选中tab + // late ValueNotifier _currentTabValueNotifier; + List _currentTabs = []; // 当前考试tab集合 + late MarkingTextQuestionParams params; // 获取试题参数 + late MarkingTextQuestionTabParams tabParams; // tba的方式获取试题参数 + bool showSetingFlag = false; // 关闭设置 + late Future _future; // 考试试卷 + int activeQuestIndex = 0; // 选中题号 + + String questScore = ''; // 打分值 + bool hasZeroPointFive = false; // 是否可以评分(已经满分了就不可更改了) + bool showSubtopicArea = true; + late Offset keyboardOrigin; // 键盘滑动起始坐标 + late Offset keyboarddestination; // 键盘滑动截止坐标 + bool showAbnormal = false; // 是否显示异常区域 + bool needRefresh = false; // 是否需要外不刷新 + + bool theRequesting = false; // 正在发起请求 + String? standardAnswers; // 标准答案 + bool showStandardAnswer = false; // 显示标准答案 + bool inputKeyboardGuidePage = false; // 输入型键盘引导页 + bool setingOperation = false; // 设置操作页 + late RemoveListener _markingKeyboardListener; + bool _theOldAnnotationGraffiti = false; + late RemoveListener _annotationGraffitiListener; + DoMarkingKeyboardModel? keyboardModel; // 键盘阅卷偏好 + bool annotationsFlag = false; // 批注开关 + + // int theExamIndex = 0; // tab模式下计算当前试题所在位置 + late FToast fToast; + + bool _completionPrompt = false; // 全部试题完成并提示 + bool _completionPromptTab = false; // tab下试题完成并提示 + + bool _reviewCompletedPrompted = false; // 已经批阅完成提示 + + // bool _switchQueTypePrompt = false; // 切换题型 + + // void _cruuentTabListenerCall() { + // // 监听当前试题类型切换后就更新 已经提示旗帜 + // print('更新是否切换试题提示'); + // _switchQueTypePrompt = false; + // } + + @override + void initState() { + super.initState(); + Wakelock.enable(); // 常亮 + fToast = FToast(); + // _currentTabValueNotifier = ValueNotifier(_currentTab)..addListener(_cruuentTabListenerCall); + // if you want to use context from globally instead of content we need to pass navigatorKey.currentContext! + fToast.init(context); + Future.delayed(const Duration(seconds: 0)).then((onValue) { + ref.read(annotationGraffitiSwitchProvider.notifier).init(); + }); + + // 批注监听 + _annotationGraffitiListener = ref.read(annotationGraffitiSwitchProvider.notifier).addListener((state) { + var oldState = _theOldAnnotationGraffiti; + _theOldAnnotationGraffiti = state.annotationSwitch; + if (oldState != state.annotationSwitch) { + toUpState(setState, () {}, mounted); + } + }, fireImmediately: false); + // 键盘阅卷偏好监听 + _markingKeyboardListener = ref.read(markingKeyboardProvider.notifier).addListener((state) { + print('进入阅卷偏好监听.................'); + if (keyboardModel == null) { + screenDirectionSwitch(state.screenDirection); + } else { + ScreenDirection nowScreenDirection = state.screenDirection; // 当前屏幕方向 + ScreenDirection theOrientation = MediaQuery.of(context).orientation == Orientation.landscape + ? ScreenDirection.HORIZONTAL_SCREEN + : ScreenDirection.VERTICAL_SCREEN; + if (theOrientation != nowScreenDirection) { + screenDirectionSwitch(nowScreenDirection); + } + } + keyboardModel = state; + + inputKeyboardGuidePage = state.guidePageDisplay && state.keyboard == KeyboardType.INPUT_TYPE; // 引导页标志配置 + toUpState(setState, () {}, mounted); + setTimeOut(300, () { + eventFireSub(model: SwitchKeyboardToReloadImages(true)); + }); + }); + + params = MarkingTextQuestionParams( + pageType: widget.markingtype?.index, + markingUserId: widget.markingUserId, + markingUserDetailId: widget.markingUserDetailId, + type: widget.pageOper, + isReview: widget.isReview, + ); + tabParams = MarkingTextQuestionTabParams( + // pageType: widget.markingtype?.index, + markingUserId: widget.markingUserId, + questionNum: _currentTab?.questionNum, + markingUserDetailId: 0, + // type: widget.pageOper, + // isExcess: _currentTab?.isExcess ?? false, + // examSubjectId: widget.examSubjectId, + ); + _future = getData(tabQuestionNum: widget.questionNum); + } + + @override + void dispose() { + FastData.getInstance().cleanMarkingKeyboardSlidingPosition(); + FastData.getInstance().cleanMarkingMarkingZoomInfo(); + exitQuestion(); + super.dispose(); + // _currentTabValueNotifier + // ..removeListener(_cruuentTabListenerCall) + // ..dispose(); + _markingKeyboardListener(); + _annotationGraffitiListener(); + // ref.read(annotationGraffitiSwitchProvider.notifier).init(); + eventCancel(); + Wakelock.disable(); + + if (keyboardModel?.screenDirection == ScreenDirection.HORIZONTAL_SCREEN) { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // 强制竖屏 + } + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]); + } + + Future exitQuestion() async { + FastData.getInstance().cleanMarkingPapersImageZoom(); // 清空当前图片放大缩小位置方向 + RestClient client = await getClient(); + client.exitMarking(widget.markingUserId); + } + + // 屏幕切换方向 + void screenDirectionSwitch(ScreenDirection direction) { + annotationsFlag = false; // 屏幕方向切换,重新计算批注组件尺寸 + toUpState(setState, () {}, mounted); + + bool isHorizontal = direction == ScreenDirection.HORIZONTAL_SCREEN; + Future.delayed(Duration.zero, () { + SystemChrome.setPreferredOrientations( + [isHorizontal ? DeviceOrientation.landscapeLeft : DeviceOrientation.portraitUp]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); + setTimeOut(1000, () => toUpState(setState, () => annotationsFlag = true, mounted)); + }); + } + + // 获取整卷数据 + Future?> getViewOriginal(String markingUserDetailId) async { + RestClient client = await getClient(); + BaseStructureResult> result = await client.getViewOriginalVolume(markingUserDetailId); + List? paperUrls = result.data; + if (!result.success || paperUrls == null || paperUrls.isEmpty) { + ToastUtils.showError(!result.success ? "获取原试卷失败" : "没有获取到原试卷"); + return null; + } + currentQuestion!.papersUrlStr = paperUrls; + currentQuestion!.papersUrl = + paperUrls.asMap().keys.map((e) => GalleryExampleItemModel(id: e.toString(), resource: paperUrls[e])).toList(); + return currentQuestion!.papersUrl; + } + + // 查看整卷 + void viewEntireVolume(BuildContext context) { + toUpState(setState, () => showSetingFlag = false, mounted); + int? markingUserDetailId = currentQuestion?.id; + + if (markingUserDetailId == null) return; + + if (currentQuestion!.papersUrlStr == null) { + getViewOriginal(markingUserDetailId.toString()).then((List? value) { + if (value == null) return; + Navigator.push( + this.context, + MaterialPageRoute( + builder: (context) => GalleryPhotoViewWrapper( + galleryItems: value, + backgroundDecoration: const BoxDecoration(color: Colors.black), + initialIndex: 0, + scrollDirection: Axis.vertical, + ), + ), + ); + }); + return; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GalleryPhotoViewWrapper( + galleryItems: currentQuestion!.papersUrl, + backgroundDecoration: const BoxDecoration(color: Colors.black), + initialIndex: 0, + scrollDirection: Axis.vertical, + ), + ), + ); + } + + // 查看答案 + void viewAnswer() async { + String? questionNum = currentQuestion?.questionNum; + if (questionNum == null) return; + try { + EasyLoading.show(status: 'loading...'); + RestClient client = await getClient(); + BaseStructureResult result = await client.getStandardAnswer(widget.examSubjectId, questionNum); + if (!result.success) { + throw Exception(result.message ?? '查看答案请求失败'); + } + if ((result.data?.length ?? 0) <= 0) { + return ToastUtils.showError('该题暂未设置标准答案'); + } + + toUpState(setState, () { + standardAnswers = result.data; + showStandardAnswer = true; + }, mounted); + } catch (e) { + ToastUtils.showError('查看答案请求失败'); + } finally { + toUpState(setState, () => showSetingFlag = false, mounted); + EasyLoading.dismiss(); + } + } + + // 发起异常 + void initiateException(flag, reasonKey, otherReasons) async { + final timer = Timer(const Duration(milliseconds: 300), () => ToastUtils.showLoading()); + try { + if (!flag) { + toUpState(setState, () => showAbnormal = false, mounted); + return; + } + int? detailId = currentQuestion?.id; + if (detailId == null) { + ToastUtils.getFluttertoast(context: context, msg: '没有必须参数,请退出重试'); + toUpState(setState, () => showAbnormal = false, mounted); + return; + } + if (currentQuestion!.completeRating) { + ToastUtils.getFluttertoast(context: context, msg: '已批阅,不可提交异常题'); + toUpState(setState, () => showAbnormal = false, mounted); + return; + } + + if (currentQuestion!.isException) { + ToastUtils.getFluttertoast(context: context, msg: '此题已异常题,不可重复提交'); + toUpState(setState, () => showAbnormal = false, mounted); + return; + } + + // 上传批注图片 + // FileResult? res = await pictureOverviewKey.currentState?.saveImage(); + // if (res != null && res.url != null) { + // currentQuestion!.setImageList(res.url!, res.otherParam as int); + // } + + // String? commentImageUrlStr; + // List? commentImageUrl = currentQuestion?.commentImageUrlTodo; + // if (commentImageUrl?.isEmpty ?? true) commentImageUrl = currentQuestion?.commentImageUrl; + // if (commentImageUrl != null) commentImageUrlStr = jsonEncode(commentImageUrl); + + RestClient client = await getClient(); + + BaseStructureResult result = await client.submitTestQuestionsOfExamAbnormal(SubmitExamAbnormalParams( + errorType: int.parse(reasonKey), + markingUserDetailId: detailId, + markingUserId: widget.markingUserId, + reason: otherReasons ?? '', + )); + if (result.success && (result.data ?? false)) { + toUpState(setState, () { + currentQuestion!.isException = true; + showAbnormal = false; + if (!needRefresh) needRefresh = true; // 是否刷新外部任务 + }, mounted); + + if (_currentTab != null) { + await getTabsData(updateCurrentTag: true); // 更新当前tag + } + + MarkingTextQuestionTab tab = await getTabsData(hasNext: true, firstWhereCall: (e) => !e.isFinished); + if (tab.questionNum == '0.0') { + toUpState(setState, () => {_currentTab?.percent = 1}, mounted); + // 非tag试题提示 + if (currentQuestion!.lastOne) { + // 最后一题需要提示 + return showExitDialog(); + } + } + + if (currentQuestion?.lastOne ?? false) { + ToastUtils.dismiss(); + bool? isGoOn = await _showExitDialogOfTag(context: context); + if (isGoOn == true) setTimeOut(0, () => _future = getData(tabQuestionNum: tab.questionNum)); + return; + } + + // 自动进入下一题 + if (ref.read(markingKeyboardProvider).autoSubmitToNextQuestion) { + ToastUtils.showSuccess('提交成功'); + refresh(isNext: true); + } else { + ToastUtils.showSuccess('评阅提交成功,请手动前往下一题', duration: const Duration(milliseconds: 400)); + } + return; + } + toUpState(setState, () => showAbnormal = false, mounted); + ToastUtils.showError('提交失败'); + } finally { + // 关闭批注操作工具栏 + ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); + timer.cancel(); + ToastUtils.dismiss(); + } + } + + // 提交试题 + Future submitTestQuestions(BuildContext theContext, MarkingTextQuestion data) async { + Timer timer = Timer(const Duration(milliseconds: 300), () => ToastUtils.showLoading()); + try { + if (widget.markingtype == MarkingListType.NORMAL && currentQuestion!.isException) + return ToastUtils.showError('异常题,不允许再评分'); + + if (currentQuestion == null) { + return ToastUtils.showError('提交失败,请退出重试。'); + } + // 上传批注图片 + FileResult? res = await pictureOverviewKey.currentState?.saveImage(); + if (res != null && res.url != null) { + currentQuestion!.setImageList(res.url!, res.otherParam as int); + } + int detailId = currentQuestion!.id; + List subQuestinDetails = []; + List subQuestionDetailList = currentQuestion!.subQuestionDetailList; + bool hasSubtopic = subQuestionDetailList.isNotEmpty; //是否有小题 + double score = 0; + if (hasSubtopic) { + // 有小题 小题处理 + for (SubQuestions item in subQuestionDetailList) { + double? gotScore = item.subQuestionGotScore; + bool completeRating = item.completeRating; + if (gotScore == null || !completeRating) { + return ToastUtils.getFluttertoast(context: theContext, msg: '小题题号:${item.subQuestionNum}未评分,请评阅分数'); + } + score += gotScore; + subQuestinDetails.add(SubmitExamSmallParams(item.subQuestionNum, item.subQuestionScore, gotScore, true)); + } + } else { + // 没有小题 + if (!currentQuestion!.completeRating || currentQuestion!.score == null) { + return ToastUtils.getFluttertoast(context: theContext, msg: '请为此题评阅分数'); + } + score += currentQuestion!.score!; + } + + RestClient client = await getClient(); + String? commentImageUrlStr; + List? commentImageUrl = currentQuestion?.commentImageUrlTodo; + if (commentImageUrl?.isEmpty ?? true) commentImageUrl = currentQuestion?.commentImageUrl; + if (commentImageUrl?.isNotEmpty ?? false) commentImageUrlStr = jsonEncode(commentImageUrl); + + // bool excessContinue = + // _currentTab?.agreementExcess == null ? true : _currentTab!.agreementExcess!; // 如果没有询问默认为true 如果询问了 就已询问为主 + bool isExit = false; + MarkingTextQuestionTab? nextTag; + + BaseStructureResult result = await client.submitTestQuestionsOfExam(SubmitExamParams( + widget.markingUserId, + detailId, + score: score, + isReview: widget.isReview, + subQuestionDetailList: subQuestinDetails, + commentImageUrl: commentImageUrlStr ?? '', + questionNum: _currentTab?.questionNum, + isExcess: _currentTab?.isExcess ?? false, + // excessContinue: excessContinue, + pageType: widget.markingtype?.index, + )); + if (!result.success || !(result.data ?? false)) { + return setTimeOut(300, () => ToastUtils.showError(result.message ?? '提交失败,请退出重试。')); + } + // toUpState(setState, () { + // if (!needRefresh) needRefresh = true; + // // 处理已提交字段 + // currentQuestion!.isFinish = true; // 已提交 + // if (hasSubtopic) { + // for (var e in subQuestionDetailList) { + // e.isFinish = true; + // } + // } + // // toScoreFlag = false; // 每次提交后是否需要关闭打分键盘 + // }, mounted); + + if (!needRefresh) needRefresh = true; + // 处理已提交字段 + currentQuestion!.isFinish = true; // 已提交 + if (hasSubtopic) { + for (var e in subQuestionDetailList) { + e.isFinish = true; + } + } + + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text('得分$score'), + // duration: const Duration(seconds: 2), + // ), + // ); + + // // 自动进入下一题 + // Fluttertoast.showToast( + // timeInSecForIosWeb: 2, + // msg: getDoubleRemoveZero(score), + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.BOTTOM, + // fontSize: 140.sp, + // textColor: Colors.red, + // backgroundColor: Colors.transparent, + // ); + + fToast.showToast( + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + ), + child: quickText(getDoubleRemoveZero(score), color: Colors.red, size: 140.sp)), + gravity: ToastGravity.BOTTOM, + toastDuration: Duration(seconds: 1), + ); + + if (_currentTab != null) { + await getTabsData(updateCurrentTag: true); // 更新当前tag + } + + MarkingTextQuestionTab tab = await getTabsData(hasNext: true, firstWhereCall: (e) => !e.isFinished); + if (tab.questionNum == '0.0') { + toUpState(setState, () => {_currentTab?.percent = 1}, mounted); + // 非tag试题提示 + + if (currentQuestion!.lastOne) { + // 最后一题需要提示 + return showExitDialog(); + } + } + + if (_currentTab != null) { + if (data.lastOne) { + timer.cancel(); + ToastUtils.dismiss(); + + bool? isGoOn = await _showExitDialogOfTag(context: context); + if (isGoOn == true) setTimeOut(0, () => _future = getData(tabQuestionNum: tab.questionNum)); + return; + } + } + + if (ref.read(markingKeyboardProvider).autoSubmitToNextQuestion) { + refresh(isNext: true); + } else { + ToastUtils.showSuccess('评阅提交成功,请手动前往下一题', duration: const Duration(milliseconds: 400)); + } + } catch (e) { + ToastUtils.showError('提交错误,请重试'); + toPrint(val: '提交报错了,$e'); + } finally { + // 关闭批注操作工具栏 + ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); + timer.cancel(); + ToastUtils.dismiss(); + } + } + + Future _showExitDialogOfTag({required BuildContext context}) async { + // if (_switchQueTypePrompt) return false; // 放开后就可以做到没有切换试题钱只提示一次 + // _switchQueTypePrompt = true; + return showDialog( + context: context, + barrierDismissible: false, + builder: (context1) { + return AlertDialog( + title: quickText("提示"), + content: Row( + children: [ + Expanded( + child: quickText( + "当前题型:${_currentTab!.questionNum}题已批阅完成,是否进入下一个题型?", + color: Theme.of(context).primaryColor, + maxLines: 2, + ), + ) + ], + ), + actions: [ + MaterialButton( + color: const Color.fromRGBO(245, 246, 251, 1), + disabledColor: const Color.fromRGBO(245, 246, 251, 1), + minWidth: 30.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(3.w)), + ), + onPressed: () { + Navigator.of(context1).pop(false); + }, // 关闭对话框返回false + child: Text( + '否', + style: TextStyle( + fontSize: 16.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + SizedBox(width: 2.w), + MaterialButton( + color: const Color.fromRGBO(54, 86, 255, 0.99), + disabledColor: const Color.fromRGBO(54, 86, 255, 0.99), + minWidth: 30.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(3.w)), + ), + onPressed: () { + Navigator.of(context1).pop(true); + }, + child: Text( + "切换", + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ); + }, + ); + } + + // 完成任务提示 + //AlertDialog + Future showExitDialog() async { + EasyLoading.dismiss(); + + if (_reviewCompletedPrompted) { + return AchievementView( + elevation: 1, + duration: Duration(milliseconds: 800), + title: "完成提示", + subTitle: "没有更多试题了", + color: Theme.of(context).primaryColor, + ).show(context); + } + _reviewCompletedPrompted = true; + + // bool? res = await showDialog( + // context: context, + // barrierDismissible: false, + // builder: (context1) { + // return AlertDialog( + // title: quickText("完成提示"), + // content: Row( + // children: [ + // Expanded( + // child: quickText( + // "您已完成批阅任务!", + // color: Theme.of(context).primaryColor, + // maxLines: 2, + // ), + // ) + // ], + // ), + // actions: [ + // MaterialButton( + // color: const Color.fromRGBO(245, 246, 251, 1), + // disabledColor: const Color.fromRGBO(245, 246, 251, 1), + // minWidth: 30.w, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.all(Radius.circular(3.w)), + // ), + // onPressed: () { + // Navigator.of(context1).pop(false); + // }, // 关闭对话框返回false + // child: Text( + // '继续回评', + // style: TextStyle( + // fontSize: 16.sp, + // color: const Color.fromRGBO(80, 87, 103, 1), + // fontWeight: FontWeight.w400, + // ), + // ), + // ), + // SizedBox(width: 2.w), + // MaterialButton( + // color: const Color.fromRGBO(54, 86, 255, 0.99), + // disabledColor: const Color.fromRGBO(54, 86, 255, 0.99), + // minWidth: 30.w, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.all(Radius.circular(3.w)), + // ), + // onPressed: () { + // Navigator.of(context1).pop(true); + // }, + // child: Text( + // "退出任务", + // style: TextStyle( + // fontSize: 16.sp, + // color: Colors.white, + // fontWeight: FontWeight.w400, + // ), + // ), + // ), + // ], + // ); + // }, + // ); + + // if (res != null && res) { + // Navigator.of(context).pop(true); + // } + var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; + + setTimeOut(200, () { + AwesomeDialog( + context: context, + dialogType: DialogType.info, + // borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 2.sp), + width: !isPortrait ? 190.w : 500.w, + buttonsBorderRadius: const BorderRadius.all(Radius.circular(2)), + dismissOnTouchOutside: true, + dismissOnBackKeyPress: false, + headerAnimationLoop: false, + animType: AnimType.bottomSlide, + title: '完成提示', + desc: '您已完成批阅任务!', + btnCancelText: '继续阅卷', + btnCancelColor: Colors.green[300], + btnOkText: '退出任务', + btnOkColor: Theme.of(context).primaryColor.withOpacity(0.8), + btnCancelOnPress: () {}, + btnOkOnPress: () { + Navigator.of(context).pop(needRefresh); + }, + ).show(); + }); + } + + //刷新数据,重新设置future就行了 + Future refresh({bool isNext = false, int? theId, String? theQuestionNum}) async { + if (isNext && !(currentQuestion?.isException ?? false) && !(currentQuestion?.isFinish ?? false)) { + return ToastUtils.getFluttertoast(context: context, msg: '请先评分后提交再进入下一题'); + } + + int detailId = theId ?? (isNext ? currentQuestion!.nextId : currentQuestion!.prevId); + bool isReview = widget.isReview; + + if (theId != null && _currentTab != null && theQuestionNum != _currentTab!.questionNum) { + MarkingTextQuestionTab? _foundCrrentTab = + _currentTabs.firstWhereOrNull((element) => element.questionNum == theQuestionNum); + if (_foundCrrentTab != null) { + _currentTab = _foundCrrentTab; + } + } + + params = MarkingTextQuestionParams( + pageType: widget.markingtype?.index, + markingUserId: widget.markingUserId, + markingUserDetailId: detailId, + type: theId == null ? (isNext ? 0 : 1) : params.type, + isReview: isReview, + ); + tabParams = MarkingTextQuestionTabParams( + // pageType: widget.markingtype?.index, + markingUserId: widget.markingUserId, + markingUserDetailId: detailId, + questionNum: _currentTab?.questionNum, + // type: isNext ? 0 : 1, + // isExcess: _currentTab?.isExcess ?? false, + // examSubjectId: widget.examSubjectId, + ); + setTimeOut(500, () => Fluttertoast.cancel()); + return _future = getData(); + } + + // 同步小题得分 score:分值 continueScoring:是否可以持续打分 hasSubtopic:是否有小题; cleanScore:是否是清空 + Future synchroScore( + {required double score, + required bool continueScoring, + required bool hasSubtopic, + bool allWrong = false, + bool cleanScore = false}) async { + if (currentQuestion == null) return; + + if (cleanScore) { + // 清空 + toUpState(setState, () { + hasZeroPointFive = false; + questScore = ''; + if (hasSubtopic) { + SubQuestions subItem = currentQuestion!.subQuestionDetailList[activeQuestIndex]; + subItem.subQuestionGotScore = null; + subItem.completeRating = false; + currentQuestion!.score = 0; + } else { + currentQuestion!.score = 0; + currentQuestion!.completeRating = false; + } + }, mounted); + + return; + } + + toUpState(setState, () { + questScore = score.toString(); + hasZeroPointFive = continueScoring; // + if (!showSubtopicArea) showSubtopicArea = true; + if (hasSubtopic) { + // 有小题 + if (score == currentQuestion!.totalScore || allWrong) { + currentQuestion!.subQuestionDetailList.forEach((subQuestion) { + subQuestion.subQuestionGotScore = allWrong ? 0 : subQuestion.subQuestionScore; + subQuestion.completeRating = true; + }); + } else { + SubQuestions subQuestion = currentQuestion!.subQuestionDetailList[activeQuestIndex]; + subQuestion.subQuestionGotScore = score; + subQuestion.completeRating = true; + try { + SubQuestions? nextSubQuestion = activeQuestIndex + 1 < currentQuestion!.subQuestionDetailList.length + ? currentQuestion!.subQuestionDetailList[activeQuestIndex + 1] + : null; + currentQuestion!.score = currentQuestion!.subQuestionDetailList + .where((element) => element.subQuestionGotScore != null) + .map((e) => e.subQuestionGotScore) + .reduce((value, element) => (value ?? 0) + (element ?? 0)); + // ignore: unnecessary_null_comparison + if (nextSubQuestion != null && !nextSubQuestion.completeRating) { + activeQuestIndex += 1; + ref.read(markingSubtopicSwitchingProvider.notifier).setVal(activeQuestIndex); + } + } catch (e) {} + return; + } + } + currentQuestion!.score = score; + currentQuestion!.completeRating = true; + }, mounted); + } + + // 关闭因输入键盘引导页 + void clostInputKeyboardGuidePage() { + toUpState(setState, () => inputKeyboardGuidePage = false, mounted); + keyboardModel!.guidePageDisplay = false; + ref.read(markingKeyboardProvider.notifier).toggleKeyboard(keyboardModel!); + // FastData.getInstance().setInputKeyboardGuidePage(false); + } + + // 查看阅卷设置操作页 + void viewMarkingSettings(bool? val) { + toUpState(setState, () => setingOperation = val ?? false, mounted); + } + + // 当打开题型列表刷新tags题型集合 + Future getUpdateTags() async { + RestClient client = await getClient(); + BaseStructureResult> res = await client.getTestQuestionsOfTab(widget.markingUserId); + if (!res.success || res.data == null) { + var msg = res.message ?? '更新试题集合失败,请重新重试'; + ToastUtils.showError(msg); + return Future.error(msg); + } + res.data = res.data!.where((element) => !(element.isFinished && element.finishCount == 0)).toList(); + + // 方便更新tags key是题号 + Map _currentTabsMap = Map.fromIterable(_currentTabs, + key: (item) => item.questionNum, value: (item) => item); + MarkingTextQuestionTab? theCurrentTab; + String currentTagQueNum = _currentTab!.questionNum; + res.data!.forEach((e) { + String questionNum = e.questionNum; + MarkingTextQuestionTab? tag = _currentTabsMap[questionNum]; + if (tag != null) { + e.scoreInterval = tag.scoreInterval; + // e.agreementExcess = tag.agreementExcess; + if (questionNum == currentTagQueNum) theCurrentTab = e; + } + }); + + if (theCurrentTab == null) { + theCurrentTab = res.data![0]; + } + + _currentTab = theCurrentTab; + _currentTabs = res.data!; + } + + /// 获取tab数据(试题的批次集合). + /// @param tabQuestionNum {String} 需要跳转的题号 + /// @param hasNext {bool} 只是检查是否还有下一个tag + /// @param firstWhereCall {Function} 筛选条件 + Future getTabsData( + {String? tabQuestionNum, + bool hasNext = false, + bool updateCurrentTag = false, + bool resetting = false, + bool Function(MarkingTextQuestionTab)? firstWhereCall}) async { + String? oldQuestionNum = _currentTab?.questionNum; + firstWhereCall ??= getTagCondition; + // if (!updateCurrentTag && tabQuestionNum != null) updateCurrentTag = true; // 切换试题更新tag + late MarkingTextQuestionTab currentTab; + if (!widget.exceptional && _currentTabs.length <= 0 && _currentTab == null) { + // tag为空,请求tag数据并且为对应的tag赋值上分值步长 + RestClient client = await getClient(); + List res = await Future.wait([ + client.getTestQuestionsOfTab(widget.markingUserId), + client.getTestQuestionsOfTabStepSize(widget.examSubjectId) + ]); + BaseStructureResult> resultTab = + res[0] as BaseStructureResult>; + BaseStructureResult> resultTabStep = + res[1] as BaseStructureResult>; + + if ((!resultTab.success || (resultTab.data?.isEmpty ?? true)) || + (!resultTabStep.success || (resultTabStep.data?.isEmpty ?? true)) || + (resultTabStep.data!.length < resultTab.data!.length)) { + throw Error(); + } + Map tabStepMap = Map.fromIterable(resultTabStep.data!, + key: (item) => item.questionNum, value: (item) => item.scoreInterval); + resultTab.data = resultTab.data!.where((element) => !(element.isFinished && element.finishCount == 0)).toList(); + resultTab.data!.forEach((element) => element.setStepSize(tabStepMap[element.questionNum] ?? 1.0)); + + _currentTabs = resultTab.data!; + // 获取当前tabs批次下第一个没有完成数据 + currentTab = resultTab.data!.firstWhere( + tabQuestionNum == null ? firstWhereCall : (e) => e.questionNum == tabQuestionNum, + orElse: () => + MarkingTextQuestionTab(isFinished: false, questionNum: '0.0', total: 0, finishCount: 0, isExcess: false)); + + if (currentTab.questionNum == '0.0') { + // 全部都批改完成了,默认tab设置为第一个 + showExitDialog(); + if (resultTab.data!.isEmpty) { + bool? flag1 = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text("提示"), + content: Text("该批阅任务已完成,没有试题"), + actions: [ + MaterialButton( + color: const Color.fromRGBO(54, 86, 255, 0.99), + disabledColor: const Color.fromRGBO(54, 86, 255, 0.99), + minWidth: 30.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + ), + onPressed: () => Navigator.of(context).pop(true), + child: Text( + '是', + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ); + }, + ); + Navigator.of(context).pop(needRefresh); + } else { + currentTab = resultTab.data![0]; + } + } + } else { + // Tags有数据,(校验当前Tag最后一题、切换Tag) + MarkingTextQuestionTab? newCurrentTag; + MarkingTextQuestionTab? currentTab1; + if (updateCurrentTag) { + // 更新当前_currentTab + bool isExcess = _currentTab!.isExcess; + String questionNum = _currentTab!.questionNum; + if (tabQuestionNum != null) { + currentTab1 = _currentTabs.firstWhere((e) => e.questionNum == tabQuestionNum); + isExcess = currentTab1.isExcess; + questionNum = currentTab1.questionNum; + } + newCurrentTag = await getSingleTaginfo( + widget.markingUserId, + MarkingTagSingleParams(isExcess: isExcess, markingUserId: widget.markingUserId, questionNum: questionNum), + ); + } + + if (newCurrentTag != null) { + if (currentTab1 != null) { + currentTab1.questionNum = newCurrentTag.questionNum; + currentTab1.total = newCurrentTag.total; + currentTab1.finishCount = newCurrentTag.finishCount; + currentTab1.isExcess = newCurrentTag.isExcess; + currentTab1.isFinished = newCurrentTag.isFinished; + currentTab1.initGradingProgress(); + } else { + _currentTab!.questionNum = newCurrentTag.questionNum; + _currentTab!.total = newCurrentTag.total; + _currentTab!.finishCount = newCurrentTag.finishCount; + _currentTab!.isExcess = newCurrentTag.isExcess; + _currentTab!.isFinished = newCurrentTag.isFinished; + _currentTab!.initGradingProgress(); + } + // 超量题 总数已已经批阅完成的总数大于total为总数 + // currentQuestion?.totalCount = newCurrentTag.isExcess && newCurrentTag.finishCount >= newCurrentTag.total + // ? newCurrentTag.finishCount + // : newCurrentTag.total; + } + + if (hasNext) { + // 查找下一个tab + return _currentTabs.firstWhere(firstWhereCall, + orElse: () => MarkingTextQuestionTab( + questionNum: '0.0', total: 0, finishCount: 0, isExcess: false, isFinished: false)); + } + + if (tabQuestionNum != null) { + currentTab = _currentTabs.firstWhere((e) => e.questionNum == tabQuestionNum, orElse: () => _currentTabs[0]); + } else { + currentTab = _currentTab!; + if (resetting) { + // 是否进行重置,继续阅卷 + MarkingTextQuestionTab theMarking = _currentTabs.firstWhere(firstWhereCall, + orElse: () => MarkingTextQuestionTab( + questionNum: '0.0', total: 0, finishCount: 0, isExcess: false, isFinished: false)); + if (theMarking.questionNum != '0.0') { + currentTab = theMarking; + } + } + } + } + if (oldQuestionNum != currentTab.questionNum) { + _completionPromptTab = false; + } + return _currentTab = currentTab; + } + + /// 找到tag下未完成第一个试题 + /// questionNum 当前题号用于指定题号finishCount+1使用 因为在提交前判定是否有下一题 只用于试题提交之前使用 + bool getTagCondition(MarkingTextQuestionTab e, {String? questionNum}) { + /** 已经废弃常量题是否完成判断 统一使用`isFinished`判断 + if (e.isExcess) { + // 超量题的完成是已当前完成量大于等于平均量 + if (e.isFinished) return false; + + return e.finishCount < e.total; + } + */ + + return !e.isFinished; + // if (e.isExcess) { + // // 非平均试题 + // bool? agreement = e.agreementExcess; // 是否已经同意继续批阅 + // bool hasExcess = e.excessCount > 0; // 题池中是否还有试题 + + // int finishCount = questionNum != null && questionNum == e.questionNum ? e.finishCount + 1 : e.finishCount; + // bool completedTask = finishCount >= e.total; // 是否完成已经领取的任务数量(没有阅卷时都是0) + + // if (hasExcess && + // completedTask && + // finishCount >= e.excessAvgCount && + // agreement == null && + // questionNum != null && + // e.questionNum == questionNum) { + // // 剔除当前这个没有询问过的数据 跳过当前正在批阅的试题 + // return false; + // } + + // if (completedTask) { + // // 完成本身任务 判断是否完成指标数量 + // if (hasExcess) { + // // 题池中还有待阅试题 + // if (finishCount >= e.excessAvgCount) { + // // 完成指标 + // if (completedTask = agreement != null) { + // // 没有询问过就视为未完成 + // completedTask = !agreement!; + // } + // } else { + // // 未完成指标 + // completedTask = false; + // } + // } + // } + + // return !completedTask; + // } + // return (questionNum != null && questionNum == e.questionNum ? e.finishCount + 1 : e.finishCount) < e.total; + } + + /* 请求获取回评tab */ + Future getData({String? tabQuestionNum, bool resetting = false}) async { + // bool flagInputKeyboardGuidePage = await FastData.getInstance().getInputKeyboardGuidePage(); // 引导页标志 + try { + theRequesting = true; + RestClient client = await getClient(); + MarkingTextQuestionTab? temTab; + bool isNormal = !widget.exceptional; + + // 正常阅卷,参与tab的方式获取试题 + MarkingTextQuestionTab? oldCurrentTab = _currentTab; + temTab = await getTabsData(tabQuestionNum: tabQuestionNum, resetting: resetting); // 切换tag 返回对应的tag + + print('当前选中类型:${temTab.questionNum}'); + + MarkingTextQuestionTabParams theParam = tabParams.setQuestionNum( + tab: temTab, + locaQuestionNum: oldCurrentTab?.questionNum, + ); + + // 回评第一次进入处理 + if (widget.isReview && firstComeIn) { + theParam..markingUserDetailId = widget.markingUserDetailId; + // ..type = 2; + } + BaseStructureResult result = await client.getTabOfExam(widget.markingUserId, theParam); + + if (result.success && result.data == null) { + ToastUtils.showInfo('当前试题已经批阅完成'); + // _currentTab!.excessCountTotal = _currentTab!.finishCount; + return currentQuestion; + } + + if (!result.success) { + setTimeOut(300, () => ToastUtils.showError('请求错诶,请重试')); + throw Error(); + } + currentQuestion = result.data; + if (temTab != null) { + result.data!.scoreInterval = temTab.scoreInterval ?? 1; + MarkingZoom? markingZoom = await UseZoomImageHistoryUtils.getZoomImageInfo( + widget.markingUserId.toString() + '-' + (currentQuestion?.questionNum.toString() ?? '')); + if (markingZoom != null) { + imageScale = double.parse(markingZoom.scale.toStringAsFixed(2)); + imagePosition = Offset( + double.parse(markingZoom.dx.toStringAsFixed(2)), + double.parse(markingZoom.dy.toStringAsFixed(2)), + ); + print('缩放比例:${imageScale};图片的历史位置:${imagePosition}'); + } else { + imageScale = 1; + imagePosition = Offset(0, 0); + } + + /** */ + // int? thetypeNum = tabParams.type; + + // // || _currentTab?.total == 0 是因为第一次进入页面 total没有更新造成了total是0 + // if (tabQuestionNum != null || _currentTab?.total == 0) + // await getTabsData(updateCurrentTag: true); // 切换tag更新当前Tag数据 + + // currentQuestion?.scoreInterval = temTab.scoreInterval!; + // if (oldCurrentQuestion == null || + // (oldCurrentQuestion.markingUserDetailId != currentQuestion!.markingUserDetailId)) { + // if (theExamIndex == 0 || tabQuestionNum != null) { + // // 当前试题位置 条件意思代表:当批阅的试题大于等于当前任务并且题池中没有试题 或者 获取当前试题是否继续取题池中的试题(!tabParams.excessContinue) + // theExamIndex = (temTab.finishCount >= temTab.total && temTab.excessCount <= 0) || + // (!tabParams.excessContinue && currentQuestion!.isFinish) + // ? temTab.finishCount + // : temTab.finishCount + 1; // 当前试题的位置 + // } else if (thetypeNum != null) { + // thetypeNum == 0 ? ++theExamIndex : --theExamIndex; + // } + // } + // theExamIndex = currentQuestion?.currentIndex ?? 0; + + /**后端返回当前试题位置不需要前端再去计算位置了 不需要判断超量题和平均量了 */ + // currentQuestion!.setTotalCountAndCurrentIndex( + // temTab.isExcess + // ? (temTab.total > temTab.excessAvgCount ? temTab.total : temTab.excessAvgCount) + // : temTab.total, + // temTab.finishCount); // 设置下标 + + // currentQuestion?.totalCount = temTab.total; + } + /** + toUpState(setState, () { + bool hasSub = currentQuestion!.subQuestionDetailList.isNotEmpty; + activeQuestIndex = 0; // 选中题号 + double fullScore; + double getScore; + if (hasSub) { + SubQuestions questions = currentQuestion!.subQuestionDetailList[activeQuestIndex]; + bool isFinish = questions.isFinish; + fullScore = questions.subQuestionScore; + getScore = isFinish ? questions.subQuestionGotScore! : 0; + } else { + fullScore = currentQuestion!.totalScore; + bool isFinish = currentQuestion!.isFinish; // 是否提交 + getScore = isFinish ? currentQuestion!.score! : 0; + } + + questScore = getScore == 0 ? '' : getScore.toString(); // 打分值 + hasZeroPointFive = getScore >= fullScore || questScore.length > 1; // 是否是满分/是否已经包含小数 + + // inputKeyboardGuidePage = flagInputKeyboardGuidePage; // 引导页标志配置 + }, mounted);*/ + bool hasSub = currentQuestion!.subQuestionDetailList.isNotEmpty; + activeQuestIndex = 0; // 选中题号 + double fullScore; + double getScore; + if (hasSub) { + SubQuestions questions = currentQuestion!.subQuestionDetailList[activeQuestIndex]; + bool isFinish = questions.isFinish; + fullScore = questions.subQuestionScore; + getScore = isFinish ? questions.subQuestionGotScore! : 0; + } else { + fullScore = currentQuestion!.totalScore; + bool isFinish = currentQuestion!.isFinish; // 是否提交 + getScore = isFinish ? currentQuestion!.score! : 0; + } + + questScore = getScore == 0 ? '' : getScore.toString(); // 打分值 + hasZeroPointFive = getScore >= fullScore || questScore.length > 1; // 是否是满分/是否已经包含小数 + + // inputKeyboardGuidePage = flagInputKeyboardGuidePage; // 引导页标志配置 + if (firstComeIn) setTimeOut(2000, () => firstComeIn = false); + if (ref.read(markingSubtopicSwitchingProvider.notifier).state != activeQuestIndex) { + // 重置小题题号位置下标 + Future.delayed( + Duration.zero, () => ref.read(markingSubtopicSwitchingProvider.notifier).setVal(activeQuestIndex)); + } + if (currentQuestion != null && widget.markingtype == MarkingListType.EXCEPTIONAL) { + BaseStructureResult res = await client.getMarkingQuestionsErrorInfo(currentQuestion!.id); + printJson(res); + if (res.success && res.data != null) { + currentQuestion!.exceptionInfo = res.data; + } + } + + if (currentQuestion != null && widget.markingtype == MarkingListType.ARBITRATE) { + BaseStructureResult> res = await client.getArbitrateOfHistoryScore(currentQuestion!.id); + if (res.success && res.data != null) { + currentQuestion!.historicalScorings = res.data!; + } + } + +// getMarkingQuestionsErrorInfo + return currentQuestion; + } catch (e) { + toPrint(val: '进入获取试题报错..................${e}'); + setTimeOut(1100, () => RouterManager.router.pop(context)); + return null; + } finally { + widget.markingUserDetailId = 0; + setState(() => theRequesting = false); + } + } + + Future getSingleTaginfo(int markingUserId, MarkingTagSingleParams params) async { + RestClient client = await getClient(); + BaseStructureResult res = await client.getMarkingTagSingleDetails(markingUserId, params); + if (res.success && res.data != null) { + return res.data; + } + } + + // 超量题提示 + void toNextPromptOfExcess(markingUserDetailId) async { + /** + MarkingTextQuestionTab tempCurrentTab = await getTabsData( + hasNext: true, + updateCurrentTag: true, + firstWhereCall: (MarkingTextQuestionTab e) => getTagCondition(e, questionNum: _currentTab!.questionNum)); + bool noNxetTag = tempCurrentTab.questionNum == '0.0'; // true 整个任务都已经批阅完成没有更多了 + bool isExit = false; + MarkingTextQuestionTab? nextTag; + bool? flag1 = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text("提示"), + content: Text("该题批阅量已完成平均值,是否继续批阅该题剩余题目?"), + actions: [ + if (noNxetTag) + MaterialButton( + color: const Color.fromRGBO(245, 246, 251, 1), + disabledColor: const Color.fromRGBO(245, 246, 251, 1), + minWidth: 40.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + ), + onPressed: () { + isExit = noNxetTag; + Navigator.of(context).pop(needRefresh); + }, + child: Text( + '退出阅卷', + style: TextStyle( + fontSize: 16.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + SizedBox(width: 2.w), + MaterialButton( + color: const Color.fromRGBO(245, 246, 251, 1), + disabledColor: const Color.fromRGBO(245, 246, 251, 1), + minWidth: 30.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + ), + onPressed: () { + // isExit = noNxetTag; + // if (!isExit) nextTag = tempCurrentTab; // 下一个试题 + Navigator.of(context).pop(needRefresh); + }, + child: Text( + '否', + style: TextStyle( + fontSize: 16.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + fontWeight: FontWeight.w400, + ), + ), + ), + SizedBox(width: 2.w), + MaterialButton( + color: const Color.fromRGBO(54, 86, 255, 0.99), + disabledColor: const Color.fromRGBO(54, 86, 255, 0.99), + minWidth: 30.w, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + ), + onPressed: () => Navigator.of(context).pop(needRefresh), + child: Text( + '是', + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ); + }, + ); + flag1 ??= false; + _currentTab!.agreementExcess = flag1; + if (flag1) { + // 注意:这里“markingUserDetailId”=0 是因为 这里没有任务却要领取下一道任务中的题,then 中获取了一次题后需要刷新一下任务 + refresh(isNext: true, detailId: 0) + .then((value) => getTabsData(updateCurrentTag: true).then((value) => toUpState(setState, () => {}, mounted))); + } else { + if (isExit) { + // 退出 + // 需要直接退出当前阅卷页面 + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]); + if (needRefresh) { + /* 是否需要刷新首页阅卷列表和阅卷主页列表主页刷新 */ + var theProvider = ref.read(currentTaskIdProvider.notifier); + theProvider.setDoTaskEntity(CurrentReviewTask(taskId: theProvider.state.taskId, refresh: true)); + } + return RouterManager.router.pop(context); + } else { + // 下一题 + nextTag = await getTabsData(hasNext: true); + _future = getData(tabQuestionNum: nextTag!.questionNum); + } + } + ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); + */ + } + + void toOpenEndDrawer() { + getUpdateTags().then((value) => scaffoldKey.currentState?.openDrawer()); + } + + @override + Widget build(BuildContext context) { + BuildContext _thecontext = context; + bool annotationSwitch = ref.watch(annotationGraffitiSwitchProvider).annotationSwitch; + DoMarkingKeyboardModel model = ref.watch(markingKeyboardProvider); + bool hasAuxiliaryKeyboard = model.auxiliaryKeyboard; // 是否需要辅助键盘打开按钮 + bool openAuxiliary = model.openAuxiliary; // 是否需要辅助键盘打开按钮 + bool isBroadwise = model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN; // 是否横向 + + int questNumTotal = _currentTab == null ? 0 : _currentTab!.total; + double percent = _currentTab?.percent ?? 0.0; + + final Color color = Color.lerp(Color.fromRGBO(209, 219, 254, 1), Theme.of(context).primaryColor, percent)!; + // 创建渐变对象 + final gradient = LinearGradient(colors: [Color.fromRGBO(209, 219, 254, 1), color], stops: [0.0, percent]); + + print(keyboardModel!.open && keyboardModel!.keyboard == KeyboardType.BOTTOM_SELECTION); + return GestureDetector( + onTap: () { + // 点击空白处时,移除当前焦点并收起键盘 + FocusScope.of(context).unfocus(); + }, + child: WillPopScope( + child: Scaffold( + key: scaffoldKey, + backgroundColor: const Color.fromRGBO(249, 250, 254, 1), + body: SafeArea( + bottom: !isBroadwise ? true : false, + top: !isBroadwise ? true : false, + left: false, + right: true, + // top: false, + child: Stack( + children: [ + MyFutureBuilder.buildFutureBuilderOfSingleInstance( + context, + _future, + (MarkingTextQuestion? data) { + if (data == null) { + return Container( + child: TextButton( + onPressed: () => RouterManager.router.pop(context), + child: quickText('没有获取到数据,点击返回'), + ), + ); + } + bool hasSubtopic = data.subQuestionDetailList.isNotEmpty; // 是否有小题 + bool isNormal = !widget.exceptional; // 正常批题 + bool notNextTest = data.nextId == 0; // 没有下一个试题了 + // 下一题点击触发的方法 + var pressedNextTest = + notNextTest ? null : () => easyThrottle('TestQuestionSwitch', () => refresh(isNext: true)); + + bool notHasPreviousTest = data.prevId == 0; + /** 无需根据当前位置判断 + (isNormal ? theExamIndex : data.currentIndex) <= 1; // 是否有上一道试题 + */ + return Column( + children: [ + Stack( + children: [ + Container( + height: 40.h, + padding: EdgeInsets.only(left: 5.w, right: 10.w), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(0, 1.h), //阴影y轴偏移量 + blurRadius: 1, //阴影模糊程度 + spreadRadius: 0.1, //阴影扩散程度 + ) + ], + color: Colors.white, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_currentTab != null) + InkWell( + onTap: () => toOpenEndDrawer(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '第${_currentTab!.questionNum}题', + style: TextStyle( + fontSize: isBroadwise ? 18.sp : 14.sp, + fontWeight: FontWeight.w500, + color: const Color.fromRGBO(80, 87, 103, 1), + ), + ), + Container( + width: isBroadwise ? 6.w : 8.w, + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: 2.w), + child: Icon( + IconData(0xe631, fontFamily: "AlibabaIcon"), + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 10.sp : 6.sp, + ), + ), + Text( + '(已阅${_currentTab!.finishCount}/${_currentTab!.total})', + style: TextStyle( + fontSize: isBroadwise ? 14.sp : 12.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ), + if (_currentTab!.isExcess && + _currentTab!.finishCount > _currentTab!.total) + Container( + margin: EdgeInsets.only(left: 4.w), + child: Row( + children: [ + quickText( + '超额完成', + size: isBroadwise ? 18.sp : 14.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + quickText( + (_currentTab!.finishCount - _currentTab!.total) + .toString(), + size: isBroadwise ? 18.sp : 14.sp, + color: Color.fromRGBO(251, 144, 84, 1), + ), + quickText( + '份', + size: isBroadwise ? 18.sp : 14.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ], + ), + ) + // SizedBox(width: isBroadwise ? 0.6.w : 0.6.w), + ], + ), + ), + if (!isNormal) + Text( + // '${isNormal ? theExamIndex : data.currentIndex}/${data.totalCount}', + '${_currentTab!.finishCount}/${_currentTab!.total}', + style: TextStyle( + fontSize: isBroadwise ? 18.sp : 14.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ), + SizedBox(width: 8.w), + if (model.hideQuestionId && + model.screenDirection == ScreenDirection.HORIZONTAL_SCREEN) + InkWell( + onTap: () { + Clipboard.setData(ClipboardData(text: data.paperNum ?? '')) + .then((value) { + ToastUtils.showSuccess('试卷编号已复制'); + }); + }, + child: Row( + children: [ + quickText('试卷编号: ', + size: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + quickText( + data.paperNum ?? '', + size: 16.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ) + ], + ), + ), + // SizedBox(width: 8.w), + // Row( + // children: [ + // quickText('总分: ', + // size: isBroadwise ? 16.sp : 14.sp, + // color: const Color.fromRGBO(148, 163, 182, 1)), + // quickText( + // '${doubleToStringAsFixed(data.totalScore)}', + // size: isBroadwise ? 16.sp : 14.sp, + // color: const Color.fromRGBO(148, 163, 182, 1), + // ) + // ], + // ), + SizedBox(width: 8.w), + if (isBroadwise && widget.markingtype == MarkingListType.ARBITRATE) + Container( + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 2.h), + color: const Color.fromRGBO(4, 201, 208, 0.10), + child: quickText( + '仲裁题', + size: 14.sp, + color: const Color.fromRGBO(4, 201, 208, 1), + ), + ), + if (isBroadwise && widget.markingtype == MarkingListType.EXCEPTIONAL || + data.isException) + Container( + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 2.h), + color: const Color.fromRGBO(245, 108, 108, 0.236), + child: quickText( + '异常题', + size: 14.sp, + color: const Color.fromRGBO(215, 74, 74, 1), + ), + ), + ], + ), + ], + ), + ), + Row( + children: [ + if ((currentQuestion?.isFinish ?? false) && !(currentQuestion?.lastOne ?? true)) + InkWell( + onTap: () { + params = MarkingTextQuestionParams( + pageType: widget.markingtype?.index, + markingUserId: widget.markingUserId, + markingUserDetailId: widget.markingUserDetailId, + type: widget.pageOper, + isReview: widget.isReview, + ); + tabParams = MarkingTextQuestionTabParams( + markingUserId: widget.markingUserId, + questionNum: null, + markingUserDetailId: 0, + ); + _future = getData(resetting: true); + // setTimeOut(0, ()=>setT) + }, + child: Row( + children: [ + SizedBox(width: 2.w), + Icon( + IconData(0xe632, fontFamily: "AlibabaIcon"), + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 15.sp : 12.sp, + ), + SizedBox(width: 1.w), + quickText('继续阅卷', + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 15.sp : 12.sp), + ], + ), + ), + SizedBox(width: 10.w), + InkWell( + onTap: () { + ref + .read(ratingProgressProvider.notifier) + .setState(EndDrawerViewEnum.REVIEW_RECORD); + scaffoldKey.currentState?.openEndDrawer(); + }, + child: Row( + children: [ + Icon( + IconData(0xe630, fontFamily: "AlibabaIcon"), + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 15.sp : 12.sp, + ), + SizedBox(width: 1.w), + quickText('阅卷记录', + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 15.sp : 12.sp), + ], + ), + ), + Container( + width: 0.3.w, + height: 15.h, + color: Colors.grey, + margin: EdgeInsets.symmetric(horizontal: 1.5.w), + ), + InkWell( + onTap: () { + ref + .read(ratingProgressProvider.notifier) + .setState(EndDrawerViewEnum.RATING_PROGRESS); + scaffoldKey.currentState?.openEndDrawer(); + }, + child: Row( + children: [ + Icon( + IconData(0xe630, fontFamily: "AlibabaIcon"), + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 15.sp : 12.sp, + ), + SizedBox(width: 1.w), + quickText('评分进度', + color: const Color.fromRGBO(80, 87, 103, 1), + size: isBroadwise ? 15.sp : 12.sp), + ], + ), + ), + SizedBox(width: 10.w), + if (widget.markingtype == MarkingListType.NORMAL && !data.isException) + InkWell( + onTap: () { + if (data.isException) { + return ToastUtils.getFluttertoast( + context: context, msg: '当前试题已为异常题,无需重复提交'); + } + toUpState(setState, () => showAbnormal = true, mounted); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + // margin: EdgeInsets.only(right: 1.w), + padding: EdgeInsets.only(bottom: 2.h), + child: Icon( + Icons.warning_amber_rounded, + color: const Color.fromRGBO(215, 74, 74, 1), + size: isBroadwise ? 16.sp : 12.sp, + ), + ), + Text( + '异常', + textAlign: TextAlign.end, + style: TextStyle( + fontSize: isBroadwise ? 16.sp : 12.sp, + color: const Color.fromRGBO(215, 74, 74, 1), + ), + ) + ], + ), + ), + SizedBox(width: 10.w), + InkWell( + onTap: () { + ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); + // setingDialog(context); + toUpState(setState, () => showSetingFlag = true, mounted); + }, + child: Icon( + Icons.settings, + color: const Color.fromRGBO(148, 163, 182, 1), + size: isBroadwise ? 30.sp : 20.sp, + ), + ), + SizedBox(width: 10.w), + InkWell( + onTap: () { + Navigator.of(context).pop(needRefresh); + }, + child: Icon( + Icons.exit_to_app_rounded, + color: Theme.of(context).primaryColor.withOpacity(0.5), + size: isBroadwise ? 30.sp : 20.sp, + ), + ), + ], + ) + ], + ), + ), + SizedBox( + height: 6.h, + child: LinearPercentIndicator( + padding: const EdgeInsets.all(0), + animation: true, + lineHeight: 12.h, + animationDuration: firstComeIn ? 800 : 0, + percent: percent, + progressColor: percent == 1 ? Colors.green : null, + linearGradient: percent != 1 ? gradient : null, + backgroundColor: Colors.transparent, + ), + ), + ], + ), + Expanded( + child: Row( + children: [ + // 小题打分框 + if (hasSubtopic) + AnimatedContainer( + width: showSubtopicArea ? (isBroadwise ? 50.w : 70.w) : (isBroadwise ? 10.w : 20.w), + duration: const Duration(seconds: 1), + curve: Curves.easeInOutQuint, + child: showSubtopicArea + ? Container( + width: 50.w, + margin: EdgeInsets.only(right: 1.5.w), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset(0.1.w, 0), //阴影y轴偏移量 + blurRadius: 6, //阴影模糊程度 + spreadRadius: 0.06, //阴影扩散程度 + ) + ], + ), + child: Column( + children: [ + InkWell( + onTap: () { + toUpState(setState, () { + showSubtopicArea = !showSubtopicArea; + }, mounted); + }, + child: Container( + height: 30.h, + width: double.infinity, + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 6.h), + color: const Color.fromRGBO(249, 250, 254, 1), + child: Icon( + Icons.chevron_left_sharp, + size: 34.sp, + color: const Color.fromRGBO(80, 87, 103, 1), + ), + ), + ), + Expanded( + child: ListView.builder( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 4.h), + itemBuilder: (context, index) { + SubQuestions quest = data.subQuestionDetailList[index]; + + bool activeIndex = activeQuestIndex == index; + return InkWell( + onTap: () { + double? getScore = quest.subQuestionGotScore; + ref + .read(markingSubtopicSwitchingProvider.notifier) + .setVal(index); + toUpState(setState, () { + activeQuestIndex = index; + // TODO 这是什么 + + // pictureOverviewKey.currentState?.jumpToPage(activeQuestIndex); + questScore = getScore?.toString() ?? ''; + hasZeroPointFive = + questScore != '' && getScore! >= quest.subQuestionScore; + }, mounted); + }, + child: Container( + margin: EdgeInsets.only(top: 5.h), + decoration: BoxDecoration( + border: Border.all( + width: 0.6.w, + color: activeIndex + ? const Color.fromRGBO(46, 91, 255, 1) + : const Color.fromRGBO(224, 230, 255, 1), + ), + borderRadius: BorderRadius.all(Radius.circular(1.8.w)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + alignment: const FractionalOffset(0.5, 1.78), + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 4.w, vertical: 8.h), + alignment: Alignment.center, + decoration: BoxDecoration( + color: !activeIndex + ? const Color.fromRGBO(249, 250, 254, 1) + : const Color.fromRGBO(46, 91, 255, 1), + border: Border( + bottom: BorderSide( + color: activeIndex + ? const Color.fromRGBO(46, 91, 255, 1) + : const Color.fromRGBO(224, 230, 255, 1), + width: 0.2.w, + ))), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '${quest.subQuestionNum}题', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: activeIndex + ? Colors.white + : const Color.fromRGBO(80, 87, 103, 1), + fontSize: 13.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 1.w), + Padding( + padding: EdgeInsets.only(bottom: 1.h), + child: quickText( + '(${getDoubleRemoveZero(data.subQuestionDetailList[index].subQuestionScore)})', + size: 11.sp, + color: activeIndex + ? Colors.white + : const Color.fromRGBO( + 80, 87, 103, 1)), + ) + ], + )), + if (activeIndex) + Icon(Icons.arrow_drop_down_outlined, + color: const Color.fromRGBO(46, 91, 255, 1), + size: 20.sp) + ], + ), + Container( + padding: + EdgeInsets.symmetric(horizontal: 4.w, vertical: 8.h), + alignment: Alignment.center, + child: Text( + getDoubleRemoveZero(quest.subQuestionGotScore, '请评分'), + style: TextStyle( + color: const Color.fromRGBO(80, 87, 103, 1), + fontSize: 14.sp, + fontWeight: FontWeight.w400, + ), + ), + ) + ], + ), + ), + ); + }, + itemCount: data.subQuestionDetailList.length, + ), + ) + ], + )) + : GestureDetector( + child: InkWell( + onTap: () { + toUpState(setState, () { + showSubtopicArea = !showSubtopicArea; + }, mounted); + }, + child: Container( + height: double.infinity, + width: double.infinity, + alignment: Alignment.center, + margin: EdgeInsets.only(right: 1.5.w), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset(0.1.w, 0), //阴影y轴偏移量 + blurRadius: 6, //阴影模糊程度 + spreadRadius: 0.06, //阴影扩散程度 + ) + ], + ), + child: Text( + '>>', + style: + TextStyle(color: Theme.of(context).primaryColor, fontSize: 12.sp), + ), + )), + onHorizontalDragEnd: (detail) { + toUpState(setState, () { + showSubtopicArea = !showSubtopicArea; + }, mounted); + }, + ), + ), + // 内容区域 + Expanded( + child: Stack( + children: [ + // 试卷 + Container( + margin: EdgeInsets.only(top: 6.h), + padding: EdgeInsets.symmetric(horizontal: 1.w), + child: PictureOverview( + questionNum: data.questionNum, + markingUserId: widget.markingUserId, + key: pictureOverviewKey, + imageScale: imageScale, + imagePosition: imagePosition, + annotationsFlag: annotationSwitch, + commentImageMap: data.commentImageUrlMap, + testQuestionNumber: widget.markingUserId.toString() + '-' + data.questionNum, + imageItems: data.getImageData(), + ), + ), + // 批注开关 暂时注释 + // if (!showSetingFlag && + // !widget.exceptional && + // currentQuestion != null && + // annotationsFlag && + // !showAbnormal && + // !showStandardAnswer) + // Positioned( + // left: 0, + // bottom: 0, + // child: BottomAnnotationSwitch(maxWidth: maxWidth - 1.5.w), + // ), + // 大题评分展示框 + if (!hasSubtopic) + Positioned( + top: 16.h, + left: 5.w, + child: DottedBorder( + dashPattern: const [8, 4], + strokeWidth: 2, + padding: EdgeInsets.zero, + color: data.isException ? Colors.red : const Color.fromRGBO(46, 91, 255, 1), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: (data.score == null || + (getDoubleRemoveZero(data.score ?? 0)).length >= 2) + ? 4.w + : 12.w, + vertical: 6.h), + alignment: Alignment.center, + child: Text( + data.completeRating + ? getDoubleRemoveZero(data.score) + : '请评分,满分${getDoubleRemoveZero(currentQuestion?.totalScore)}', + style: TextStyle( + fontSize: 18.sp, + color: const Color.fromRGBO(46, 91, 255, 1), + ), + ), + ), + ), + ) + else + Positioned( + top: 16.h, + left: 5.w, + child: DottedBorder( + dashPattern: const [8, 4], + strokeWidth: 2, + padding: EdgeInsets.zero, + color: data.isException ? Colors.red : const Color.fromRGBO(46, 91, 255, 1), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: (data.score == null || + (getDoubleRemoveZero(data.score ?? 0)).length >= 2) + ? 4.w + : 12.w, + vertical: 6.h), + alignment: Alignment.center, + child: Text( + getDoubleRemoveZero(data.score, + '请评分,满分${getDoubleRemoveZero(currentQuestion?.totalScore)}'), + style: TextStyle( + fontSize: 18.sp, + color: const Color.fromRGBO(46, 91, 255, 1), + ), + ), + ), + ), + ), + // 上一题 按钮 + if (widget.markingtype != MarkingListType.EXCEPTIONAL && + !_theOldAnnotationGraffiti && + !notHasPreviousTest) + Positioned( + left: 3.w, + top: ScreenUtil().setHeight(MediaQuery.of(context).size.height) / 2 * 0.87, + child: FloatingActionButton( + tooltip: '前往上一题', + backgroundColor: notHasPreviousTest + ? const Color.fromRGBO(24, 32, 32, 0.04) + : const Color.fromRGBO(24, 32, 32, 0.1), + focusColor: Theme.of(context).primaryColor, + elevation: 0, + onPressed: notHasPreviousTest + ? null + : () => easyThrottle('TestQuestionSwitch', () => refresh()), + child: const Icon(Icons.arrow_back_ios, color: Colors.white), + heroTag: 'other', + ), + ), + // 下一题 按钮 + if (!_theOldAnnotationGraffiti && pressedNextTest != null && isNormal) + Positioned( + right: 2.w, + top: ScreenUtil().setHeight(MediaQuery.of(context).size.height) / 2 * 0.86, + child: FloatingActionButton( + elevation: 0, + tooltip: '点击前往下一题', + backgroundColor: notNextTest + ? const Color.fromRGBO(24, 32, 32, 0.04) + : const Color.fromRGBO(24, 32, 32, 0.1), + foregroundColor: Colors.white, + onPressed: pressedNextTest, + child: const Icon(Icons.arrow_forward_ios, color: Colors.white), + ), + ), + $AbnormalQuestionInfo( + score: data.score, + info: data.exceptionInfo, + isBroadwise: isBroadwise, + hasSubtopic: hasSubtopic, + show: widget.markingtype == MarkingListType.EXCEPTIONAL || data.isException, + ), + + $ArbitrationQuestionInfo( + data: data.historicalScorings ?? [], + show: widget.markingtype == MarkingListType.ARBITRATE, + hasSubtopic: hasSubtopic, + isBroadwise: isBroadwise, + ), + ], + ), + ), + + // 键盘 + if (isNormal && keyboardModel!.open) + MarkingKeyboardSwitch( + data: data, + markingUserId: widget.markingUserId, + subtopicIndex: activeQuestIndex, + questScore: questScore, + totalScore: data.totalScore.toString(), + closeKeyboard: () => toUpState(setState, () => keyboardModel!.open = false, mounted), + submitCall: () => submitTestQuestions(_thecontext, data), + synchroScore: synchroScore, + ) + ], + ), + ), + + // 底部键盘 + if (isNormal && keyboardModel!.open && keyboardModel!.keyboard == KeyboardType.BOTTOM_SELECTION) + SelectableKeyboardBottom( + markingUserId: widget.markingUserId, + data: data, + subtopicIndex: activeQuestIndex, + questScore: questScore, + totalScore: data.totalScore, + submitCall: () => submitTestQuestions(_thecontext, data), + synchroScore: synchroScore, + ) + ], + ); + }, + ), + + // 输入型键盘引导页 + if (inputKeyboardGuidePage) InputKeyboardGuidePage(closeCall: clostInputKeyboardGuidePage), + // 设置 + if (showSetingFlag) + SetingBox( + closeHandle: () => toUpState(setState, () => showSetingFlag = false, mounted), + exitPaper: () { + if (!needRefresh) return; + /* 是否需要刷新首页阅卷列表和阅卷主页列表主页刷新 */ + var theProvider = ref.read(currentTaskIdProvider.notifier); + CurrentReviewTask newTask = CurrentReviewTask(taskId: theProvider.state.taskId, refresh: true); + theProvider.setDoTaskEntity(newTask); + ref.read(markingKeyboardProvider.notifier).toggleKeyboard(ref.read(markingKeyboardProvider)); + }, + viewWholeTestPaper: viewEntireVolume, + viewAnswer: viewAnswer, + setingOperation: viewMarkingSettings, + ), + + // 评阅设置主页 + if (setingOperation) + MarkingSeting( + questTotalScore: currentQuestion?.totalScore, + defaultScoreInterval: currentQuestion?.scoreInterval ?? 1, + markingUserId: widget.markingUserId, + questionNum: currentQuestion?.questionNum ?? '', + tagName: _currentTab?.questionNum, + close: (bool closeFlag) async { + setingOperation = false; + if (closeFlag) showSetingFlag = false; + setState(() {}); + }, + ), + + // 异常显示区域 + $AbnormalBox( + businessHandle: (bool flag, String? reasonKey, String? otherReasons) => + initiateException(flag, reasonKey, otherReasons), + isBroadwise: isBroadwise, + showAbnormal: !showAbnormal, + ), + + // 标准答案 + if (showStandardAnswer && standardAnswers != null) + StandardAnswerRegion( + closeCall: () => {toUpState(setState, () => showStandardAnswer = false, mounted)}, + htmlWStr: standardAnswers!, + ), + + // 右侧键盘辅助键盘操作按钮 + if (currentQuestion != null && + !widget.exceptional && + !theRequesting && + hasAuxiliaryKeyboard && + model.keyboard == KeyboardType.RIGHT_SELECTION && + !(showStandardAnswer || showSetingFlag || showAbnormal)) + Positioned( + right: isBroadwise ? 72.h : 57.h, + top: 48.h, + child: GestureDetector( + onTap: () { + easyThrottle('Auxiliary_keyboard_control_buttons', () { + bool newOpenAuxiliary = !openAuxiliary; + eventFire(model: KeyboardAssistEvent(openAuxiliary: newOpenAuxiliary)); + setTimeOut(300, () { + // 双栏打分开关需要重新加载图片 + eventFireSub(model: SwitchKeyboardToReloadImages(true)); + }); + ref.read(annotationGraffitiSwitchProvider.notifier).setSwitch(false); + ToastUtils.showSuccess(newOpenAuxiliary ? '开启双栏打分' : '关闭双栏打分', + duration: const Duration(milliseconds: 300)); + }); + }, + // 触发总线事件 + child: Container( + height: isBroadwise ? 24.h : 18.h, + width: isBroadwise ? 18.w : 42.w, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: + BorderRadius.only(topLeft: Radius.circular(8.w), bottomLeft: Radius.circular(8.w)), + color: Theme.of(context).primaryColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + !openAuxiliary ? Icons.arrow_back_ios : Icons.arrow_forward_ios, + color: Colors.white, + size: isBroadwise ? 16.sp : 12.sp, + ), + quickText('双栏', color: Colors.white, size: isBroadwise ? 12.sp : 10.sp), + ], + )), + ), + ), + ], + ), + ), + drawer: MarkingQuestionTypeDrawer( + scaffoldKey: scaffoldKey, + currentTab: _currentTab, + currentTabs: _currentTabs, + call: (String tabQuestionNum) { + _future = getData(tabQuestionNum: tabQuestionNum); + scaffoldKey.currentState?.closeDrawer(); + }, + widthPercent: keyboardModel?.screenDirection == ScreenDirection.VERTICAL_SCREEN ? 0.6 : 0.38, + ), + endDrawer: ReviewRecords( + tabName: _currentTab?.questionNum ?? '', + tabs: _currentTabs, + direction: keyboardModel?.screenDirection ?? ScreenDirection.HORIZONTAL_SCREEN, + call: (int detailId, String selectedQuesiontNum) => + refresh(theId: detailId, theQuestionNum: selectedQuesiontNum), + markingUserId: widget.markingUserId, + questionNum: _currentTab?.questionNum, + ), + floatingActionButton: keyboardModel!.open || showStandardAnswer || showSetingFlag || showAbnormal + ? null + : FloatingActionButton( + onPressed: () { + toUpState(setState, () => keyboardModel!.open = true, mounted); + }, + + ///长按提示 + tooltip: "这是打开软键盘按钮", + + ///设置悬浮按钮的背景 + backgroundColor: Theme.of(context).primaryColor, + + ///获取焦点时显示的颜色 + focusColor: Colors.green, + + ///鼠标悬浮在按钮上时显示的颜色 + hoverColor: Colors.yellow, + + ///水波纹颜色 + splashColor: Colors.deepPurple, + + ///定义前景色 主要影响文字的颜色 + foregroundColor: Colors.black, + + ///配制阴影高度 未点击时 + elevation: 0.0, + + ///配制阴影高度 点击时 + highlightElevation: 20.0, + child: Text( + '打开键盘', + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + ), + ), + ), + + ///用来控制 FloatingActionButton 的位置 + ///FloatingActionButtonLocation.endFloat 默认使用 浮动右下角 + ///FloatingActionButtonLocation.endDocked 右下角 + ///FloatingActionButtonLocation.endTop 右上角 + ///FloatingActionButtonLocation.startTop 左上角 + ///FloatingActionButtonLocation.centerFloat 底部中间浮动 + ///FloatingActionButtonLocation.centerDocked 底部中间不浮动 + floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, + ), + onWillPop: () async { + if (!setingOperation && !showSetingFlag && !showStandardAnswer) { + ToastUtils.showInfo('阅卷模式下无法物理退出,请前往设置'); + } + return Future.value(false); + }, + ), + ); + } +} + +// 异常区域 +@hwidget +Widget $abnormalBox( + BuildContext context, { + required Function(bool, String?, String?) businessHandle, + required bool showAbnormal, + required bool isBroadwise, +}) { + UseAbnormal _useAbnormal = UseAbnormal.use(); + Map map = _useAbnormal.abnormals.value; + String? _abnormalsReason = _useAbnormal.abnormalsReason.value; + TextEditingController controller = useTextEditingController(); + + useValueChanged(showAbnormal, (oldValue, oldResult) { + controller.clear(); + _useAbnormal.abnormalsReason.value = null; + }); + useEffect(() { + _useAbnormal.getData(); + return () { + try { + controller.dispose(); + } catch (e) {} + }; + }, []); + + return Offstage( + offstage: showAbnormal, + child: Container( + height: double.infinity, + width: double.infinity, + color: const Color.fromRGBO(0, 0, 0, 0.65), + alignment: Alignment.center, + child: Container( + height: isBroadwise ? 400.h : 220.h, + width: double.infinity, + // padding: EdgeInsets.only(bottom: 10.h), + margin: EdgeInsets.symmetric(vertical: 80.h, horizontal: isBroadwise ? 100.w : 40.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(6.w)), + ), + child: Column( + children: [ + Container( + height: 42.h, + padding: EdgeInsets.only(left: 8.w), + alignment: Alignment.centerLeft, + decoration: BoxDecoration( + borderRadius: BorderRadius.only(topLeft: Radius.circular(6.w), topRight: Radius.circular(6.w)), + color: Theme.of(context).primaryColor, + ), + child: quickText('异常', color: Colors.white, size: 18.sp), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + // Image.asset( + // 'assets/images/abnormal_img.png', + // fit: BoxFit.cover, + // width: isBroadwise ? 42.w : 50.w, + // height: isBroadwise ? 42.w : 50.w, + // ), + // Text( + // '你确定该题异常?', + // style: TextStyle( + // fontSize: isBroadwise ? 20.sp : 16.sp, + // color: const Color.fromRGBO(0, 0, 0, 1), + // ), + // ), + Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + child: quickText( + '设置问题卷后,该试题交由管理员或科组长处理是否继续设置为问题卷?', + size: 20.sp, + color: const Color.fromRGBO(87, 89, 94, 1), + maxLines: 2, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 18.w), + child: Row( + children: [ + quickText('异常类型', size: 16.sp, color: Color.fromRGBO(80, 87, 103, 1)), + SizedBox(width: 4.w), + Expanded( + child: SizedBox( + height: 44.h, + child: DropdownSearch( + items: map.keys.toList(), + onChanged: (String? item) { + _useAbnormal.abnormalsReason.value = item; + }, + selectedItem: _abnormalsReason, + ), + ), + ) + ], + ), + ), + if (_abnormalsReason != null && map[_abnormalsReason] == '-1') + Container( + height: 60.h, + padding: EdgeInsets.symmetric(horizontal: 18.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText('异常原因', size: 16.sp, color: Color.fromRGBO(80, 87, 103, 1)), + SizedBox(width: 4.w), + Expanded( + child: Container( + height: 200.h, + padding: EdgeInsets.only(left: 2.w), + color: Color.fromRGBO(245, 246, 230, 1), + child: TextField( + controller: controller, + textInputAction: TextInputAction.next, + onEditingComplete: () => easyThrottle( + 'Abnormal_submission_confirmation_button', + () => _useAbnormal.submit(context, controller, + (reasonType, reason) => businessHandle(true, reasonType, reason))), + maxLines: 15, + keyboardType: TextInputType.multiline, + decoration: InputDecoration( + hintText: '请输入异常原因', // 设置提示文本 + hintStyle: TextStyle(fontSize: 12.sp), + border: InputBorder.none, + ), + ), + ), + ) + ], + ), + ), + Container( + padding: EdgeInsets.only(top: 10.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () => easyThrottle( + 'Abnormal_submission_confirmation_button', () => businessHandle(false, null, null)), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + margin: EdgeInsets.only(right: 16.w), + decoration: BoxDecoration( + color: const Color.fromRGBO(245, 246, 251, 1), + borderRadius: BorderRadius.all(Radius.circular(2.w)), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(0, 2.h), //阴影y轴偏移量 + blurRadius: 6, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: Text( + '取 消', + style: TextStyle( + color: const Color.fromRGBO(80, 87, 103, 1), + fontSize: isBroadwise ? 16.sp : 13.sp, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + if (!isBroadwise) SizedBox(width: 60.w) else SizedBox(width: 14.w), + InkWell( + onTap: () => easyThrottle( + 'Abnormal_submission_confirmation_button', + () => _useAbnormal.submit( + context, controller, (reasonType, reason) => businessHandle(true, reasonType, reason)), + ), + + // () { + // String? reasonType = map[_abnormalsReason]; + // String? reason = controller?.text; + // if (reasonType == null) { + // return ToastUtils.getFluttertoast(context: context, msg: '请选择异常类型'); + // } + // print(reason); + // if (reasonType == '-1' && (reason == null || reason.length <= 0)) { + // return ToastUtils.getFluttertoast(context: context, msg: '请填写异常原因'); + // } + // easyThrottle('Abnormal_submission_confirmation_button', + // () => businessHandle(true, reasonType, reason)); + // }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + gradient: const LinearGradient(colors: [ + Color.fromRGBO(54, 97, 255, 1), + Color.fromRGBO(93, 128, 255, 1), + ]), + borderRadius: BorderRadius.all(Radius.circular(2.w)), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(0, 2.h), //阴影y轴偏移量 + blurRadius: 6, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: Text( + '确 定', + style: TextStyle( + color: Colors.white, + fontSize: isBroadwise ? 16.sp : 13.sp, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); +} + +// 显示标准答案 +class StandardAnswerRegion extends StatelessWidget { + final GestureTapCallback closeCall; + final String htmlWStr; + const StandardAnswerRegion({required this.htmlWStr, required this.closeCall, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: const FractionalOffset(0.91, 0.12), + children: [ + Container( + height: double.infinity, + width: double.infinity, + color: const Color.fromRGBO(0, 0, 0, 0.65), + padding: EdgeInsets.symmetric(horizontal: 50.w, vertical: 90.h), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(5.w)), + color: Colors.white, + ), + child: ListView( + padding: EdgeInsets.all(6.w), + children: [HtmlWidget(htmlWStr)], + ), + ), + ), + InkWell( + onTap: closeCall, + child: Icon( + Icons.clear_sharp, + color: Colors.white, + size: 40.sp, + ), + ) + ], + ); + } +} + +/// 异常题提交异常信息 +@hwidget +Widget $abnormalQuestionInfo( + {required bool hasSubtopic, required bool isBroadwise, required bool show, double? score, ExceptionInfo? info}) { + final _useFlagAnimation = useState(true); + AnimationController _useAnimationHorizontal = useAnimationController( + initialValue: 300, + lowerBound: 30, + upperBound: 300, + duration: const Duration(milliseconds: 300), + ); + AnimationController _useAnimationVertical = useAnimationController( + initialValue: 280, + lowerBound: 30, + upperBound: 280, + duration: const Duration(milliseconds: 300), + ); + AnimationController _useAnimation = isBroadwise ? _useAnimationHorizontal : _useAnimationVertical; + + double left = 0; + double top = 14.h; + if (isBroadwise) { + // 横向 + left = hasSubtopic ? (score != null ? 40.w : 50.w) : 62.w; + } else { + // 纵向 + left = hasSubtopic ? 4.w : 4.w; + top = 60.h; + } + String? reason = info?.reason ?? info?.errorType; + if (!show || (reason == null)) return SizedBox(); + return Positioned( + top: top, + left: left, + child: AnimatedBuilder( + animation: _useAnimation, + builder: (BuildContext context, Widget? child) { + double widVal = _useAnimation.value; + + return Container( + clipBehavior: Clip.hardEdge, + width: _useAnimation.value, + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 1.6.h), + decoration: BoxDecoration( + color: const Color.fromRGBO(215, 74, 74, 0.06), + // color: Color.fromRGBO(255, 255, 255, 0), + borderRadius: BorderRadius.all(Radius.circular(4.r)), + // boxShadow: [ + // BoxShadow( + // color: Color.fromRGBO(46, 91, 255, 0.07), + // offset: Offset(0, 0), + // blurRadius: 2.r, + // spreadRadius: 0, + // ), + // ], + ), + child: widVal <= (isBroadwise ? 12.w : 36.w) + ? InkWell( + onTap: () { + _useAnimation.forward(); + _useFlagAnimation.value = true; + }, + child: Icon( + Icons.warning_amber_rounded, + color: const Color.fromRGBO(215, 74, 74, 1), + size: 20.sp, + ), + ) + : GestureDetector( + onTap: () { + if (_useFlagAnimation.value) { + _useAnimation.reverse(); + _useFlagAnimation.value = false; + } else { + _useAnimation.forward(); + _useFlagAnimation.value = true; + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.warning_amber_rounded, + color: const Color.fromRGBO(215, 74, 74, 1), + size: 20.sp, + ), + SizedBox(width: 4.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if ((info?.studentName.length ?? 0) > 0) + quickText('考生:${info!.studentName}', + color: const Color.fromRGBO(104, 104, 113, 1), size: 14.sp + // overflow: TextOverflow.clip, + ), + SizedBox(width: 3.w), + if ((info?.studentExamNum.length ?? 0) > 0) + quickText('考号:${info!.studentExamNum}', + color: const Color.fromRGBO(104, 104, 113, 1), size: 14.sp + // overflow: TextOverflow.clip, + ), + ], + ), + SizedBox(height: 1.h), + quickText( + '异常原因:$reason', + color: Color.fromRGBO(215, 74, 74, 1), + size: 16.sp, + maxLines: 2, + // overflow: TextOverflow.clip, + ), + ], + ), + ), + // quickText('异常原因:${info!.reason ?? info.errorType}', color: Color.fromRGBO(215, 74, 74, 1), size: 14.sp), + SizedBox(width: 4.w), + Icon( + Icons.arrow_back_ios_new, + color: const Color.fromRGBO(148, 163, 182, 1), + size: 20.sp, + ), + ], + ), + ), + ); + }, + ), + ); +} + +/// 冲裁题仲裁历史得分信息 +@hwidget +Widget $arbitrationQuestionInfo( + {required bool show, required bool isBroadwise, required bool hasSubtopic, required List data}) { + final _useFlagAnimation = useState(true); + AnimationController _useAnimationHorizontal = useAnimationController( + initialValue: 300, + lowerBound: 36, + upperBound: 300, + duration: const Duration(milliseconds: 300), + ); + AnimationController _useAnimationVertical = useAnimationController( + initialValue: 240, + lowerBound: 30, + upperBound: 240, + duration: const Duration(milliseconds: 300), + ); + AnimationController _useAnimation = isBroadwise ? _useAnimationHorizontal : _useAnimationVertical; + + double left = 0; + double top = 16.h; + if (isBroadwise) { + // 横向 + left = hasSubtopic ? 4.w : 62.w; + } else { + // 纵向 + left = hasSubtopic ? 4.w : 4.w; + top = 60.h; + } + if (!show) return Container(); + + return Positioned( + top: top, + left: left, + child: AnimatedBuilder( + animation: _useAnimation, + builder: (BuildContext context, Widget? child) { + double widVal = _useAnimation.value; + + return Container( + clipBehavior: Clip.hardEdge, + width: _useAnimation.value, + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 3.h), + decoration: BoxDecoration( + color: const Color.fromRGBO(4, 201, 208, 0.08), + borderRadius: BorderRadius.all(Radius.circular(4.r)), + // boxShadow: [ + // BoxShadow( + // color: Color.fromRGBO(46, 91, 255, 0.1), + // offset: Offset(0, 0), + // blurRadius: 2.r, + // spreadRadius: 0, + // ), + // ], + ), + child: widVal <= (isBroadwise ? 12.w : 36.w) + ? InkWell( + onTap: () { + _useAnimation.forward(); + _useFlagAnimation.value = true; + }, + child: Icon( + Icons.edit_square, + // color: const Color.fromRGBO(80, 84, 103, 1), + color: const Color.fromRGBO(4, 201, 208, 1), + size: 20.sp, + ), + ) + : GestureDetector( + onTap: () { + if (_useFlagAnimation.value) { + _useAnimation.reverse(); + _useFlagAnimation.value = false; + } else { + _useAnimation.forward(); + _useFlagAnimation.value = true; + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.edit_square, + color: const Color.fromRGBO(4, 201, 208, 1), + size: 20.sp, + ), + SizedBox(width: 4.w), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ...data.map((e) { + return quickText( + '${e.name}:${doubleToStringAsFixed(e.scorel)}', + color: const Color.fromRGBO(4, 201, 208, 1), + size: 16.sp, + maxLines: 2, + // overflow: TextOverflow.clip, + ); + }) + ], + ), + ), + // quickText('异常原因:${info!.reason ?? info.errorType}', color: Color.fromRGBO(215, 74, 74, 1), size: 14.sp), + SizedBox(width: 8.w), + Icon( + Icons.arrow_back_ios_new, + color: const Color.fromRGBO(148, 163, 182, 1), + size: 20.sp, + ), + ], + ), + ), + ); + }, + ), + ); +} diff --git a/marking_app/lib/pages/marking/hooks/use_abnormal.dart b/marking_app/lib/pages/marking/hooks/use_abnormal.dart new file mode 100644 index 0000000..0479c28 --- /dev/null +++ b/marking_app/lib/pages/marking/hooks/use_abnormal.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/marking/marking_abnormal_res.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/utils/toast_utils.dart'; + +class UseAbnormal with CommonMixin { + ValueNotifier> abnormals; // 设置主页 + ValueNotifier abnormalsReason; // 设置主页 + + UseAbnormal._({required this.abnormals, required this.abnormalsReason}); + + factory UseAbnormal.use() { + return UseAbnormal._(abnormals: useState({}), abnormalsReason: useState(null)); + } + + getData() async { + RestClient client = await getClient(); + BaseStructureResult result = await client.getAbnormalTypes(); + MarkingAbnormalRes? data = result.data; + if (result.success && data != null) { + Map errorsTypeDic = {}; + data.errorsTypeDic.forEach((key, value) => errorsTypeDic[value] = key); + abnormals.value = errorsTypeDic; + } + } + + submit(BuildContext context, TextEditingController controller, Function(String? reasonType, String? reason) call) { + String? reasonType = abnormals.value[abnormalsReason.value]; + String? reason = controller.text; + if (reasonType == null) { + return ToastUtils.getFluttertoast(context: context, msg: '请选择异常类型'); + } + print(reason); + if (reasonType == '-1' && (reason == null || reason.length <= 0)) { + return ToastUtils.getFluttertoast(context: context, msg: '请填写异常原因'); + } + call(reasonType, reason); + } +} diff --git a/marking_app/lib/pages/marking/hooks/use_zoomImage_history_utils.dart b/marking_app/lib/pages/marking/hooks/use_zoomImage_history_utils.dart new file mode 100644 index 0000000..497c462 --- /dev/null +++ b/marking_app/lib/pages/marking/hooks/use_zoomImage_history_utils.dart @@ -0,0 +1,15 @@ +import 'package:marking_app/common/model/marking/marking_zoom.dart'; +import 'package:marking_app/utils/index.dart'; + +class UseZoomImageHistoryUtils { + static Future getZoomImageInfo(String questionNumber) async { + print('题号:$questionNumber'); + MarkingZoom? markingZoom = await FastData.getInstance().getMarkingPapersImageZoom(); + print('KKKKKKKKKKKKK****************'); + print(markingZoom?.toJson()); + if (markingZoom != null && questionNumber != markingZoom.questionNumber) { + markingZoom = null; + } + return markingZoom; + } +} diff --git a/marking_app/lib/pages/marking/hooks/use_zoom_image_history.dart b/marking_app/lib/pages/marking/hooks/use_zoom_image_history.dart new file mode 100644 index 0000000..2dcbb0d --- /dev/null +++ b/marking_app/lib/pages/marking/hooks/use_zoom_image_history.dart @@ -0,0 +1,76 @@ +// 使用缩放图片hooks +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:marking_app/common/model/marking/marking_zoom.dart'; +import 'package:marking_app/utils/fast_data.dart'; + +class UseZoomImageHistory { + String theQuestionNumber; + + ValueNotifier initScale; + ValueNotifier initPosition; + + double? scale; + Offset? position; + + UseZoomImageHistory._({required this.theQuestionNumber, required this.initScale, required this.initPosition}); + + // 工厂构造函数 + factory UseZoomImageHistory.use(String questionNumber) { + return UseZoomImageHistory._( + theQuestionNumber: questionNumber, + initScale: useState(1), + initPosition: useState(Offset(0, 0)), + ); + } + + void onPositionUpdate(Offset offset) { + print('位置:$offset ;scale: ${initScale.value}'); + if (offset.dx == 0 && offset.dy == 0) return; + + double dx = offset.dx; + double dy = 0; + position = Offset(dx, dy); + initPosition.value = position!; + MarkingZoom theSetVal = MarkingZoom(dx: dx, dy: 0, questionNumber: theQuestionNumber, scale: initScale.value); + print('onPositionUpdate :${theSetVal.toJson()}'); + FastData.getInstance().setMarkingPapersImageZoom(theSetVal); + } + + void onScaleUpdate(double theScale, double zoom) { + print('缩放比例:$theScale; 缩放级别:$zoom'); + scale = zoom; + initScale.value = zoom; + MarkingZoom theSetVal = MarkingZoom( + dx: position?.dx ?? initPosition.value.dx, + // dy: position?.dy ?? initPosition.value.dy, + dy: 0, + questionNumber: theQuestionNumber, + scale: zoom, + ); + print('onScaleUpdate :${theSetVal.toJson()}'); + FastData.getInstance().setMarkingPapersImageZoom(theSetVal); + } + + // 初始化历史信息 + void initInfo([String? questionNumber]) async { + MarkingZoom? zoomInfo = await FastData.getInstance().getMarkingPapersImageZoom(); + if (zoomInfo != null) { + if (zoomInfo.questionNumber == (questionNumber ?? theQuestionNumber)) { + // 有对应的试题 + + if (questionNumber != null) { + theQuestionNumber = questionNumber; // 赋值questionNumber + } + + initScale.value = zoomInfo.scale; + initPosition.value = Offset(zoomInfo.dx, zoomInfo.dy); + print('执行了位置初始化............${initScale.value} -- ${initPosition.value}'); + scale = zoomInfo.scale; + position = initPosition.value; + return; + } + FastData.getInstance().cleanMarkingPapersImageZoom(); // 不是对应的试题就清空历史题型数据 + } + } +} diff --git a/marking_app/lib/pages/marking/index.dart b/marking_app/lib/pages/marking/index.dart new file mode 100644 index 0000000..9800ff3 --- /dev/null +++ b/marking_app/lib/pages/marking/index.dart @@ -0,0 +1,322 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-05 16:42:44 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 16:15:03 + * @FilePath: \marking_app\lib\pages\marking\index.dart + * @Description: 阅卷主页 + */ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; +import 'package:marking_app/common/model/event_bus/marking_statistics_bus.dart'; +import 'package:marking_app/common/model/marking/marking_statistics.dart'; +import 'package:marking_app/pages/common/event_bus_mixin.dart'; +import 'package:marking_app/provider/review_provider.dart'; +import 'package:marking_app/utils/anti_shake_throttling.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/marking/marking_item.dart'; +import 'package:marking_app/common/model/marking/marking_list_params.dart'; +import 'package:marking_app/components/TestPaperItem.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; + +// 阅卷主页 +class TheMarking extends StatefulHookConsumerWidget { + const TheMarking({Key? key}) : super(key: key); + + @override + _TheMarkingState createState() => _TheMarkingState(); +} + +class _TheMarkingState extends ConsumerState + with + CommonMixin, + EventBusMixin, + SingleTickerProviderStateMixin, + RefreshDataHandle, + AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + + /* Tab控制器 */ + late TabController _tabController; + int _tabIndex = 0; + bool completedToRefresh = true; + + Map tabNumsMap = new Map(); + + /* 阅卷 */ + late final EasyRefreshController _refreshController1; + final MarkingListParams params1 = MarkingListParams( + isFinish: false, + pageType: MarkingListType.NORMAL.index, + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + ); + List markingDatas1 = []; + + /* 仲裁 */ + late final EasyRefreshController _refreshController2; + RemoveListener? _currentTaskIdListener; + final MarkingListParams params2 = MarkingListParams( + isFinish: false, + pageType: 1, + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + ); + List markingDatas2 = []; + + /* 异常 */ + late final EasyRefreshController _exceptionalRefreshController; + final MarkingListParams exceptionalParams = MarkingListParams( + isFinish: false, + pageType: 2, + page: RequestConfig.basePage.page, + limit: RequestConfig.basePage.limit, + ); + List exceptionalMarkingDatas = []; + + Future> getData(EasyRefreshController controller, MarkingListParams theParams, + {bool isReFresh = false}) async { + if (!isReFresh) { + theParams.page++; + } + + RestClient client = await getClient(); + BasePageData? results = await toRefreshData( + controller, + api: client.getMarkingsByPage, + params: theParams, + isReFresh: isReFresh, + context: context, + ); + if (results != null) { + if (_tabIndex == 0) { + eventFire(model: MarkingStatisticsMainListBus()); + } + return results.items; + } + return []; + } + + void getTabNumsData() async { + RestClient client = await getClient(); + Map map = {}; + BaseStructureResult> result = await client.getMarkingStatistics(); + if (result.success && result.data != null) { + List _theTypes = MarkingListType.values; + result.data!.forEach((e) => map[_theTypes[e.navld]] = e.total); + } + tabNumsMap = map; + } + + @override + void initState() { + _tabController = TabController(length: 3, vsync: this); + _refreshController1 = EasyRefreshController(); + _refreshController2 = EasyRefreshController(); + _exceptionalRefreshController = EasyRefreshController(); + + Future.delayed(Duration.zero, () { + getTabNumsData(); + //在这里处理页面 + _currentTaskIdListener = ref.watch(currentTaskIdProvider.notifier).addListener((state) { + int? taskId = state.taskId; + + if (taskId != null && state.refresh) { + getTabNumsData(); + _refreshController1.callRefresh(); + _refreshController2.callRefresh(); + _exceptionalRefreshController.callRefresh(); + } + }); + }); + super.initState(); + } + + @override + void dispose() { + if (_currentTaskIdListener != null) { + _currentTaskIdListener!(); + } + eventCancel(); + _tabController.dispose(); + _refreshController1.dispose(); + _refreshController2.dispose(); + _exceptionalRefreshController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + super.build(context); //调用super.build(返回值始终返回null,应将其忽略) + + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + body: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 4.h), + child: TabBar( + controller: _tabController, + unselectedLabelStyle: TextStyle(fontSize: 14.sp), + labelStyle: TextStyle( + fontSize: 16.sp, fontWeight: FontWeight.bold, color: const Color.fromRGBO(148, 163, 182, 1)), + labelColor: const Color.fromRGBO(45, 56, 76, 1), + indicatorSize: TabBarIndicatorSize.label, + onTap: (index) { + setState(() { + _tabIndex = index; + if (index == 1 && markingDatas2.length == 0) { + _refreshController2.callRefresh(); + } else if (index == 2 && exceptionalMarkingDatas.length == 0) { + _exceptionalRefreshController.callRefresh(); + } + }); + }, + tabs: [ + Tab( + text: '阅卷' + + (tabNumsMap[MarkingListType.NORMAL] == null + ? '' + : '(${tabNumsMap[MarkingListType.NORMAL]})')), + Tab( + text: '仲裁' + + (tabNumsMap[MarkingListType.ARBITRATE] == null + ? '' + : '(${tabNumsMap[MarkingListType.ARBITRATE]})')), + Tab( + text: '异常' + + (tabNumsMap[MarkingListType.EXCEPTIONAL] == null + ? '' + : '(${tabNumsMap[MarkingListType.EXCEPTIONAL]})')), + ], + ), + ), + // $TypesOf(controller: controller, params: params), + // MarkingStatisticsBox(controller: controller, isShow: _tabIndex != 0, params: params), + Expanded( + child: IndexedStack( + index: _tabIndex, + children: [ + EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: markingDatas1.isEmpty ? const MyEmptyWidget() : null, + controller: _refreshController1, + header: MaterialHeader(), + footer: TaurusFooter(), + child: ListView.builder( + padding: EdgeInsets.only(top: 4.h, bottom: 10.h), + itemBuilder: (context, index) => TestPaperItem( + markingItem: markingDatas1[index], + markingtype: MarkingListType.values[params1.pageType], + call: () => _refreshController1.callRefresh(), + ), + itemCount: markingDatas1.length, + ), + onRefresh: () async { + List lists = await getData(_refreshController1, params1, isReFresh: true); + if (lists.isNotEmpty) { + setState(() => markingDatas1 = lists); + } + }, + onLoad: () async { + List lists = await getData(_refreshController1, params1); + if (lists.isNotEmpty) { + setState(() => markingDatas1.addAll(lists)); + } + }, + ), + EasyRefresh( + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: markingDatas2.isEmpty ? const MyEmptyWidget() : null, + controller: _refreshController2, + header: MaterialHeader(), + footer: TaurusFooter(), + child: ListView.builder( + padding: EdgeInsets.only(top: 4.h, bottom: 10.h), + itemBuilder: (context, index) => TestPaperItem( + markingItem: markingDatas2[index], + markingtype: MarkingListType.values[params2.pageType], + ), + itemCount: markingDatas2.length, + ), + onRefresh: () async { + List lists = await getData(_refreshController2, params2, isReFresh: true); + if (lists.isNotEmpty) { + setState(() => markingDatas2 = lists); + } + }, + onLoad: () async { + List lists = await getData(_refreshController2, params2); + if (lists.isNotEmpty) { + setState(() => markingDatas2.addAll(lists)); + } + }, + ), + EasyRefresh( + firstRefresh: false, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + emptyWidget: exceptionalMarkingDatas.isEmpty ? const MyEmptyWidget() : null, + controller: _exceptionalRefreshController, + header: MaterialHeader(), + footer: TaurusFooter(), + child: ListView.builder( + padding: EdgeInsets.only(top: 4.h, bottom: 10.h), + itemBuilder: (context, index) => TestPaperItem( + markingItem: exceptionalMarkingDatas[index], + markingtype: MarkingListType.values[exceptionalParams.pageType], + call: () => _exceptionalRefreshController.callRefresh(), + ), + itemCount: exceptionalMarkingDatas.length, + ), + onRefresh: () async { + print('参数:' + exceptionalParams.toJson().toString()); + List lists = + await getData(_exceptionalRefreshController, exceptionalParams, isReFresh: true); + if (lists.isNotEmpty) { + setState(() => exceptionalMarkingDatas = lists); + } + }, + onLoad: () async { + List lists = await getData(_exceptionalRefreshController, exceptionalParams); + if (lists.isNotEmpty) { + setState(() => exceptionalMarkingDatas.addAll(lists)); + } + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/marking_app/lib/pages/marking/progress.dart b/marking_app/lib/pages/marking/progress.dart new file mode 100644 index 0000000..a7ec43d --- /dev/null +++ b/marking_app/lib/pages/marking/progress.dart @@ -0,0 +1,455 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-15 09:14:59 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-29 10:22:25 + * @FilePath: \marking_app\lib\pages\marking\progress.dart + * @Description: 进度页面 + */ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/progress/progress_item.dart'; +import 'package:marking_app/common/model/progress/progress_page_params.dart'; +import 'package:marking_app/common/model/progress/progress_statistics.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; + +class Progress extends StatefulWidget { + final String name; + final int examSubjectId; + const Progress(this.examSubjectId, this.name, {Key? key}) : super(key: key); + + @override + State createState() => ProgressState(); +} + +class ProgressState extends State + with SingleTickerProviderStateMixin, CommonMixin, RefreshDataHandle { + late final String name; + late final int examSubjectId; + late ProgressPageParams pageParams; + List listData = []; + ProgressStatistics statisticsData = ProgressStatistics(0, 0, 0, 0, 0); /* 发起请求获取统计数据 */ + late final EasyRefreshController _refreshController; + + /* + * 获取统计数据 + * getDataForStatistics + */ + void getDataForStatistics() async { + RestClient client = await getClient(); + BaseStructureResult result = await client.getProgressStatistics(examSubjectId.toString()); + if (result.code == RequestConfig.successCode) { + toUpState(setState, () => statisticsData = result.data!, mounted); + } + } + + /* + * 获取分页数据 + * getDataForPageData + */ + void getDataForPageData(EasyRefreshController controller, {bool isReFresh = false}) async { + RestClient client = await getClient(); + + BasePageData? results = await toRefreshDataWithPath( + controller, + api: client.getProgressByPage, + params: pageParams, + pahtVal: examSubjectId.toString(), + isReFresh: isReFresh, + context: context, + ); + if (results != null && results.items.isNotEmpty) { + if (isReFresh) { + setState(() => listData = results.items); + } else { + setState(() => listData.addAll(results.items)); + } + } + } + + @override + void initState() { + name = widget.name; + examSubjectId = widget.examSubjectId; + pageParams = ProgressPageParams(examSubjectId: examSubjectId, page: 1, limit: 10); + _refreshController = EasyRefreshController(); + + getDataForStatistics(); + super.initState(); + } + + @override + void dispose() { + _refreshController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.dark, + ), + child: Stack( + children: [ + SizedBox( + height: double.infinity, + child: Column( + children: [ + Container( + height: 200.h, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/personal_bgi.png'), + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Container( + color: const Color.fromRGBO(248, 248, 248, 1), + ), + ) + ], + ), + ), + Scaffold( + backgroundColor: Colors.transparent, + appBar: PreferredSize( + preferredSize: Size.fromHeight(40.h), + child: AppBar( + title: quickText( + '$name进度', + size: 18.sp, + color: Colors.white, + ), + elevation: 0, + backgroundColor: Colors.transparent, + ), + ), + body: EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + controller: _refreshController, + onRefresh: () async => getDataForPageData(_refreshController, isReFresh: true), + onLoad: () async { + pageParams.page += 1; + getDataForPageData(_refreshController); + }, + header: DeliveryHeader(), + footer: MaterialFooter(), + child: ListView( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 14.h), + children: [_StatisticsInfo(widget.name, statisticsData), ...listData.map((e) => _DataItem(e))], + ), + ), + ), + ], + ), + ); + } +} + +// 统计情况 +// ignore: must_be_immutable +class _StatisticsInfo extends StatelessWidget { + String name; + ProgressStatistics theData; + + _StatisticsInfo(this.name, this.theData, {Key? key}) : super(key: key); + + final TextStyle ts2 = TextStyle( + fontSize: 12.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ); + + final TextStyle ts1 = TextStyle( + fontSize: 18.sp, + color: const Color.fromRGBO(45, 56, 76, 1), + ); + + @override + Widget build(BuildContext context) { + TextStyle ts11 = TextStyle( + fontSize: 18.sp, + color: Theme.of(context).primaryColor, + ); + + double myAverage = theData.myAverage; + double overallAverage = theData.overallAverage; + + double comparison = myAverage - overallAverage; // 比较情况 + String imageUrl; + if (comparison > 0) { + imageUrl = 'assets/images/rise.png'; + } else if (comparison < 0) { + imageUrl = 'assets/images/fall.png'; + } else { + imageUrl = 'assets/images/equal.png'; + } + Image comparisonImg = Image.asset(imageUrl, fit: BoxFit.cover, width: 10.w, height: 10.w); + + return Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), + height: 194.h, + margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(6.w)), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset(6.w, 10.h), //阴影y轴偏移量 + blurRadius: 14, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + textAlign: TextAlign.left, + style: TextStyle(fontSize: 14.sp, color: const Color.fromRGBO(45, 56, 76, 1)), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(top: 18.h), + child: Row( + children: [ + Stack( + alignment: const FractionalOffset(0.5, 0.5), + children: [ + SizedBox( + width: 96.w, + height: 96.w, + child: theData.markingTotal == 0 + ? null + : CircularProgressIndicator( + backgroundColor: const Color.fromRGBO(217, 225, 255, 1), + valueColor: AlwaysStoppedAnimation(Theme.of(context).primaryColor), + value: theData.completionRate, + strokeWidth: 9.w), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + quickText( + '${theData.completionRateStr}%', + size: 20.sp, + color: Theme.of(context).primaryColor, + ), + quickText( + '整体进度', + size: 10.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ) + ], + ), + ], + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + children: [ + Text('${theData.markingFinishedCount}', style: ts1), + Container(height: 3.h), + Text('已阅总量', style: ts2), + ], + ), + Column( + children: [ + Text('${theData.markingUnfinishedCount}', style: ts11), + Container(height: 3.h), + Text('待阅总量', style: ts2), + ], + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + children: [ + Text(overallAverage.toStringAsFixed(2), style: ts1), + Container(height: 3.h), + Text('整体均分', style: ts2), + ], + ), + Stack( + alignment: const FractionalOffset(1.7, 0.4), + children: [ + Column( + children: [ + Text(myAverage.toStringAsFixed(2), style: ts11), + Container(height: 3.h), + Text('我的平均分', style: ts2), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + comparisonImg, + Container(height: 1.h), + Text( + (overallAverage - myAverage).toStringAsFixed(2), + style: TextStyle( + fontSize: 9.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ), + ], + ) + ], + ) + ], + ) + ], + ), + ) + ], + ), + )) + ], + ), + ); + } +} + +// 数据单个详情 +class _DataItem extends StatelessWidget { + ProgressItem item; + + _DataItem(this.item, {Key? key}) : super(key: key); + GlobalKey _redKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Container( + height: 100.h, + margin: EdgeInsets.only(top: 12.h), + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(6.w)), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset(6.w, 4.h), //阴影y轴偏移量 + blurRadius: 8, //阴影模糊程度 + spreadRadius: 0.3, //阴影扩散程度 + ) + ], + ), + child: Column( + // crossAxisAlignment: C, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Text( + '${item.teacherName}老师', + style: TextStyle( + color: const Color.fromRGBO(45, 56, 76, 1), + fontWeight: FontWeight.w700, + fontSize: 14.sp, + ), + ), + Container( + margin: EdgeInsets.only(left: 14.w), + child: Row( + children: [ + Text( + '评阅均分:', + style: TextStyle( + color: const Color.fromRGBO(46, 91, 255, 1), + fontSize: 12.sp, + ), + ), + Text( + item.avgScore.toStringAsFixed(2), + style: TextStyle( + color: const Color.fromRGBO(46, 91, 255, 1), + fontSize: 12.sp, + ), + ), + ], + ), + ) + ], + )), + Text( + item.schoolName, + style: TextStyle( + fontSize: 12.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + ) + ], + ), + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: Row( + children: [ + Expanded( + child: LinearPercentIndicator( + padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), + // width: MediaQuery.of(context).size.width - 50, + animation: true, + lineHeight: 10.h, + animationDuration: 2500, + percent: item.completionRate, + center: quickText( + '${item.completionRateStr}%', + size: 10.sp, + color: Colors.white, + ), + // linearStrokeCap: LinearStrokeCap.butt, + progressColor: Theme.of(context).primaryColor, + backgroundColor: const Color.fromRGBO(219, 224, 243, 1), + barRadius: Radius.circular(10.h)), + ), + Container( + margin: EdgeInsets.only(left: 14.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + quickText(item.finishCount.toString(), size: 12.sp, color: Theme.of(context).primaryColor), + quickText('/', size: 12.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + quickText(item.totalCount.toString(), size: 12.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + ], + ), + ) + ], + ), + ) + ], + ), + ); + } +} diff --git a/marking_app/lib/pages/marking/progress_abnormal.dart b/marking_app/lib/pages/marking/progress_abnormal.dart new file mode 100644 index 0000000..a24db4d --- /dev/null +++ b/marking_app/lib/pages/marking/progress_abnormal.dart @@ -0,0 +1,114 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-21 15:14:10 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 18:12:08 + * @FilePath: \marking_app\lib\pages\marking\progress_abnormal.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/review/review_item.dart'; +import 'package:marking_app/common/model/review/review_page_params.dart'; +import 'package:marking_app/components/review_item_view.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; + +class ProgressAbnormal extends StatefulWidget { + final String markingId; + final String name; + final String questionNum; + final int examSubjectId; + const ProgressAbnormal( + this.examSubjectId, this.markingId, this.name, this.questionNum, + {Key? key}) + : super(key: key); + + @override + State createState() => + _ProgressAbnormalState(markingId, questionNum); +} + +class _ProgressAbnormalState extends State + with RefreshDataHandle, CommonMixin { + final ReviewPageParams params; + _ProgressAbnormalState(String markingId, String questionNum) + : params = ReviewPageParams( + qNum: questionNum == '' ? null : questionNum, + isException: true, + page: 1, + limit: 10, + ); + + late final EasyRefreshController controller; + List listData = []; + void toGetPageData({bool isReFresh = false}) async { + RestClient client = await getClient(); + BasePageData? results = await toRefreshDataWithPath( + controller, + api: client.getReviewPage, + params: params, + pahtVal: widget.markingId, + isReFresh: isReFresh, + context: context, + ); + if (results != null && results.items.isNotEmpty) { + if (isReFresh) { + listData.clear(); + setState(() => listData = results.items); + } else { + setState(() => listData.addAll(results.items)); + } + } + } + + @override + void initState() { + controller = EasyRefreshController(); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('${widget.name}的异常详情'), + ), + body: EasyRefresh( + firstRefresh: true, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + header: MaterialHeader(), + footer: TaurusFooter(), + emptyWidget: listData.isEmpty ? const MyEmptyWidget() : null, + controller: controller, + child: ListView.builder( + padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h), + itemBuilder: (context, index) => ReviewItemView( + int.parse(widget.markingId), + listData[index], + exceptional:true, + widget.examSubjectId), + itemCount: listData.length, + ), + onRefresh: () async => toGetPageData(isReFresh: true), + onLoad: () async { + params.page += 1; + toGetPageData(); + }, + ), + ); + } +} diff --git a/marking_app/lib/pages/marking/provider/rating_progress_provider.dart b/marking_app/lib/pages/marking/provider/rating_progress_provider.dart new file mode 100644 index 0000000..67156a7 --- /dev/null +++ b/marking_app/lib/pages/marking/provider/rating_progress_provider.dart @@ -0,0 +1,28 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// 评分进度还是批阅进度 +final ratingProgressProvider = StateNotifierProvider( + (ref) => ZoomHeightProviderHandle(EndDrawerViewEnum.REVIEW_RECORD)); + +class ZoomHeightProviderHandle extends StateNotifier { + ZoomHeightProviderHandle(EndDrawerViewEnum theHeight) : super(theHeight); + + void setState(EndDrawerViewEnum val) { + state = val; + } +} + +// 批阅 +enum EndDrawerViewEnum { + RATING_PROGRESS, // 评分进度 + REVIEW_RECORD, // 评阅记录 +} diff --git a/marking_app/lib/pages/marking/provider/zoom_height_provider.dart b/marking_app/lib/pages/marking/provider/zoom_height_provider.dart new file mode 100644 index 0000000..080b5a2 --- /dev/null +++ b/marking_app/lib/pages/marking/provider/zoom_height_provider.dart @@ -0,0 +1,22 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// 涂鸦批注开关 +final zoomHeightProvider = + StateNotifierProvider((ref) => ZoomHeightProviderHandle(0)); + +class ZoomHeightProviderHandle extends StateNotifier { + ZoomHeightProviderHandle(double theHeight) : super(theHeight); + + void setState(double val) { + state = val; + } +} diff --git a/marking_app/lib/pages/marking/provider/zoom_history_provider.dart b/marking_app/lib/pages/marking/provider/zoom_history_provider.dart new file mode 100644 index 0000000..eb484a9 --- /dev/null +++ b/marking_app/lib/pages/marking/provider/zoom_history_provider.dart @@ -0,0 +1,22 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +// 涂鸦批注开关 +final zoomHistoryProvider = + StateNotifierProvider((ref) => ZoomHistoryProviderHandle(0)); + +class ZoomHistoryProviderHandle extends StateNotifier { + ZoomHistoryProviderHandle(double theHeight) : super(theHeight); + + void setState(double val) { + state = val; + } +} diff --git a/marking_app/lib/pages/marking/review.dart b/marking_app/lib/pages/marking/review.dart new file mode 100644 index 0000000..c514135 --- /dev/null +++ b/marking_app/lib/pages/marking/review.dart @@ -0,0 +1,670 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-15 09:20:43 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-29 10:17:22 + * @FilePath: \marking_app\lib\pages\marking\review.dart + * @Description: 回评页面 + */ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/model/review/additional_conditions_for_review.dart'; +import 'package:marking_app/utils/drawer_util/auto_drawer.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/review/review_item.dart'; +import 'package:marking_app/common/model/review/review_page_params.dart'; +import 'package:marking_app/common/model/review/review_tab.dart'; +import 'package:marking_app/components/review_item_view.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle.dart'; +import 'package:marking_app/utils/index.dart'; + +class Review extends StatefulWidget { + final int examSubjectId; + final int markingUserId; + final String examName; + const Review(this.markingUserId, this.examSubjectId, this.examName, {Key? key}) : super(key: key); + + @override + State createState() => _ReviewState(); +} + +class _ReviewState extends State with CommonMixin, SingleTickerProviderStateMixin { + final GlobalKey<_TheBodyBoxState> _key = GlobalKey<_TheBodyBoxState>(); + late final AdditionalConditionsForReview otherConditions = AdditionalConditionsForReview(); // 赛选条件 + late final int markingUserId; + late final String examName; + + late Future> _future; + late final TabController? _tabController; + + /* 请求获取回评tab */ + Future> getTabs() async { + RestClient client = await getClient(); + BaseStructureResult> result = await client.getReviewTab(markingUserId.toString(), markingUserId); + if (result.code == 200 && result.data != null) { + int allNUmber = result.data!.isEmpty + ? 0 + : result.data!.map((e) => e.questionCount).reduce((value, element) => value + element); + List data = [ReviewTab(allNUmber, '全部'), ...result.data!]; + return data; + } + return []; + } + + @override + void initState() { + markingUserId = widget.markingUserId; + examName = widget.examName; + + _future = getTabs()..then((value) => _tabController = TabController(length: value.length, vsync: this)); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _tabController?.dispose(); + } + + // 抽屉关闭回调 + void autoDrawerSwitch(bool val) {} + + // 筛选条件回调 + Future conditionsDrawerCall({required bool reset, CollationItem? item, String? testId}) async { + if (reset || (item == null && testId == null)) { + // 筛选条件重置 + otherConditions.cleanCollation().whenComplete(() => _key.currentState?.setingOtherParams()); + return; + } + + // 同步数据 + otherConditions.synchronizationInstance(item, testId); + _key.currentState?.setingOtherParams(otherParam: otherConditions); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.dark, + ), + child: Container( + width: ScreenUtil().scaleWidth, + height: ScreenUtil().scaleHeight, + color: Colors.white, + child: Stack( + children: [ + const ReviewHeadBox(), + Scaffold( + backgroundColor: Colors.transparent, + appBar: PreferredSize( + preferredSize: Size.fromHeight(40.h), + child: AppBar( + title: quickText( + '回评', + size: 18.sp, + color: Colors.white, + ), + elevation: 0, + actions: [ + Container( + height: double.infinity, + padding: EdgeInsets.only(right: 6.w), + alignment: Alignment.center, + child: InkWell( + onTap: () { + _key.currentState!.seeAbnormal(); + }, + child: Text( + '查看异常', + style: TextStyle(fontSize: 14.sp, color: Colors.white), + ), + ), + ) + ], + backgroundColor: Colors.transparent, + ), + ), + body: MyFutureBuilder.buildFutureBuilder( + context, + _future, + (List data) { + return TheBodyBox( + data, + _tabController!, + markingUserId, + widget.examSubjectId, + key: _key, + ); + }, + ), + floatingActionButton: Builder(builder: (context) { + return FloatingActionButton( + tooltip: '筛选条件', + backgroundColor: Theme.of(context).primaryColor, + foregroundColor: Colors.white, + onPressed: () { + Scaffold.of(context).openEndDrawer(); //打开右边抽屉 + }, + child: Icon(Icons.filter_alt_outlined, size: 24.sp), + ); + }), + drawerEdgeDragWidth: 0.0, + // 右侧抽屉 + endDrawer: AutoDrawer( + callback: autoDrawerSwitch, + widthPercent: 0.72, + child: ConditionsDrawer( + condition: otherConditions, + callback: conditionsDrawerCall, + ), + ), + ), + ], + ), + ), + ); + } +} + +class TheBodyBox extends StatefulWidget { + final List data; + final TabController tabController; + final int examSubjectId; + final int markingUserId; + final Function? call; + const TheBodyBox(this.data, this.tabController, this.markingUserId, this.examSubjectId, {this.call, Key? key}) + : super(key: key); + + @override + State createState() => _TheBodyBoxState(); +} + +class _TheBodyBoxState extends State with CommonMixin, RefreshDataHandle { + late final int markingUserId; + late final TabController tabController; + int _tabIndex = 0; + late final List reviewTabs; + late final List tabs = []; + late final List refreshCotlrs = []; + late final List refreshParams = []; + late final List> refreshDatas = []; + late final List tabRefresh = []; + + @override + void initState() { + reviewTabs = widget.data; + tabController = widget.tabController; + markingUserId = widget.markingUserId; + + disRefreshCotlrs(); + for (ReviewTab e in reviewTabs) { + String questionNum = e.questionNum; + tabs.add(Tab(text: '$questionNum(${e.questionCount})')); + refreshCotlrs.add(EasyRefreshController()); + refreshParams.add(ReviewPageParams( + qNum: questionNum == '全部' ? null : questionNum, + isException: false, + page: 1, + limit: 10, + )); + refreshDatas.add([]); + tabRefresh.add(false); + } + if (tabRefresh.isNotEmpty) { + tabRefresh[0] = true; + } + + super.initState(); + } + + /* 发起请求 */ + Future?> toGetPageData(EasyRefreshController controller, ReviewPageParams params, + {bool isReFresh = false}) async { + RestClient client = await getClient(); + // ignore: use_build_context_synchronously + BasePageData? results = await toRefreshDataWithPath( + controller, + api: client.getReviewPage, + params: params, + isReFresh: isReFresh, + pahtVal: markingUserId.toString(), + context: context, + ); + if (results != null) return results.items; + } + + // 查看异常 + void seeAbnormal() { + ReviewTab tab = reviewTabs[_tabIndex]; + String thePath = + '${RouterManager.markingReviewAbnormalPath}?markingId=$markingUserId&examSubjectId=${widget.examSubjectId}&name=${Uri.encodeComponent(tab.questionNum)}&questionNum=${Uri.encodeComponent(tab.questionNum == '全部' ? '' : tab.questionNum)}'; + + RouterManager.router.navigateTo( + context, + thePath, + transition: getTransition(), + ); + } + + // 设置其他赛选参数 + void setingOtherParams({AdditionalConditionsForReview? otherParam}) { + // 同步参数、初始化上拉下拉插件 + if (otherParam == null) { + for (var param in refreshParams) { + param.markingUserDetailId = null; + param.markingTimeOrderType = null; + param.markingUserDetailId = null; + } + refreshCotlrs[_tabIndex].callRefresh(); + return; + } + + int? markingTimeOrderType = otherParam.markingTimeOrderType; + int? scoreOrderType = otherParam.scoreOrderType; + + String? questionIdStr = otherParam.questionId; + int? questionId = questionIdStr == null ? null : (questionIdStr == '' ? null : int.parse(questionIdStr)); + + refreshParams.asMap().keys.forEach((index) { + ReviewPageParams param = refreshParams[index]; + + param.markingUserDetailId = questionId; + if (markingTimeOrderType != null) { + param.markingTimeOrderType = markingTimeOrderType; + param.scoreOrderType = null; + } + + if (scoreOrderType != null) { + param.scoreOrderType = scoreOrderType; + param.markingTimeOrderType = null; + } + tabRefresh[index] = false; + }); + refreshCotlrs[_tabIndex].callRefresh(); + } + + @override + void dispose() { + super.dispose(); + disRefreshCotlrs(); + } + + // 清空上拉加载下拉刷新工具 + void disRefreshCotlrs() { + tabs.clear(); + if (refreshCotlrs.isEmpty) return; + for (var e in refreshCotlrs) { + e.dispose(); + } + refreshCotlrs.clear(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 10.h), + child: Align( + alignment: Alignment.centerLeft, + child: TabBar( + isScrollable: true, + controller: tabController, + indicatorColor: Colors.white, + unselectedLabelStyle: TextStyle(fontSize: 14.sp, color: const Color.fromRGBO(173, 191, 255, 1)), + labelStyle: TextStyle(fontSize: 16.sp, color: const Color.fromRGBO(148, 163, 182, 1)), + labelColor: Colors.white, + indicatorSize: TabBarIndicatorSize.label, + onTap: (index) { + bool flagTbRsh = tabRefresh[index]; + setState(() { + _tabIndex = index; + if (!flagTbRsh) { + refreshCotlrs[index].callRefresh(); + tabRefresh[index] = true; + } + }); + }, + tabs: tabs, + ), + ), + ), + Expanded( + child: IndexedStack( + index: _tabIndex, + children: [ + ...tabs.asMap().keys.map((theIdx) { + EasyRefreshController controller = refreshCotlrs[theIdx]; + ReviewPageParams param = refreshParams[theIdx]; + List reviewItems = refreshDatas[theIdx]; + return EasyRefresh( + firstRefresh: theIdx == 0, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + header: MaterialHeader(), + footer: TaurusFooter(), + emptyWidget: reviewItems.isEmpty ? const MyEmptyWidget() : null, + controller: controller, + child: ListView.builder( + padding: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h), + itemBuilder: (context, index) => + ReviewItemView(markingUserId, reviewItems[index], widget.examSubjectId), + itemCount: reviewItems.length, + ), + onRefresh: () async { + param.page = 1; + List? data = await toGetPageData(controller, param, isReFresh: true); + if (data != null) { + reviewItems.clear(); + setState(() => reviewItems.addAll(data)); + } + }, + onLoad: () async { + param.page += 1; + List? data = await toGetPageData(controller, param); + if (data != null && data.isNotEmpty) { + setState(() => reviewItems.addAll(data)); + return; + } + param.page -= 1; + }, + ); + }).toList() + ], + ), + ), + ], + ); + } +} + +// 赛选条件抽屉 +class ConditionsDrawer extends StatefulWidget { + final AdditionalConditionsForReview condition; + final CollationCallback callback; + const ConditionsDrawer({required this.callback, required this.condition, Key? key}) : super(key: key); + @override + State createState() => _ConditionsDrawerState(); +} + +class _ConditionsDrawerState extends State { + late StreamSubscription keyboardSubscription; + late TextEditingController _inputController; + late FocusNode _inputFocusNode; + + int collationIndex = -1; // 排序方式选中下标 + + @override + void initState() { + super.initState(); + AdditionalConditionsForReview condition = widget.condition; + + CollationItem? orderItem = condition.orderItem; + if (orderItem != null) { + toUpState( + setState, () => collationIndex = AdditionalConditionsForReview.collationArr.indexOf(orderItem), mounted); + } + + _inputController = TextEditingController(text: condition.questionId); + _inputFocusNode = FocusNode()..addListener(_theSetState); + + // 软键盘监听 + keyboardSubscription = KeyboardVisibilityController().onChange.listen((bool visible) { + debugPrint('Keyboard visibility update. Is visible: $visible'); + if (!visible) _inputFocusNode.unfocus(); + }); + } + + @override + void dispose() { + super.dispose(); + keyboardSubscription.cancel(); + _inputController.dispose(); + _inputFocusNode + ..removeListener(_theSetState) + ..dispose(); + } + + void _theSetState({VoidCallback? call}) { + toUpState(setState, () { + if (call != null) call(); + }, mounted); + } + + // 设置排序规则 + void choiceOrderField(CollationItem item, int index) { + if (_inputFocusNode.hasFocus) { + _inputFocusNode.unfocus(); + } + _theSetState(call: () => collationIndex = index); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 270.w, + color: Colors.white, + child: Column( + children: [ + Expanded( + child: Container( + width: double.infinity, + height: double.infinity, + padding: EdgeInsets.only( + left: 20.w, + right: 30.h, + bottom: 30.h, + top: MediaQuery.of(context).padding.top + 20.h, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + getPaperIDInput(), + getCollation(), + ], + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 10.w, + vertical: 20.h, + ), + child: getBtn(), + ), + ], + ), + ); + } + + // 确认、重置按钮 + Widget getBtn() { + return Row( + children: [ + Expanded( + child: MaterialButton( + height: 40.h, + splashColor: Colors.pink, + // color: Colors.blueGrey, + color: Colors.grey[100], + textColor: const Color.fromRGBO(102, 102, 102, 1), + minWidth: MediaQuery.of(context).size.width / 2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: const Text( + '重置', + ), + onPressed: () { + if (_inputFocusNode.hasFocus) { + _inputFocusNode.unfocus(); + } + widget.callback(reset: false).then((_) => Navigator.pop(context)); + }, + ), + ), + SizedBox(width: 15.w), + Expanded( + flex: 2, + child: MaterialButton( + height: 40.h, + splashColor: Colors.pink, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: const Text( + '确定', + ), + onPressed: () { + if (_inputFocusNode.hasFocus) { + _inputFocusNode.unfocus(); + } + widget + .callback( + reset: false, + item: collationIndex > -1 ? AdditionalConditionsForReview.collationArr[collationIndex] : null, + testId: _inputController.text) + .then((_) => Navigator.pop(context)); + }), + ), + ], + ); + } + + // 试题ID输入框 + Widget getPaperIDInput() { + return Container( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText('试题ID', size: 18.sp), + SizedBox(height: 10.h), + TextField( + controller: _inputController, + focusNode: _inputFocusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], //数字,只能是整数 + style: TextStyle(color: Colors.black45, fontSize: 14.sp), + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.grey[300]!), + ), + hintText: '请输入答卷ID', + hintStyle: TextStyle(color: Colors.grey[300], fontSize: 14.sp), + suffixIcon: !_inputFocusNode.hasFocus + ? null + : IconButton( + icon: Icon(Icons.highlight_off, size: 14.sp, color: Colors.grey), + onPressed: () { + _inputController.clear(); + }, + ), + ), + ) + ], + ), + ); + } + + // 排序规则 + Widget getCollation() { + return Container( + margin: EdgeInsets.only(top: 50.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + quickText('试题ID', size: 18.sp), + SizedBox(height: 20.h), + ...AdditionalConditionsForReview.collationArr.asMap().keys.map((iIndex) { + CollationItem item = AdditionalConditionsForReview.collationArr[iIndex]; + bool flag = iIndex == collationIndex; + + return Container( + margin: EdgeInsets.symmetric(vertical: 6.h), + width: double.infinity, + height: 34.h, + alignment: Alignment.center, + decoration: BoxDecoration( + color: flag ? Colors.blue[50] : Colors.grey[100], + borderRadius: BorderRadius.all(Radius.circular(4.sp)), + ), + child: Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => choiceOrderField(item, iIndex), + child: quickText( + item.title, + size: 14.sp, + color: flag ? Theme.of(context).primaryColor : Colors.black54, + ), + ), + ) + ], + ), + ); + + // return Container( + // margin: EdgeInsets.symmetric(vertical: 6.h), + // width: double.infinity, + // height: 34.h, + // alignment: Alignment.center, + // decoration: BoxDecoration( + // color: flag ? Colors.blue[50] : Colors.grey[200], + // borderRadius: BorderRadius.all(Radius.circular(4.sp)) + // ), + // child: quickText(item.title, + // size: 15.sp, color: flag ? Theme.of(context).primaryColor : Colors.black54), + // ); + }).toList() + ], + ), + ); + } +} + +// 底层头部区域 +class ReviewHeadBox extends StatelessWidget { + const ReviewHeadBox({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: double.infinity, + child: Column( + children: [ + Container( + height: 200.h, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/personal_bgi.png'), + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Container( + color: const Color.fromRGBO(248, 248, 248, 1), + )) + ], + ), + ); + } +} diff --git a/marking_app/lib/pages/mine/index.dart b/marking_app/lib/pages/mine/index.dart new file mode 100644 index 0000000..6023e16 --- /dev/null +++ b/marking_app/lib/pages/mine/index.dart @@ -0,0 +1,343 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-05 16:42:48 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-29 11:56:18 + * @FilePath: \marking_app\lib\pages\mine\index.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:fluro/fluro.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/enum/subject.dart'; +import 'package:marking_app/provider/do_marking_provider.dart'; +import 'package:marking_app/provider/user_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/fluro/index.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; + +class TheMine extends StatefulHookConsumerWidget { + const TheMine({Key? key}) : super(key: key); + + @override + _TheMineState createState() => _TheMineState(); +} + +class _TheMineState extends ConsumerState with CommonMixin { + @override + Widget build(BuildContext context) { + final userState = ref.watch(userProvider); + final tokenState = ref.watch(userTokenProvider); + + final personalInfoTitleStly = TextStyle( + color: const Color.fromRGBO(80, 87, 103, 1), + fontSize: 16.sp, + ); + + final personalInfoValStly = TextStyle( + color: const Color.fromRGBO(148, 163, 182, 1), + fontSize: 16.sp, + ); + + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.dark, + ), + child: Stack( + children: [ + SizedBox( + height: double.infinity, + child: Column( + children: [ + Container( + height: 240.h, + width: double.infinity, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/personal_bgi.png'), + fit: BoxFit.cover, + ), + ), + ), + Expanded( + child: Container( + color: const Color.fromRGBO(248, 248, 248, 1), + )) + ], + ), + ), + SafeArea( + child: Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + Stack( + alignment: const FractionalOffset(0.04, 0.1), + children: [ + Container( + height: 200.h, + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(80.w), + child: Container( + width: 80.w, + height: 80.w, + padding: EdgeInsets.all(4.w), + decoration: BoxDecoration( + border: Border.all( + width: 1.w, + color: Colors.white, + ), + borderRadius: BorderRadius.all( + Radius.circular(40.w), + ), + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(4.w, 6.h), //阴影y轴偏移量 + blurRadius: 8, //阴影模糊程度 + spreadRadius: 0.2, //阴影扩散程度 + ) + ], + ), + child: CachedNetworkImage( + fit: BoxFit.cover, + imageUrl: userState.avatar, + placeholder: (context, url) => Image.asset('assets/images/default_user_dead.png'), + errorWidget: (context, url, error) => + Image.asset('assets/images/default_user_dead.png'), + ), + ), + ), + InkWell( + onTap: () { + if (tokenState == '' || userState.id == '') { + toLoginPage(context); + } + }, + child: Container( + margin: EdgeInsets.only(top: 16.h), + child: Text( + tokenState != '' ? userState.userName : '请前往登录', + style: TextStyle(fontSize: 16.sp, color: Colors.white), + ), + ), + ), + ], + ), + ), + InkWell( + onTap: () => Navigator.pop(context), + child: Icon(Icons.arrow_back_ios_new_rounded, color: Colors.white, size: 24.sp), + ), + ], + ), + SizedBox(height: 14.h), + Container( + margin: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w), + padding: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w), + height: 310.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(6.w)), + color: Colors.white, + boxShadow: const [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset.zero, //阴影y轴偏移量 + blurRadius: 20, //阴影模糊程度 + spreadRadius: 10, //阴影扩散程度 + ) + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('账号', style: personalInfoTitleStly), + Text(userState.loginName, style: personalInfoValStly) + ], + ), + Container( + height: 1.w, + color: const Color.fromRGBO(240, 243, 255, 1), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('所在学校', style: personalInfoTitleStly), + Text(userState.schoolName, style: personalInfoValStly) + ], + ), + Container( + height: 1.w, + color: const Color.fromRGBO(240, 243, 255, 1), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('担任职位', style: personalInfoTitleStly), + SizedBox(width: 20.w), + Expanded( + child: Text( + userState.positionNames.map((e) => e).toList().join(','), + maxLines: 2, + textAlign: TextAlign.right, + overflow: TextOverflow.ellipsis, + style: personalInfoValStly, + ), + ) + ], + ), + Container(height: 1.w, color: const Color.fromRGBO(240, 243, 255, 1)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('所授科目', style: personalInfoTitleStly), + Expanded( + child: Text( + userState.subjectIds.map((e) => getSubjectEnumName(e)).toList().join(','), + maxLines: 2, + textAlign: TextAlign.right, + overflow: TextOverflow.ellipsis, + style: personalInfoValStly, + ), + ) + ], + ), + Container(height: 1.w, color: const Color.fromRGBO(240, 243, 255, 1)), + Padding( + padding: EdgeInsets.only(top: 10.h), + child: InkWell( + onTap: () { + RouterManager.router.navigateTo( + context, + RouterManager.ohterMainPagePath, + transition: TransitionType.custom, + transitionBuilder: (context, animation, secondaryAnimation, child) { + return SkewTransition( + turns: Tween( + begin: -0.06, + end: 0.0, + ).animate(animation), + child: child); + }, + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('其他', style: personalInfoTitleStly), + Icon( + Icons.arrow_forward_ios, + color: const Color.fromRGBO(80, 87, 103, 1), + size: 16.sp, + ) + ], + ), + ), + ) + ], + ), + ), + Expanded( + child: Column( + children: [ + Expanded(child: SizedBox()), + Container( + margin: EdgeInsets.only(bottom: 40.h), + alignment: Alignment.bottomCenter, + child: InkWell( + child: Container( + padding: EdgeInsets.symmetric(vertical: 14.h), + margin: EdgeInsets.only(right: 16.w, left: 16.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(6.w), + ), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(46, 91, 255, 0.2), + offset: Offset(2.w, 2.h), //阴影y轴偏移量 + blurRadius: 14, //阴影模糊程度 + spreadRadius: 0.5, //阴影扩散程度 + ) + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.exit_to_app_outlined, + size: 16.sp, + color: const Color.fromRGBO(148, 163, 182, 1), + ), + Container( + width: 6.w, + ), + Text( + '退出登录', + style: TextStyle(color: const Color.fromRGBO(148, 163, 182, 1), fontSize: 16.sp), + ), + ], + ), + ), + onTap: () { + _showAlertDialog(context, ref); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + // 确认对话框 + _showAlertDialog(context1, WidgetRef ref) async { + await showDialog( + // 表示点击灰色背景的时候是否消失弹出框 + barrierDismissible: false, + context: context1, + builder: (context) { + return AlertDialog(title: quickText("提示信息"), content: quickText("您确定要退出登录吗?"), actions: [ + TextButton( + child: quickText("取消"), + onPressed: () { + Navigator.pop(context, 'Cancle'); + }, + ), + TextButton( + child: quickText("确定"), + onPressed: () { + ref.read(markingKeyboardProvider.notifier).clean(); + ref.read(markingSubtopicSwitchingProvider.notifier).clean(); + ref.read(userTokenProvider.notifier).clean(); + ref.read(userProvider.notifier).clean(); + Navigator.pop(context, "Ok"); + toLoginPage(context); + }) + ]); + }); + } +} diff --git a/marking_app/lib/pages/mine/other_pages/index.dart b/marking_app/lib/pages/mine/other_pages/index.dart new file mode 100644 index 0000000..a27f13f --- /dev/null +++ b/marking_app/lib/pages/mine/other_pages/index.dart @@ -0,0 +1,170 @@ +import 'package:fluro/fluro.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/const_text.dart'; +import 'package:marking_app/utils/fluro/index.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:package_info/package_info.dart'; +// 其他页面 + +class OhterPage extends StatefulWidget { + const OhterPage({super.key}); + + @override + State createState() => _OhterPageState(); +} + +class _OhterPageState extends State { + String localVersion = ''; + + @override + void initState() { + super.initState(); + getPageInfo(); + } + + // 获取当前版本号 + getPageInfo() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + toUpState(setState, () => localVersion = packageInfo.version, mounted); + } + + @override + Widget build(BuildContext context) { + final personalInfoTitleStly = TextStyle( + color: const Color.fromRGBO(80, 87, 103, 1), + fontSize: 16.sp, + ); + final personalInfoValStly = TextStyle( + color: const Color.fromRGBO(148, 163, 182, 1), + fontSize: 16.sp, + ); + + return Scaffold( + backgroundColor: const Color.fromRGBO(248, 248, 248, 1), + appBar: AppBar( + backgroundColor: Theme.of(context).primaryColor, + title: quickText('其他', color: Colors.white), + ), + body: Stack( + alignment: const FractionalOffset(0.5, 0.98), + children: [ + ListView( + children: [ + Container( + margin: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w), + padding: EdgeInsets.symmetric(vertical: 22.h, horizontal: 16.w), + height: 130.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(6.w)), + color: Colors.white, + boxShadow: const [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.1), + offset: Offset.zero, //阴影y轴偏移量 + blurRadius: 20, //阴影模糊程度 + spreadRadius: 10, //阴影扩散程度 + ) + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + RouterManager.router.navigateTo( + context, + transition: TransitionType.fadeIn, + '${RouterManager.agreementPath}?type=${AGREEMENT_KEY.PRIVACY_GREEMENT.name}', + ); + }, + child: Container( + padding: EdgeInsets.only(bottom: 4.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('用户隐私协议', style: personalInfoTitleStly), + Icon( + Icons.arrow_forward_ios, + color: const Color.fromRGBO(80, 87, 103, 1), + size: 16.sp, + ) + ], + ), + ), + ), + Container( + height: 1.w, + color: const Color.fromRGBO(240, 243, 255, 1), + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('APP版本', style: personalInfoTitleStly), quickText(localVersion)], + ) + ], + ), + ), + // InkWell( + // onTap: () { + // RouterManager.router.navigateTo( + // context, + // transition: TransitionType.fadeIn, + // '${RouterManager.agreementPath}?type=${AGREEMENT_KEY.PRIVACY_GREEMENT.name}', + // ); + // }, + // child: Container( + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide( + // color: Color.fromARGB(255, 171, 172, 175), + // width: 1.w, + // ), + // ), + // ), + // padding: EdgeInsets.symmetric(vertical: 12.h), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text('用户隐私协议', style: personalInfoTitleStly), + // Icon( + // Icons.arrow_forward_ios, + // color: const Color.fromRGBO(80, 87, 103, 1), + // size: 16.sp, + // ) + // ], + // ), + // ), + // ), + // Container( + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide( + // color: Color.fromARGB(255, 171, 172, 175), + // width: 1.w, + // ), + // ), + // ), + // margin: EdgeInsets.only(top: 20.h), + // padding: EdgeInsets.symmetric(vertical: 14.h), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text('APP版本', style: personalInfoTitleStly), + // quickText(localVersion) + // ], + // ), + // ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [Text('APP备案号: ', style: personalInfoTitleStly), quickText('渝ICP备17007225号-3A', size: 14.sp)], + ), + ], + )); + } +} diff --git a/marking_app/lib/pages/other/agreement_page.dart b/marking_app/lib/pages/other/agreement_page.dart new file mode 100644 index 0000000..23c6eb4 --- /dev/null +++ b/marking_app/lib/pages/other/agreement_page.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; +import 'package:marking_app/utils/const_text.dart'; +import 'package:marking_app/utils/my_text.dart'; + +// 协议富文本 +class AgreementPage extends StatelessWidget { + final AGREEMENT_KEY type; + const AgreementPage({required this.type, super.key}); + + @override + Widget build(BuildContext context) { + AgreementClass agreement = AGREEMENT_MAP[type]!; + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).primaryColor, + title: quickText(agreement.title,color: Colors.white), + ), + body: ListView( + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h), + children: [ + HtmlWidget(agreement.richText,textStyle:const TextStyle(color: Colors.black)), + ], + ), + ); + } +} diff --git a/marking_app/lib/pages/reports/bar_chart_histogram.dart b/marking_app/lib/pages/reports/bar_chart_histogram.dart new file mode 100644 index 0000000..e2dd1d9 --- /dev/null +++ b/marking_app/lib/pages/reports/bar_chart_histogram.dart @@ -0,0 +1,206 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/model/report/report_histogram_model.dart'; +import 'package:marking_app/utils/index.dart'; + +// ignore: must_be_immutable +class _BarChart extends StatelessWidget { + List tagComparisonList; + _BarChart(this.tagComparisonList); + + @override + Widget build(BuildContext context) { + return BarChart( + BarChartData( + barTouchData: barTouchData, + titlesData: titlesData, + + borderData: borderData, + barGroups: barGroups, + gridData: FlGridData( + show: true, + drawHorizontalLine: true, // 绘制横线 + drawVerticalLine: false, + // horizontalInterval: 10, // 设置横线间隔 + getDrawingHorizontalLine: (value) { + return FlLine( + color: Color.fromRGBO(229, 234, 255, 1), // 设置横线颜色 + strokeWidth: 0.8, // 设置横线宽度 + ); + }, + ), + alignment: BarChartAlignment.spaceAround, + // maxY: 20, + ), + ); + } + + BarTouchData get barTouchData => BarTouchData( + enabled: false, + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: Colors.transparent, + tooltipPadding: EdgeInsets.zero, + tooltipMargin: 6.h, + getTooltipItem: (BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex) { + return BarTooltipItem( + rod.toY.round().toString(), + TextStyle(color: const Color.fromRGBO(46, 91, 255, 1), fontWeight: FontWeight.w500, fontSize: 12.sp), + ); + }, + ), + ); + + Widget getTitles(double value, TitleMeta meta) { + final style = TextStyle( + color: const Color.fromRGBO(46, 91, 255, 1), + fontSize: 12.sp, + ); + ReportHistogramModel _model = tagComparisonList[value.toInt()]; + + String name = _model.name; + List newNames = []; + bool multiLine = name.length > 5; + if (multiLine) { + List nameList = name.split(''); + int nameListLength = nameList.length; + int fiveRemainder = nameListLength % 5; // 5数取余 + bool hasFiveRemainder = fiveRemainder > 0; // 是否有余数 + + int step = 5; + int i = 0; + while (i < nameListLength) { + int oldI = i; + i += hasFiveRemainder && i + step > nameListLength ? fiveRemainder : step; + newNames.add(nameList.sublist(oldI, i).join('') + '\n'); + } + } + + toPrint(val: '数据:' + newNames.toList().join('')); + return SizedBox( + height: 300.h, + child: SideTitleWidget( + axisSide: meta.axisSide, + space: 6, + child: multiLine + ? + // Text(newNames.toList().join(''), style: style, maxLines: null, textAlign: TextAlign.center) + RichText( + text: TextSpan( + children: newNames.map((e) { + toPrint(val: e); + return TextSpan(text: e, style: style); + }).toList()), + ) + : Text(name, style: style, maxLines: null, textAlign: TextAlign.center), + ), + ); + } + + Widget leftTitles(double value, TitleMeta meta) { + if (value == meta.max) { + return Container(); + } + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + meta.formattedValue, + style: TextStyle( + fontSize: 12.sp, + color: Color.fromRGBO(148, 163, 182, 1), + fontWeight: FontWeight.w400, + ), + ), + ); + } + + FlTitlesData get titlesData => FlTitlesData( + show: true, + bottomTitles: AxisTitles( + axisNameSize: 200.w, + drawBehindEverything: false, + sideTitles: SideTitles( + showTitles: true, + // margin: 16, // 设置底部标题的边距 + reservedSize: 30, + getTitlesWidget: getTitles, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: leftTitles, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ); + + FlBorderData get borderData => FlBorderData( + show: false, + ); + + LinearGradient get _barsGradient => LinearGradient( + colors: [ + Color.fromRGBO(46, 91, 255, 0.1), + Color.fromRGBO(46, 91, 255, 1), + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + ); + + List get barGroups => tagComparisonList.asMap().keys.map((i) { + ReportHistogramModel _model = tagComparisonList[i]; + return BarChartGroupData( + x: i, + barRods: [ + BarChartRodData( + toY: _model.val, + gradient: _barsGradient, + ) + ], + showingTooltipIndicators: [0], + ); + }).toList(); +} + +// ignore: must_be_immutable +class BarChartSample3 extends StatefulWidget { + List tagComparisonList; + bool horizontal; + double aspectRatio; + double singleWidth; + double heightVal; + BarChartSample3(this.tagComparisonList, {this.singleWidth = 220, this.aspectRatio = 3, this.horizontal = false, this.heightVal = 200, super.key}); + + @override + State createState() => BarChartSample3State(); +} + +class BarChartSample3State extends State { + @override + Widget build(BuildContext context) { + if (!widget.horizontal) + return Container( + width: widget.tagComparisonList.length * widget.singleWidth.w, + height: widget.heightVal, + child: _BarChart(widget.tagComparisonList), + ); + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + width: widget.tagComparisonList.length * widget.singleWidth.w, + child: AspectRatio( + aspectRatio: widget.aspectRatio, + child: _BarChart(widget.tagComparisonList), + ), + ), + ); + } +} diff --git a/marking_app/lib/pages/reports/index.dart b/marking_app/lib/pages/reports/index.dart new file mode 100644 index 0000000..27cedee --- /dev/null +++ b/marking_app/lib/pages/reports/index.dart @@ -0,0 +1,679 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/enum/reportUserIdentity.dart'; +import 'package:marking_app/common/model/report/report_histogram_model.dart'; +import 'package:marking_app/common/model/report/report_home_model.dart'; +import 'package:marking_app/common/model/user/user_info.dart'; +import 'package:marking_app/common/model/user/user_info_report.dart'; +import 'package:marking_app/pages/reports/bar_chart_histogram.dart'; +import 'package:marking_app/pages/reports/services/report_home_services.dart'; +import 'package:marking_app/provider/user_provider.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; + +part 'index.g.dart'; + +class TheReport extends StatefulHookConsumerWidget { + const TheReport({Key? key}) : super(key: key); + + @override + _TheReportState createState() => _TheReportState(); +} + +class _TheReportState extends ConsumerState with CommonMixin, AutomaticKeepAliveClientMixin { + late RemoveListener _userReportListener; + + late UserInfo _user; + late UserInfoReport _userReport; + late Future _future; + int? theExamId; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + _future = initData(); + Future.delayed(Duration.zero, () { + _user = ref.read(userProvider); + _userReportListener = ref.read(userReportProvider.notifier).addListener( + (state) { + _userReport = state; + if (_userReport.normal) { + // _future = initData(); + toUpState(setState, () {}, mounted); + } + }, + ); + }); + super.initState(); + } + + @override + void dispose() { + _userReportListener(); + super.dispose(); + } + + /// 初始化数据 + Future initData({int? examId}) async { + try { + RestClientReport clientReport = await getClientReport(); + UserInfoReport _userReport = ref.read(userReportProvider); + if (!_userReport.normal) { + // 用户职位数据是否已经请求,在主页已经请求一次 为正常使用 备用 + bool theFlag = await ref.read(userReportProvider.notifier).initUserReport(); + if (!theFlag) { + return ToastUtils.showError('请求失败请重试'); + } + } + BaseStructureResultReport res = await clientReport.getReportHomeData(examId); + if (res.success) { + return res.data; + } else { + ToastUtils.showError(res.message ?? '请求错误,请重试'); + } + } catch (e) { + if (e is CheckedFromJsonException) { + // JSON 解析失败,处理异常 + toPrint(val: 'JSON 解析失败: ${e.message}'); + toPrint(val: 'JSON 解析失败,出错字段: ${e.key}'); + } else { + // 其他异常,处理其他错误 + toPrint(val: '发生了其他错误: $e'); + } + return null; + } + return null; + } + + @override + Widget build(BuildContext context) { + super.build(context); //调用super.build(返回值始终返回null,应将其忽略) + + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + body: Stack( + children: [ + // 渐变背景色 + $ScaffoldBgc(), + + RefreshIndicator( + displacement: 50, + color: Theme.of(context).primaryColor, + backgroundColor: Colors.white, + child: MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _future, + (ReportHomeModel? datas) { + return _TheReportBody( + user: _user, + userReport: _userReport, + data: datas, + refreshReport: () { + _future = initData(); + toUpState(setState, () {}, mounted); + }, + switchCall: (int? examId, int positionIndex) { + theExamId = examId; + if (examId != null) { + ref.read(userReportProvider.notifier).setPositionIndex(positionIndex); + + _future = initData(examId: examId); + toUpState(setState, () {}, mounted); + } + }, + ); + }), + onRefresh: () async { + _future = initData(examId: theExamId); + await _future; + await Future.delayed(Duration(seconds: 1), () => toUpState(setState, () => {}, mounted)); + }, + ), + ], + ), + ), + ); + } +} + +// 报告body +class _TheReportBody extends HookWidget { + final UserInfo user; + final UserInfoReport userReport; + final ReportHomeModel? data; + final Function(int? examId, int positionIndex) switchCall; + final Function() refreshReport; + + _TheReportBody( + {required this.user, + required this.userReport, + required this.switchCall, + required this.refreshReport, + required this.data, + Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + ExamData? examData = data?.examData; + List? tagComparisonList = + examData?.tagComparisonList.map((e) => ReportHistogramModel(name: e.name, val: e.count)).toList(); + List? examSubjectDatas = data?.examSubjectDatas; + + List _positions = userReport.positions; + + SwitchIdentityExamsController potnAndExamCotl = SwitchIdentityExamsController.use( + position: userReport.positionIndex, exam: -1, initExamId: examData?.examId, positons: userReport.positions); + Positions? theCurrentPosition = _positions.isEmpty ? null : _positions[potnAndExamCotl.positionIndex.value]; + + if (examData == null || theCurrentPosition == null) { + return Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: MediaQuery.of(context).padding.top), + child: Column( + children: [ + $ReportTitle( + titleName: '', + switchCall: () => easyThrottle('reportHomeExamSwitchCall', () async { + // switchCall(examId, potnAndExamCotl.positionIndex.value); + List positions = userReport.positions; + if (positions.isEmpty) { + return ToastUtils.showInfo('没有数据,请刷新'); + } + int? examId = await potnAndExamCotl.openSwitch( + context: context, + currentExamId: examData?.examId ?? -1, + currentPositionIndex: userReport.positionIndex); + switchCall(examId, potnAndExamCotl.positionIndex.value); + }), + ), + Expanded( + child: Center( + child: Container( + child: CupertinoButton( + padding: const EdgeInsets.only(left: 10, right: 10), + color: Theme.of(context).primaryColor, + //按钮被按下时的不透明度程度,0~1之间 + pressedOpacity: 1, + //这也可以自己定义Container然后固定大小进行设置等等 + child: quickText('没有数据,请重试', color: Colors.white, size: 14.sp), + onPressed: () => easyThrottle('reportHomeNoDataAgain', () => refreshReport()), + ), + ), + ), + ) + ], + ), + ); + } + + // 内容区域 + return Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: MediaQuery.of(context).padding.top), + child: Column( + children: [ + SizedBox(height: 6.h), + // 考试报告TITLE + $ReportTitle( + titleName: examData.examName, + switchCall: () => easyThrottle('reportHomeExamSwitchCall', () async { + List positions = userReport.positions; + if (positions.isEmpty) { + return ToastUtils.showInfo('没有数据,请刷新'); + } + int? examId = await potnAndExamCotl.openSwitch( + context: context, currentExamId: examData.examId, currentPositionIndex: userReport.positionIndex); + + switchCall(examId, potnAndExamCotl.positionIndex.value); + }), + ), + Expanded( + child: ScrollConfiguration( + behavior: EUMNoScrollBehavior(), + child: ListView( + padding: EdgeInsets.only(top: 20.h, bottom: 20.h), + children: [ + // 顶部平均分模块 + $TopAverageContainer( + averageVal: examData.averageScore, totalVal: examData.totalScore, context: context), + + // 详细报告入口区域 + $DetailedReportEntry( + context: context, + examId: examData.examId, + subjectId: theCurrentPosition.subjectId, + classId: theCurrentPosition.classId, + grade: examData.grade, + subject: examData.subjects, + examDate: examData.startTime, + identity: theCurrentPosition.identityEnum, + subjectName: theCurrentPosition.subjectName ?? '', + ), + + // 分段情况 + $GraphicDataArea( + title: '分段概况', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: Padding( + padding: EdgeInsets.only(top: 60.h, bottom: 20.h), + child: BarChartSample3(tagComparisonList!), + )), + + // 单科概况 + $GraphicDataArea( + title: '单科概况', + titleImg: 'assets/images/report_home_icon_subject.png', + graphicContainer: $TheProgressChart(context, examSubjectDatas!)), + ], + ), + ), + ), + ], + ), + ); + } +} + +/// 底部渐变色 +@swidget +Widget $scaffoldBgc() { + return Container( + height: ScreenUtil().screenHeight, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color.fromRGBO(226, 232, 255, 1), Colors.white, Colors.white], + ), + ), + ); +} + +// 页面TITLE +@swidget +Widget $reportTitle({required String titleName, required switchCall}) { + return Stack( + alignment: const FractionalOffset(0.98, 0.5), + children: [ + Container( + padding: EdgeInsets.only(right: titleName.length > 14 ? 50 : 0).w, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Center( + child: quickText( + titleName, + size: 16.sp, + color: Color.fromRGBO(45, 56, 76, 1), + fontWeight: FontWeight.w600, + maxLines: 2, + ), + ), + ), + ], + ), + ), + InkWell( + onTap: () => easyThrottle('switchRoleExamSheetCall', () => switchCall()), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.swap_vert_rounded, color: Color.fromRGBO(80, 87, 103, 0.8), size: 16.sp), + quickText('切换', color: Color.fromRGBO(80, 87, 103, 1), size: 12.sp), + ], + ), + ) + ], + ); +} + +/// 顶部平均分模块 +@swidget +Widget $topAverageContainer({required double averageVal, required double totalVal, required BuildContext context}) { + return Stack( + alignment: const FractionalOffset(0.99, 0), + children: [ + Container( + padding: EdgeInsets.only(left: 12.w), + child: Row( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(3).r, + child: Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: 4, right: 4, top: 4, bottom: 7).r, + child: quickText('平均分', size: 6.sp, color: Colors.white), + decoration: ShapeDecoration( + shape: BeveledRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(2).r, + topRight: Radius.circular(2).r, + bottomLeft: Radius.circular(15).r, + bottomRight: Radius.circular(15).r, + ), + ), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Theme.of(context).primaryColor, Theme.of(context).primaryColor.withOpacity(0.3)], + ), + ), + ), + ), + SizedBox(height: 1.h), + quickText(averageVal.toString(), + size: 38.sp, color: Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.bold), + SizedBox(height: 1.h), + quickText('满分' + totalVal.toString(), size: 12.sp, color: Color.fromRGBO(80, 87, 103, 0.7)), + ], + ), + ], + ), + ), + Image.asset("assets/images/report_home_top_img.png", width: 100.r, height: 100.r) + ], + ); +} + +/// 详细报告入口模块 +@swidget +Widget $detailedReportEntry({ + required String grade, + required String subject, + required String examDate, + required int examId, + required int? classId, + required int? subjectId, + required ReportUserIdentity identity, + required String subjectName, + required BuildContext context, +}) { + return Container( + width: double.infinity, + margin: EdgeInsets.only(top: 6.h, bottom: 28.h), + // constraints: BoxConstraints( + // minHeight: 140.h, + // minWidth: double.infinity, + // maxHeight: 160.h, + // maxWidth: double.infinity, + // ), + padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 16.h), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/detailed_report_entry_bgm.png'), + fit: BoxFit.fill, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText('年级', size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1)), + SizedBox(height: 5.h), + quickText(grade, size: 16.sp, fontWeight: FontWeight.bold, color: Color.fromRGBO(45, 56, 76, 1)), + ], + ), + SizedBox(width: 23.w), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText('学科', size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1)), + SizedBox(height: 5.h), + quickText(subject, + size: 16.sp, fontWeight: FontWeight.bold, color: Color.fromRGBO(45, 56, 76, 1), maxLines: 2), + ], + ) + ], + ), + ), + SizedBox(width: 23.w), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText('考试日期', size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1)), + SizedBox(height: 5.h), + quickText(examDate, size: 16.sp, fontWeight: FontWeight.bold, color: Color.fromRGBO(45, 56, 76, 1)), + ], + ), + ], + ), + SizedBox(height: 30.h), + InkWell( + onTap: () => easyThrottle('reportToUserPositionDetails', () { + String rootPath; + switch (identity) { + case ReportUserIdentity.TEACHER: // 单科老师 + rootPath = RouterManager.reportSubjectTeacherPath + + '?examId=$examId&classId=$classId&subject=$subjectId&subjectName=${Uri.encodeComponent(subjectName)}'; + break; + case ReportUserIdentity.CLASS: // 班主任 + rootPath = RouterManager.reportClassTeacherPath + '?examId=$examId'; + break; + case ReportUserIdentity.GRADE: // 年级 + case ReportUserIdentity.SCHOOL_LEVEL: // 校级 + case ReportUserIdentity.STATE_EDUCATION_COMMISSION: // 国家教育委员会 + default: + rootPath = ''; + return AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "当前职位详情还未开放,请稍后", + color: Theme.of(context).primaryColor, + ).show(context); + } + RouterManager.router.navigateTo(context, rootPath, transition: getTransition()); + }), + child: Container( + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 25.w), + padding: EdgeInsets.symmetric(vertical: 9.h), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(80).r, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color.fromRGBO(54, 86, 255, 1), Color.fromRGBO(93, 128, 255, 1)], + ), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.4), + offset: Offset(2.0, 2.0), //阴影xy轴偏移量 + blurRadius: 15.0, //阴影模糊程度 + spreadRadius: 1.0, //阴影扩散程度 + ) + ], + ), + child: quickText('查看详细报告', color: Colors.white, size: 14.sp, fontWeight: FontWeight.w400), + ), + ), + ], + ), + ); +} + +/// 图形数据模块 +@swidget +Widget $graphicDataArea({required String title, required String titleImg, required Widget graphicContainer}) { + return Container( + margin: EdgeInsets.only(bottom: 28.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset(titleImg, width: 22.w, height: 22.h), + SizedBox(width: 2.w), + quickText(title, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1)), + ], + ), + SizedBox(height: 6.h), + Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: graphicContainer, + ), + ], + ), + ); +} + +/// 进度图 +@swidget +Widget $theProgressChart(context, List datas) { + bool isMultiple = datas.length > 1; // 是不是多个数据 + + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 19.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6).r, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color.fromRGBO(249, 250, 255, 1), Colors.white], + ), + ), + child: Column( + children: [ + /// 平均分 + ...datas.map((e) { + return $theProgressWidget( + context: context, + isMultiple: isMultiple, + linearGradient: LinearGradient(colors: [Colors.white, Theme.of(context).primaryColor]), + subjectName: e.subjectName + '平均分', + suffixTitle: e.totalScoreStr + '分', + contentVal: e.averageScoreStr + '分', + percent: e.averagePercent, + contentColor: Theme.of(context).primaryColor, + ); + }), + + SizedBox(height: 20.h), + + /// 通过率 + ...datas.map((e) { + return $theProgressWidget( + context: context, + isMultiple: isMultiple, + linearGradient: LinearGradient(colors: [Colors.white, Color.fromRGBO(0, 131, 253, 1)]), + subjectName: e.subjectName + '及格率', + suffixTitle: '100%', + contentVal: e.passRateStr + '%', + percent: e.passPercent, + contentColor: Color.fromRGBO(0, 131, 253, 1), + ); + }) + ], + ), + ); +} + +/// 进度条图 +@swidget +Widget $theProgressWidget({ + required bool isMultiple, + required BuildContext context, + required LinearGradient linearGradient, + required String subjectName, + required String suffixTitle, + required String contentVal, + required Color contentColor, + required double percent, +}) { + return Padding( + padding: EdgeInsets.only(top: isMultiple ? 4.h : 0), + child: Stack( + alignment: const FractionalOffset(0.5, 0.45), + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(subjectName, size: 14.sp, color: Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.w400), + SizedBox(height: 6.h), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: LinearPercentIndicator( + padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), + // width: MediaQuery.of(context).size.width - 50, + animation: true, + lineHeight: 9.h, + animationDuration: 2500, + percent: percent, + linearGradient: linearGradient, + // center: Text( + // '${markingItem.completionRateStr}%', + // style: TextStyle(color: Colors.white, fontSize: 10.sp), + // ), + // linearStrokeCap: LinearStrokeCap.butt, + // progressColor: Theme.of(context).primaryColor, + backgroundColor: const Color.fromRGBO(219, 224, 243, 1).withOpacity(0.45), + barRadius: Radius.circular(10.h), + ), + ), + Container( + width: 42.w, + alignment: Alignment.centerRight, + // padding: EdgeInsets.only(left: 20.w),suffixTitle + child: quickText(suffixTitle, + size: 12.sp, color: Color.fromRGBO(148, 163, 182, 1), fontWeight: FontWeight.w400), + ), + ], + ), + ], + ), + quickText(contentVal, size: 12.sp, fontWeight: FontWeight.w700, color: contentColor), + ], + ), + ); +} diff --git a/marking_app/lib/pages/reports/report_class_teacher.dart b/marking_app/lib/pages/reports/report_class_teacher.dart new file mode 100644 index 0000000..e3b8b82 --- /dev/null +++ b/marking_app/lib/pages/reports/report_class_teacher.dart @@ -0,0 +1,564 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:graphic/graphic.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/report/report_for_class_teacher_model.dart'; +import 'package:marking_app/common/model/report/report_for_class_teacher_params.dart'; +import 'package:marking_app/common/model/report/report_histogram_model.dart'; +import 'package:marking_app/common/model/user/user_info_report.dart'; +import 'package:marking_app/pages/reports/bar_chart_histogram.dart'; +import 'package:marking_app/pages/reports/index.dart'; +import 'package:marking_app/pages/reports/report_subject_teacher.dart'; +import 'package:marking_app/provider/user_provider.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; + +part 'report_class_teacher.g.dart'; + +class ReportClassTeacher extends StatefulHookConsumerWidget { + final int examId; + const ReportClassTeacher({required this.examId, Key? key}) : super(key: key); + + @override + _ReportSubjectTeacherState createState() => _ReportSubjectTeacherState(); +} + +class _ReportSubjectTeacherState extends ConsumerState with CommonMixin { + late Positions position; + bool _showAppBar = true; + double _scrollOffset = 0.0; + double _appBarHeight = kToolbarHeight; + late Future _future; // + late ScrollController _scrollController; + final double fixedScrollDistance = 80.w; + bool showScrollBox =false; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController()..addListener(_scrollListener); + UserInfoReport _userReport = ref.read(userReportProvider); + position = _userReport.positions[_userReport.positionIndex]; + _future = _getData(); + } + + void _scrollListener() { + bool theScrollDistance = _scrollController.offset >= fixedScrollDistance; // 获取当前滚动的距离 + if(theScrollDistance!=showScrollBox){ + + toUpState(setState, () =>showScrollBox=theScrollDistance, mounted); + } + + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: Colors.transparent, + extendBodyBehindAppBar: true, + appBar: !_showAppBar + ? AppBar( + elevation: 0, + automaticallyImplyLeading: false, // 禁用默认的返回按钮 + shadowColor: Colors.transparent, + backgroundColor: Colors.transparent, + ) + : AppBar( + centerTitle: true, // 标题居中 + leading: IconButton( + icon: Icon(Icons.arrow_back_ios, color: Color.fromRGBO(80, 87, 103, 1), size: 20.sp), + onPressed: () => Navigator.of(context).pop(), + ), + title: quickText( + position.grade + (position.className ?? '') + '班主任老师报告', + size: 16.sp, + color: Color.fromRGBO(45, 56, 76, 1), + ), + elevation: 0, + shadowColor: Colors.transparent, + backgroundColor: Colors.transparent, + ), + body: Stack( + children: [ + // 渐变背景色 + $ScaffoldBgc(), + + MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _future, (ReportForClassTeacherModel? datas) { + if (datas == null) { + return Center( + child: Container( + child: CupertinoButton( + padding: const EdgeInsets.only(left: 10, right: 10), + color: Theme.of(context).primaryColor, + //按钮被按下时的不透明度程度,0~1之间 + pressedOpacity: 1, + //这也可以自己定义Container然后固定大小进行设置等等 + child: quickText('没有数据,请重试', color: Colors.white, size: 14.sp), + onPressed: () => toUpState(setState, () { + _future = _getData(); + }, mounted), + ), + ), + ); + } + return NotificationListener( + onNotification: (notification) { + if (notification is ScrollUpdateNotification) { + bool _oldShowAppBar = _showAppBar; + + _scrollOffset += notification.scrollDelta!; + if (_scrollOffset < 0.0) { + _scrollOffset = 0.0; + } else if (_scrollOffset > _appBarHeight) { + _scrollOffset = _appBarHeight; + } + if (_scrollOffset == 0.0) { + _showAppBar = true; + } else if (_scrollOffset == _appBarHeight) { + _showAppBar = false; + } + if (_oldShowAppBar != _showAppBar) setState(() {}); + } + return true; + }, + child: $reportBody(datas, position, context,scrollController:_scrollController,showScrollBox:showScrollBox), + ); + }), + ], + ), + ), + ); + } + + /// 获取数据 + Future _getData() async { + try { + RestClientReport clientReport = await getClientReport(); + BaseStructureResultReport res = await clientReport.getreportsForClassTeacher(ReportForClassTeacherParams(examId: widget.examId, compareAll: true)); + if (res.success) { + ReportForClassTeacherModel? model = res.data; + if (model != null) { + String schoolGradeName = position.schoolName + position.grade; // 学校年级名称 + model.averageModels = model.avgComparison.avgComparisonList.map((e) { + return ReportHistogramModel(name: e.name.replaceAll(schoolGradeName, ""), val: e.score); + }).toList(); + } + return model; + } + } catch (e) { + if (e is CheckedFromJsonException) { + // JSON 解析失败,处理异常 + toPrint(val: 'JSON 解析失败: ${e.message}'); + toPrint(val: 'JSON 解析失败,出错字段: ${e.key}'); + } else { + // 其他异常,处理其他错误 + toPrint(val: '发生了其他错误: $e'); + } + } + return null; + } +} + +/// 报告body +@swidget +Widget $reportBody(ReportForClassTeacherModel data, Positions position, BuildContext context,{ScrollController? scrollController,bool showScrollBox=false}) { + YearLevel yearLevel = data.yearLevel; + OverallLevel overallLevel = data.overallLevel; + + + List averageModels = data.averageModels; + List examTagDistributions = data.examTagDistributionList; + StudentInfo studentInfo = data.studentInfo; + List> studentInfoData = studentInfo.data; + List studentInfoHeads = studentInfo.heads; + SubjectRadarMap subjectRadarMap = data.subjectRadarMap; + List examSubjects = subjectRadarMap.examSubjects; + + List> studentInfoIterable = []; + List titleheads = studentInfoHeads.map((e) => ListTableTitle(title: e, titleKey: e, widthNum: e == '学生姓名' ? 70.w : 60.w)).toList(); + + for (var i = 0; i < studentInfoData.length; i++) { + Map theMapData = {}; + List item = studentInfoData[i]; + item.asMap().keys.forEach((elementIndex) { + dynamic val = item[elementIndex]; + double? newVal = double.tryParse(val); + theMapData[titleheads[elementIndex].titleKey] = newVal != null ? getDoubleRemoveZero(newVal) : val.toString(); + }); + studentInfoIterable.add(theMapData); + } + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + child: ScrollConfiguration( + behavior: EUMNoScrollBehavior(), + child: ListView( + children: [ + /// 基本信息 + $baseTitleAndBody(title: '基本信息', titleImg: 'assets/images/report_home_icon_burst.png', graphicContainer: $baseicInformationTable(data.baseInfo)), + + /// 年级水平 + $baseTitleAndBody( + title: '年级水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $levelBoxWidget(title1: '本校参考班级数', title2: '本校排名', val1: yearLevel.actor.toString(), val2: yearLevel.ranking.toString()), + ), + + /// 总体水平 + $baseTitleAndBody( + title: '总体水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $levelBoxWidget(title1: '总参考班级数', title2: '总排名', val1: overallLevel.actor.toString(), val2: overallLevel.ranking.toString()), + ), + + /// 单科雷达图 + $baseTitleAndBody( + title: '单科雷达图', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: + // TheRadarChart(radarSubjects: radarSubjects, rawDataSets: rawDataSets), + TheRadarChart(examSubjects: examSubjects), + ), + + /// 平均分对比 + $baseTitleAndBody( + title: '平均分对比', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: Padding( + padding: EdgeInsets.only(top: 40.h, bottom: 20.h), + child: Container( + height: 200.h, + child: BarChartSample3(averageModels, horizontal: false, aspectRatio: 1.6), + ), + ), + ), + + /// 分段分布 + $baseTitleAndBody( + title: '分段分布', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $listTable( + titles: [ListTableTitle(title: '大幅进步', titleKey: 'TagName'), ListTableTitle(title: '人数', titleKey: 'Number')], + datas: examTagDistributions.map((e) => e.toJson()), + ), + ), + + /// 试题分析 + $baseTitleAndBody( + title: '学生成绩详情', + subTitle: '(横向滑动查看更多)', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $listTableHorizontalScrolling(titles: titleheads, datas: studentInfoIterable,scrollController:scrollController,showScrollBox:showScrollBox,fixedIndex: 1), + ), + ], + ), + ), + ); +} + +/// 基本信息table +@swidget +Widget $baseicInformationTable(BaseInfo baseInfo) { + List columns = [ + {'考试名称': baseInfo.examName, '考试类型': baseInfo.examType}, + {'年级': baseInfo.grade, '学科': baseInfo.subjects.map((e) => e.subjectName).toString()}, + {'考试日期': baseInfo.examTime, '总分值': getDoubleRemoveZero(baseInfo.totalScore)}, + {'平均分': getDoubleRemoveZero(baseInfo.average), '最高分': getDoubleRemoveZero(baseInfo.maxScore)}, + {'优秀率': baseInfo.excellent, '及格率': baseInfo.qualified}, + ]; + + return Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Color.fromRGBO(232, 235, 255, 1), width: 1.3.r), + borderRadius: BorderRadius.circular(4).r, + ), + child: Column(mainAxisSize: MainAxisSize.min, children: columns.map((e) => $getTableRow(e)).toList()), + ), + ), + ); +} + +@swidget +Widget $getTableRow(Map datas) { + return IntrinsicHeight( + child: Container( + decoration: BoxDecoration(border: Border.all(color: Color.fromRGBO(229, 234, 255, 1), width: 0.4.r)), + child: Row( + children: datas.entries.map((entry) { + return Expanded( + child: Row( + children: [ + Expanded( + flex: 9, + child: Container( + height: double.infinity, + alignment: Alignment.centerLeft, + padding: EdgeInsets.all(9.5).r, + decoration: BoxDecoration( + color: Color.fromRGBO(245, 247, 255, 1), + ), + child: quickText(entry.key, color: Color.fromRGBO(80, 87, 103, 1), size: 12.sp, fontWeight: FontWeight.w400, maxLines: 2), + ), + ), + Expanded( + flex: 11, + child: Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.all(9.5).r, + child: quickText(entry.value, color: Color.fromRGBO(45, 56, 76, 1), size: 12.sp, fontWeight: FontWeight.w500, maxLines: 6), + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ); +} + +/// 基本信息table +@swidget +Widget $baseTitleAndBody({required String title, String? subTitle, required String titleImg, required Widget graphicContainer}) { + return Container( + width: double.infinity, + margin: EdgeInsets.only(bottom: 28.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [Image.asset(titleImg, width: 22.w, height: 22.h), SizedBox(width: 2.w), quickText(title, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1)), if (subTitle != null) quickText(subTitle, size: 12.sp, color: Color.fromRGBO(45, 56, 76, 1))], + ), + SizedBox(height: 6.h), + Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: graphicContainer, + ), + ], + ), + ); +} + +// 年级水平Widget +@swidget +Widget $levelBoxWidget({required String title1, required String title2, required String val1, required String val2}) { + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(left: 19.w, top: 16.h, bottom: 16.h), + decoration: BoxDecoration( + image: const DecorationImage(image: AssetImage('assets/images/report_level_bgm.png'), fit: BoxFit.fill), + borderRadius: BorderRadius.circular(6).r, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(title1, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 2), + SizedBox(height: 8.h), + quickText(val1, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.w700), + ], + ), + ), + ), + SizedBox(width: 22.w), + Expanded( + child: Container( + padding: EdgeInsets.only(left: 19.w, top: 16.h, bottom: 16.h), + decoration: BoxDecoration( + image: const DecorationImage(image: AssetImage('assets/images/report_level_bgm.png'), fit: BoxFit.fill), + borderRadius: BorderRadius.circular(6).r, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(title2, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 2), + SizedBox(height: 8.h), + quickText(val2, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.w700), + ], + ), + ), + ), + ], + ), + ); +} + +class TheRadarChart extends StatelessWidget { + // final List radarSubjects; + // final List rawDataSets; + // const TheRadarChart({required this.radarSubjects, required this.rawDataSets, Key? key}) : super(key: key); + final List examSubjects; + const TheRadarChart({required this.examSubjects, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + const adjustData = [ + {"type": "Email", "index": 'dafa0', "value": 120}, + {"type": "Email", "index": 'dafa1', "value": 132}, + {"type": "Email", "index": 'dafa2', "value": 101}, + {"type": "Email", "index": 'dafa3', "value": 134}, + {"type": "Email", "index": 'dafa4', "value": 90}, + {"type": "Email", "index": 'dafa5', "value": 230}, + {"type": "Email", "index": 'dafa6', "value": 210}, + ]; + + // @JsonKey(name: 'Subject') + // String subject; + + // @JsonKey(name: 'Score') + // double score; + + // @JsonKey(name: 'TotalScore') + // double totalScore; + return Container( + padding: EdgeInsets.symmetric(horizontal: 30.w), + child: Chart( + data: examSubjects.map((e) => e.toJson()).toList(), + variables: { + 'Subject': Variable( + accessor: (Map map) => map['Subject'].toString(), // +'('+map['TotalScore'].toString()+')' + ), + 'Score': Variable( + accessor: (Map map) => map['Score'] as num, + ), + }, + marks: [ + LineMark( + position: Varset('Subject') * Varset('Score'), + label: LabelEncode(encoder: (Map map) => Label(doubleToStringAsFixed(map['Score']))), + shape: ShapeEncode(value: BasicLineShape(loop: true)), + color: ColorEncode(variable: 'Subject', values: Defaults.colors10), + ) + ], + coord: PolarCoord(), + axes: [ + Defaults.circularAxis, + Defaults.radialAxis, + ], + selections: {}, + tooltip: TooltipGuide( + anchor: (_) => Offset.zero, + align: Alignment.bottomRight, + multiTuples: true, + variables: ['Subject', 'Score'], + ), + crosshair: CrosshairGuide(followPointer: [false, true]), + ), + // Chart( + // data: adjustData, + // variables: { + // 'index': Variable( + // accessor: (Map map) => map['index'].toString(), + // ), + // 'type': Variable( + // accessor: (Map map) => map['type'] as String, + // ), + // 'value': Variable( + // accessor: (Map map) => map['value'] as num, + // ), + // }, + // marks: [ + // LineMark( + // position: Varset('index') * Varset('value'), + // shape: ShapeEncode(value: BasicLineShape(loop: true)), + // color: ColorEncode(variable: 'type', values: Defaults.colors10), + // ) + // ], + // coord: PolarCoord(), + // axes: [ + // Defaults.circularAxis, + // Defaults.radialAxis, + // ], + // selections: { + // // 'touchMove': PointSelection( + // // on: {GestureType.scaleUpdate, GestureType.tapDown, GestureType.longPressMoveUpdate}, + // // dim: Dim.x, + // // variable: 'index', + // // ) + // }, + // tooltip: TooltipGuide( + // anchor: (_) => Offset.zero, + // align: Alignment.bottomRight, + // multiTuples: true, + // variables: ['type', 'value'], + // ), + // crosshair: CrosshairGuide(followPointer: [false, true]), + // ), + + // Echarts( + // option: ''' + // { + // radar: { + // shape: 'circle', + // indicator: ${radarSubjects.map((e) => jsonEncode(e.toJson())).toList()} + // }, + // series: [ + // { + // name: 'Budget vs spending', + // type: 'radar', + // itemStyle: { + // color: '#2E5BFF', // 设置每个点的颜色 + // }, + // lineStyle: { + // color: '#2E5BFF', // 设置线条的颜色 + // }, + // data: ${rawDataSets.map((e) => jsonEncode(e)).toList()}, + // label: { + // show: true, + // formatter: function(params) { + // return params.value; // 设置标签显示的内容为每个点的值 + // }, + // }, + // } + // ] + // } + // ''', + // ), + height: 460.h, + ); + } +} diff --git a/marking_app/lib/pages/reports/report_personal_subject.dart b/marking_app/lib/pages/reports/report_personal_subject.dart new file mode 100644 index 0000000..56f8a24 --- /dev/null +++ b/marking_app/lib/pages/reports/report_personal_subject.dart @@ -0,0 +1,329 @@ +import 'dart:convert'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/report/report_for_subject_student_model.dart'; +import 'package:marking_app/common/model/report/report_for_subject_student_params.dart'; +import 'package:marking_app/pages/reports/index.dart'; +import 'package:marking_app/pages/reports/report_subject_teacher.dart' as reportSubjectTeacher; +import 'package:marking_app/utils/image/gallery_example_item_model.dart'; +import 'package:marking_app/utils/image/image_utils.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; + +part 'report_personal_subject.g.dart'; + +/// 个人单科报告 +class ReportPersonalSubject extends StatefulWidget { + final int examId; + final int userId; + final int subject; + final String studentName; + final String subjectName; + const ReportPersonalSubject({required this.examId, required this.userId, required this.subject, required this.studentName, required this.subjectName, Key? key}) : super(key: key); + + @override + State createState() => _ReportPersonalSubjectState(); +} + +class _ReportPersonalSubjectState extends State with CommonMixin { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: Colors.transparent, + extendBodyBehindAppBar: true, + appBar: AppBar( + centerTitle: true, // 标题居中 + leading: IconButton( + icon: Icon(Icons.arrow_back_ios, color: Color.fromRGBO(80, 87, 103, 1), size: 20.sp), + onPressed: () => Navigator.of(context).pop(), + ), + title: quickText( + widget.studentName + '的' + widget.subjectName + '成绩报告', + size: 16.sp, + color: Color.fromRGBO(45, 56, 76, 1), + ), + elevation: 0, + shadowColor: Colors.transparent, + backgroundColor: Colors.transparent, + ), + body: Stack( + children: [ + // 渐变背景色 + $ScaffoldBgc(), + + MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _getData(), (ReportForSubjectStudentModel? datas) { + if (datas == null) { + return Center( + child: Container( + child: CupertinoButton( + padding: const EdgeInsets.only(left: 10, right: 10), + color: Theme.of(context).primaryColor, + //按钮被按下时的不透明度程度,0~1之间 + pressedOpacity: 1, + //这也可以自己定义Container然后固定大小进行设置等等 + child: quickText('没有数据,请重试', color: Colors.white, size: 14.sp), + onPressed: () => easyThrottle('getSubjectTeacher/\'sReportAgain', () => toUpState(setState, () {}, mounted)), + ), + ), + ); + } + + return $reportBody(datas, context); + }) + ], + ), + ), + ); + } + + /// 获取数据 + Future _getData() async { + try { + RestClientReport clientReport = await getClientReport(); + BaseStructureResultReport res = await clientReport.getReportSubjectStudent(ReportForSubjectStudentParams( + examId: widget.examId, + subject: widget.subject, + userId: widget.userId, + )); + if (res.success) { + return res.data; + } + } catch (e) { + if (e is CheckedFromJsonException) { + // JSON 解析失败,处理异常 + toPrint(val: 'JSON 解析失败: ${e.message}'); + toPrint(val: 'JSON 解析失败,出错字段: ${e.key}'); + } else { + // 其他异常,处理其他错误 + toPrint(val: '发生了其他错误: $e'); + } + } + return null; + } +} + +/// 报告body +@swidget +Widget $reportBody(ReportForSubjectStudentModel data, BuildContext context) { + YearLevel yearLevel = data.yearLevel; + ClassLevel classLevel = data.classLevel; + OverallLevel overallLevel = data.overallLevel; + + List tagAnalysis = data.tagAnalysis; + + List knowledgeAnalysisList = data.knowledgeAnalysisList; + List answerDetails = data.answerDetails; + + + // List examTagDistributions = data.examTagDistributionList; + // StudentInfo studentInfo = data.studentInfo; + // List> studentInfoData = studentInfo.data; + // List studentInfoHeads = studentInfo.heads; + + // List> studentInfoIterable = []; + // List titleheads = studentInfoHeads.map((e) => reportSubjectTeacher.ListTableTitle(title: e, titleKey: e, widthNum: e == '学生姓名' ? 70.w : 60.w)).toList(); + + // for (var i = 0; i < studentInfoData.length; i++) { + // Map theMapData = {}; + // List item = studentInfoData[i]; + // item.asMap().keys.forEach((elementIndex) { + // dynamic val = item[elementIndex]; + // double? newVal = double.tryParse(val); + // theMapData[titleheads[elementIndex].titleKey] = newVal != null ? getDoubleRemoveZero(newVal) : val.toString(); + // }); + // studentInfoIterable.add(theMapData); + // } + + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + child: ScrollConfiguration( + behavior: EUMNoScrollBehavior(), + child: ListView( + children: [ + /// 基本信息 + reportSubjectTeacher.$BaseTitleAndBody(title: '基本信息', titleImg: 'assets/images/report_home_icon_burst.png', graphicContainer: $BaseicInformationTable(data.baseInfo)), + + /// 班级水平 + reportSubjectTeacher.$BaseTitleAndBody( + title: '班级水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: reportSubjectTeacher.$LevelBoxWidget(title1: '排名/参考人数', title2: '班级平均分', val1: classLevel.ranking.toString()+'/'+classLevel.actor.toString(), val2: classLevel.average.toString()), + margin: EdgeInsets.only(bottom: 18.h), + ), + reportSubjectTeacher.$LevelBoxWidget(title1: '班级最高分', title2: '', val1: classLevel.max.toString(), val2: ''), + SizedBox(height: 30.h), + + /// 年级水平 + reportSubjectTeacher.$BaseTitleAndBody( + title: '年级水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: reportSubjectTeacher.$LevelBoxWidget(title1: '排名/参考人数', title2: '年级平均分', val1: yearLevel.ranking.toString()+'/'+yearLevel.actor.toString(), val2: yearLevel.average.toString()), + margin: EdgeInsets.only(bottom: 18.h), + ), + reportSubjectTeacher.$LevelBoxWidget(title1: '班级最高分', title2: '', val1: yearLevel.max.toString(), val2: ''), + SizedBox(height: 30.h), + + /// 总体水平 + reportSubjectTeacher.$BaseTitleAndBody( + title: '总体水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: reportSubjectTeacher.$LevelBoxWidget(title1: '排名/参考人数', title2: '整体平均分', val1: overallLevel.ranking.toString()+'/'+overallLevel.actor.toString(), val2: overallLevel.average.toString()), + margin: EdgeInsets.only(bottom: 18.h), + ), + reportSubjectTeacher.$LevelBoxWidget(title1: '整体最高分', title2: '', val1: doubleToStringAsFixed(overallLevel.max), val2: ''), + SizedBox(height: 30.h), + + /// ----------------------- 表格 ---------------------- + + /// 试题标签分析 + // if (tagAnalysis.isNotEmpty) + reportSubjectTeacher.$baseTitleAndBody( + title: '试题标签分析', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: reportSubjectTeacher.$listTable( + titles: [ + reportSubjectTeacher.ListTableTitle(title: '题型', titleKey: 'QuestionLevelTitle'), + reportSubjectTeacher.ListTableTitle(title: '涉及题号', titleKey: 'QuestionNums', alignment: Alignment.center), + reportSubjectTeacher.ListTableTitle(title: '正确率', titleKey: 'CorrectRate', alignment: Alignment.center), + reportSubjectTeacher.ListTableTitle(title: '得分率', titleKey: 'ScoringRate', alignment: Alignment.center), + ], + datas: tagAnalysis.map((e) => e.toJson()), + ), + ), + + /// 本次考试知识点掌握度 + reportSubjectTeacher.$baseTitleAndBody( + title: '本次考试知识点掌握度', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: reportSubjectTeacher.$listTable( + titles: [ + reportSubjectTeacher.ListTableTitle(title: '知识点', titleKey: 'Name'), + reportSubjectTeacher.ListTableTitle(title: '对应题号', titleKey: 'QuestionNums', alignment: Alignment.center), + reportSubjectTeacher.ListTableTitle(title: '个人正确率', titleKey: 'UserCorrectRate', alignment: Alignment.center), + reportSubjectTeacher.ListTableTitle(title: '班级正确率', titleKey: 'ClassCorrectRate', alignment: Alignment.center), + ], + datas: knowledgeAnalysisList.map((e) => e.toJson()), + ), + ), + + /// 错题详情 + reportSubjectTeacher.$baseTitleAndBody( + title: '错题详情', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: reportSubjectTeacher.$listTable( + titles: [ + reportSubjectTeacher.ListTableTitle(title: '题号', titleKey: 'No'), + reportSubjectTeacher.ListTableTitle(title: '题目', titleKey: 'Title', alignment: Alignment.center, isHtmlContent: true), + reportSubjectTeacher.ListTableTitle( + title: '学生答案', + titleKey: 'YouAnswer', + alignment: Alignment.center, + call: (Map item, reportSubjectTeacher.ListTableTitle titleModel) { + var val = item[titleModel.titleKey]?.toString() ?? ''; + + if (['1', 1].contains(item['Type'])) { + return Container( + alignment: titleModel.alignment, + child: titleModel.isHtmlContent ? HtmlWidget(val) : quickText(val, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 2), + ); + } + + List dataList = []; + try { + dataList = json.decode(val); + } catch (e) {} + List models = dataList.map((e)=>GalleryExampleItemModel(id: '', resource: 'https:' +e)).toList(); + + return Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: dataList.map((url) { + + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GalleryPhotoViewWrapper( + galleryItems: models, + backgroundDecoration: const BoxDecoration(color: Colors.black), + initialIndex: 0, + scrollDirection: Axis.vertical, + ), + ), + ); + }, + child: CachedNetworkImage( + imageUrl: 'https:' + url, + height: 200.h, + placeholder: (context3, url) => CircularProgressIndicator(), + errorWidget: (context3, url, error) => Icon(Icons.error), + ), + ); + }).toList(), + ), + ); + }), + // reportSubjectTeacher.ListTableTitle(title: '标准答案', titleKey: 'Answer', alignment: Alignment.center,isHtmlContent: true), + ], + datas: answerDetails.map((e) => e.toJson()), + ), + ), + ], + ), + ), + ); +} + +/// 基本信息table +@swidget +Widget $baseicInformationTable(BaseInfo baseInfo) { + List columns = [ + {'我的得分/总分值': getDoubleRemoveZero(baseInfo.score) + '/' + getDoubleRemoveZero(baseInfo.totalScore), '学科': baseInfo.subject.subjectName}, + {'考试日期': baseInfo.startTime, '评级': baseInfo.rating}, + ]; + + return Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Color.fromRGBO(232, 235, 255, 1), width: 1.3.r), + borderRadius: BorderRadius.circular(4).r, + ), + child: Column(mainAxisSize: MainAxisSize.min, children: columns.map((e) => reportSubjectTeacher.$GetTableRow(e)).toList()), + ), + ), + ); +} diff --git a/marking_app/lib/pages/reports/report_subject_teacher.dart b/marking_app/lib/pages/reports/report_subject_teacher.dart new file mode 100644 index 0000000..c589739 --- /dev/null +++ b/marking_app/lib/pages/reports/report_subject_teacher.dart @@ -0,0 +1,782 @@ +import 'dart:convert'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_card_swiper/flutter_card_swiper.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/report/report_for_marking_pagerdetail_model.dart'; +import 'package:marking_app/common/model/report/report_for_marking_pagerdetail_params.dart'; +import 'package:marking_app/common/model/report/report_for_subject_teacher_model.dart'; +import 'package:marking_app/common/model/report/report_for_subject_teacher_params.dart'; +import 'package:marking_app/pages/reports/index.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; + +part 'report_subject_teacher.g.dart'; + +class ReportSubjectTeacher extends StatefulWidget { + final int examId; + final int classId; + final int subject; + final String subjectName; + ReportSubjectTeacher({required this.examId, required this.classId, required this.subject, required this.subjectName, Key? key}) : super(key: key); + + @override + State createState() => _ReportSubjectTeacherState(); +} + +class _ReportSubjectTeacherState extends State with CommonMixin { + OverlayEntry? overlayEntry; + OverlayState? overlayState; + bool _showAppBar = true; + double _scrollOffset = 0.0; + double _appBarHeight = kToolbarHeight; + late Future _future; + + @override + void initState() { + overlayState = Overlay.of(context); + _future = _getData(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + overlayEntry?.remove(); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: const SystemUiOverlayStyle( + systemNavigationBarColor: Color(0xFF000000), + systemNavigationBarDividerColor: null, + statusBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarBrightness: Brightness.light, + ), + child: Scaffold( + backgroundColor: Colors.transparent, + extendBodyBehindAppBar: true, + appBar: !_showAppBar + ? null + : AppBar( + centerTitle: true, // 标题居中 + leading: IconButton( + icon: Icon(Icons.arrow_back_ios, color: Color.fromRGBO(80, 87, 103, 1), size: 20.sp), + onPressed: () => Navigator.of(context).pop(), + ), + title: quickText( + '学科老师报告', + size: 16.sp, + color: Color.fromRGBO(45, 56, 76, 1), + ), + elevation: 0, + shadowColor: Colors.transparent, + backgroundColor: Colors.transparent, + ), + body: Stack( + children: [ + // 渐变背景色 + $ScaffoldBgc(), + + MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _future, (ReportForSubjectTeacherModel? datas) { + if (datas == null) { + return Center( + child: Container( + child: CupertinoButton( + padding: const EdgeInsets.only(left: 10, right: 10), + color: Theme.of(context).primaryColor, + //按钮被按下时的不透明度程度,0~1之间 + pressedOpacity: 1, + //这也可以自己定义Container然后固定大小进行设置等等 + child: quickText('没有数据,请重试', color: Colors.white, size: 14.sp), + onPressed: () => easyThrottle('getSubjectTeacher/\'sReportAgain', () => toUpState(setState, () {}, mounted)), + ), + ), + ); + } + + return NotificationListener( + onNotification: (notification) { + if (notification is ScrollUpdateNotification) { + bool _oldShowAppBar = _showAppBar; + + _scrollOffset += notification.scrollDelta!; + if (_scrollOffset < 0.0) { + _scrollOffset = 0.0; + } else if (_scrollOffset > _appBarHeight) { + _scrollOffset = _appBarHeight; + } + if (_scrollOffset == 0.0) { + _showAppBar = true; + } else if (_scrollOffset == _appBarHeight) { + _showAppBar = false; + } + if (_oldShowAppBar != _showAppBar) setState(() {}); + } + return true; + }, + child: $reportBody(datas, widget.subjectName, context, (StudentDetailList item) async { + int theCurrentIndex = 1; + List urls = await getViewOriginalVolume(ReportForMarkingPagerdetailParams( + examSubjectSchoolId: item.examSubjectSchoolId, + examSubjectId: item.examSubjectId, + examInfoId: item.examId, + studentExamNum: item.studentExamNum, + )); + + if (urls.isEmpty) { + return; + } + overlayEntry = OverlayEntry( + opaque: true, + builder: (context1) { + return StatefulBuilder( + builder: (context2, state) { + return Stack( + children: [ + Container( + color: Color.fromRGBO(0, 0, 0, 0.7), + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight, + ), + CardSwiper( + padding: EdgeInsets.zero, + cardsCount: urls.length, + cardBuilder: (context3, index) { + return Container( + color: Colors.transparent, + alignment: Alignment.center, + child: CachedNetworkImage( + imageUrl: urls[index], + placeholder: (context3, url) => CircularProgressIndicator(), + errorWidget: (context3, url, error) => Icon(Icons.error), + ), + ); + }, + onSwipe: (int previousIndex, int? currentIndex, CardSwiperDirection direction) { + state(() => theCurrentIndex = (currentIndex ?? 0) + 1); + return true; + }, + ), + Positioned( + top: 100.h, + right: 22.w, + child: InkWell( + onTap: () => overlayEntry?.remove(), + child: Icon(Icons.close, size: 30.sp, color: Colors.white), + ), + ), + Positioned(bottom: 30.h, left: 22.w, child: quickText(theCurrentIndex.toString() + '/' + urls.length.toString(), color: Colors.white, size: 16.sp)) + ], + ); + }, + ); + }, + ); + overlayState?.insert(overlayEntry!); + }), + ); + }) + ], + ), + ), + ); + } + + /// 获取数据 + Future _getData() async { + try { + RestClientReport clientReport = await getClientReport(); + BaseStructureResultReport res = await clientReport.getreportsForSubjectTeacher(ReportForSubjectTeacherParams( + classId: widget.classId, + examId: widget.examId, + subject: widget.subject, + )); + if (res.success) { + return res.data; + } + ToastUtils.showError(res.message ?? '请求错误,请重试'); + } catch (e) { + if (e is CheckedFromJsonException) { + // JSON 解析失败,处理异常 + toPrint(val: 'JSON 解析失败: ${e.message}'); + toPrint(val: 'JSON 解析失败,出错字段: ${e.key}'); + } else { + // 其他异常,处理其他错误 + toPrint(val: '发生了其他错误: $e'); + } + } + return null; + } + + // 查看原卷接口 + Future> getViewOriginalVolume(ReportForMarkingPagerdetailParams params) async { + try { + ToastUtils.showLoading(); + RestClientReport clientReport = await getClientReport(); + BaseStructureResultReport res = await clientReport.getMarkingPagerDetails(params); + if (res.success && res.data != null) { + if (res.data!.markingPagerImgs.isEmpty) { + setTimeOut(1, () => ToastUtils.showInfo('暂时没有数据...')); + } + return res.data!.markingPagerImgs; + } + } catch (e) { + toPrint(val: e.toString()); + } finally { + ToastUtils.dismiss(); + } + + return []; + } +} + +/// 报告body +@swidget +Widget $reportBody(ReportForSubjectTeacherModel datas, String subjectName, BuildContext context, Function(StudentDetailList) call) { + BaseInfo baseInfo = datas.baseInfo; + GradeLevel gradeLevel = datas.gradeLevel; // 年级水平 + AllLevel allLevel = datas.allLevel; // 总体水平 + List improveStudents = datas.studentRankInfoTopList; // 排名上升集合 + List declineStudents = datas.studentRankInfoFallList; // 排名大幅下降 + List topFiveStudents = datas.studentResultInfoTopList; // 前5 + List lastFiveStudents = datas.studentResultInfoFallList; // 后5 + List questionList = datas.questionList; // 试题分析 + bool questionHasKownledgeName = questionList.any((element) => element.kownledgeName != ''); + + List studentDetailList = datas.studentDetailList; // 学生信息 + + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + child: ScrollConfiguration( + behavior: EUMNoScrollBehavior(), + child: ListView( + // top: 20.h, bottom: 20.h + children: [ + /// 基本信息 + $baseTitleAndBody( + title: '基本信息', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $baseicInformationTable(baseInfo), + ), + + /// 年级水平 + $baseTitleAndBody( + title: '年级水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $levelBoxWidget(title1: '本校参考班级数', title2: '本校排名', val1: gradeLevel.joinClassCount.toString(), val2: gradeLevel.ranking.toString()), + ), + + /// 总体水平 + $baseTitleAndBody( + title: '总体水平', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $levelBoxWidget(title1: '总参考班级数', title2: '总排名', val1: allLevel.joinClassCount.toString(), val2: allLevel.ranking.toString()), + ), + + /// 重点关注学生 + $baseTitleAndBody( + title: '重点关注学生', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: Column( + children: [ + $listTable( + titles: [ + ListTableTitle(title: '大幅进步', titleKey: 'StudentName'), + ListTableTitle(title: '排名', titleKey: 'Ranking', alignment: Alignment.center), + ListTableTitle(title: '排名提升', titleKey: 'RankChange', alignment: Alignment.center), + ], + datas: improveStudents.map((e) => e.toJson()), + ), + SizedBox(height: 10.h), + $listTable( + titles: [ + ListTableTitle(title: '大幅下降', titleKey: 'StudentName'), + ListTableTitle(title: '排名', titleKey: 'Ranking', alignment: Alignment.center), + ListTableTitle(title: '排名下降', titleKey: 'RankChange', alignment: Alignment.center), + ], + datas: declineStudents.map((e) => e.toJson()), + ), + SizedBox(height: 10.h), + $listTable( + titles: [ + ListTableTitle(title: '班级前5名', titleKey: 'StudentName'), + ListTableTitle(title: '成绩', titleKey: 'Result', alignment: Alignment.center), + ListTableTitle(title: '', titleKey: '', alignment: Alignment.center), + ], + datas: topFiveStudents.map((e) => e.toJson()), + ), + SizedBox(height: 10.h), + $listTable( + titles: [ + ListTableTitle(title: '班级后5名', titleKey: 'StudentName'), + ListTableTitle(title: '排名', titleKey: 'Result', alignment: Alignment.center), + ListTableTitle(title: '', titleKey: '', alignment: Alignment.center), + ], + datas: lastFiveStudents.map((e) => e.toJson()), + ) + ], + )), + + /// 试题分析 + $baseTitleAndBody( + title: '试题分析', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $listTableHorizontalScrolling(titles: [ + ListTableTitle(title: '题号', titleKey: 'QuestionNum', widthNum: 60.w), + ListTableTitle(title: '知识点', titleKey: 'KownledgeName', widthNum: questionHasKownledgeName ? 200.w : 40.w), + ListTableTitle(title: '正确率', titleKey: 'CorrectRate', alignment: Alignment.center), + ListTableTitle( + title: '正确答案及人数', + titleKey: 'StudentName', + call: (Map info, ListTableTitle titleModel) { + List correctAnswers = info['CorrectAnswerDetailList']; + + return Container( + child: Column( + children: correctAnswers.map((e) { + return quickText('${e.option}\/${e.checkCount}'); + }).toList(), + ), + ); + }), + ], datas: questionList.map((e) => e.toJson())), + ), + + /// 学生成绩详情 + $baseTitleAndBody( + title: '学生成绩详情', + titleImg: 'assets/images/report_home_icon_burst.png', + graphicContainer: $listTable( + titles: [ + ListTableTitle(title: '学生姓名', flex: 2, titleKey: 'StudentName'), + ListTableTitle(title: '成绩', alignment: Alignment.center, titleKey: 'Score'), + ListTableTitle(title: '班次', alignment: Alignment.center, titleKey: 'Ranking'), + ListTableTitle(title: '班次进退步', flex: 2, alignment: Alignment.center, titleKey: 'Step'), + ListTableTitle(title: '操作', flex: 2, alignment: Alignment.center, titleKey: ''), + ], + datas: studentDetailList.map((e) => e.toJson()), + operateWidget: (Map itemMap) { + StudentDetailList item = StudentDetailList.fromJson(itemMap); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + InkWell( + onTap: () { + RouterManager.router.navigateTo( + context, + RouterManager.reportPersonalSubjectPath + '?examId=${item.examId}&userId=${item.userId}&subject=${item.subjectId}&studentName=${Uri.encodeComponent(item.studentName)}&subjectName=${Uri.encodeComponent(subjectName)}', + transition: getTransition(), + ); + }, + child: quickText('报告', size: 12.sp, color: Theme.of(context).primaryColor, decoration: TextDecoration.underline), + ), + SizedBox(width: 10.w), + InkWell( + onTap: () => call(item), + child: quickText('原卷', size: 12.sp, color: Theme.of(context).primaryColor, decoration: TextDecoration.underline), + ), + ], + ); + }), + ), + ], + ), + ), + ); +} + +/// 基本信息table +@swidget +Widget $baseicInformationTable(BaseInfo baseInfo) { + List columns = [ + {'考试名称': baseInfo.examName, '考试类型': baseInfo.examTypeName}, + {'年级': baseInfo.grade, '学科': baseInfo.subjects}, + {'考试日期': baseInfo.examTime, '满分': getDoubleRemoveZero(baseInfo.totalScore) + '分'}, + {'平均分': getDoubleRemoveZero(baseInfo.avgScore) + '分', '最高分': getDoubleRemoveZero(baseInfo.maxScore) + '分'}, + {'优秀率(120)': baseInfo.goodRate, '及格率': baseInfo.passRate}, + ]; + + return Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Color.fromRGBO(232, 235, 255, 1), width: 1.3.r), + borderRadius: BorderRadius.circular(4).r, + ), + child: Column(mainAxisSize: MainAxisSize.min, children: columns.map((e) => $getTableRow(e)).toList()), + ), + ), + ); +} + +@swidget +Widget $getTableRow(Map datas) { + return IntrinsicHeight( + child: Container( + decoration: BoxDecoration(border: Border.all(color: Color.fromRGBO(229, 234, 255, 1), width: 0.4.r)), + child: Row( + children: datas.entries.map((entry) { + return Expanded( + child: Row( + children: [ + Expanded( + flex: 10, + child: Container( + height: double.infinity, + alignment: Alignment.centerLeft, + padding: EdgeInsets.all(9.5).r, + decoration: BoxDecoration( + color: Color.fromRGBO(245, 247, 255, 1), + ), + child: quickText(entry.key, color: Color.fromRGBO(80, 87, 103, 1), size: 12.sp, fontWeight: FontWeight.w400, maxLines: 6), + ), + ), + Expanded( + flex: 11, + child: Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.all(9.5).r, + child: quickText(entry.value, color: Color.fromRGBO(45, 56, 76, 1), size: 12.sp, fontWeight: FontWeight.w500, maxLines: 6), + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ); +} + +/// 基本信息table +@swidget +Widget $baseTitleAndBody({required String title, required String titleImg, required Widget graphicContainer, EdgeInsetsGeometry? margin}) { + return Container( + width: double.infinity, + margin: margin ?? EdgeInsets.only(bottom: 28.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset(titleImg, width: 22.w, height: 22.h), + SizedBox(width: 2.w), + quickText(title, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1)), + ], + ), + SizedBox(height: 6.h), + graphicContainer, + ], + ), + ); +} + +// 年级水平Widget +@swidget +Widget $levelBoxWidget({required String title1, required String title2, required String val1, required String val2}) { + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only(left: 19.w, top: 16.h, bottom: 16.h), + decoration: BoxDecoration( + image: const DecorationImage(image: AssetImage('assets/images/report_level_bgm.png'), fit: BoxFit.fill), + borderRadius: BorderRadius.circular(6).r, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(title1, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 2), + SizedBox(height: 8.h), + quickText(val1, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.w700), + ], + ), + ), + ), + SizedBox(width: 22.w), + Expanded( + child: title2 == '' + ? Container() + : Container( + padding: EdgeInsets.only(left: 19.w, top: 16.h, bottom: 16.h), + decoration: BoxDecoration( + image: const DecorationImage(image: AssetImage('assets/images/report_level_bgm.png'), fit: BoxFit.fill), + borderRadius: BorderRadius.circular(6).r, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + quickText(title2, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 2), + SizedBox(height: 8.h), + quickText(val2, size: 16.sp, color: Color.fromRGBO(45, 56, 76, 1), fontWeight: FontWeight.w700), + ], + ), + ), + ), + ], + ), + ); +} + +class ListTableTitle { + int flex; + final String title; + final String titleKey; + final double? minWidthNum; + final double? widthNum; + final Alignment? alignment; + final bool isHtmlContent; + final Widget Function(Map, ListTableTitle)? call; + ListTableTitle({required this.title, required this.titleKey, this.flex = 1, this.widthNum, this.minWidthNum, this.alignment, this.isHtmlContent = false, this.call}); +} + +/// table +@swidget +Widget $listTable({required List titles, required Iterable> datas, Function? operateWidget}) { + return Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: false, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Column( + children: [ + /// table title + Container( + height: 40.h, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 17.w), + decoration: BoxDecoration( + color: Color.fromRGBO(240, 243, 255, 1), + borderRadius: BorderRadius.only(topLeft: Radius.circular(8).r, topRight: Radius.circular(6).r), + ), + child: Row( + children: titles.map((e) { + return Expanded( + flex: e.flex, + child: Container( + alignment: e.alignment, + child: quickText(e.title, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 6), + ), + ); + }).toList(), + ), + ), + + /// 数据. + ...datas.map((e) { + return Container( + padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 17.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color.fromRGBO(240, 243, 255, 1), width: 0.5.r), + ), + child: Row( + children: titles.map( + (titleModel) { + String val = e[titleModel.titleKey]?.toString() ?? ''; + var callFun = titleModel.call; + + return Expanded( + flex: titleModel.flex, + child: operateWidget != null && titleModel.title == '操作' + ? operateWidget(e) + : callFun != null + ? callFun(e, titleModel) + : Container( + alignment: titleModel.alignment, + child: titleModel.isHtmlContent ? HtmlWidget(val) : quickText(val, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 2), + ), + ); + }, + ).toList(), + ), + ); + }), + ], + ), + ), + ); +} + +/// table +@swidget +Widget $listTableHorizontalScrolling({required List titles, required Iterable> datas, ScrollController? scrollController, int? fixedIndex, bool showScrollBox = false}) { + bool theFixed = scrollController != null && fixedIndex != null && fixedIndex > 0; + List titlesFixed = theFixed ? titles.sublist(0, fixedIndex) : []; + + return Card( + elevation: 11.h, + shadowColor: Color.fromRGBO(46, 91, 255, 0.10), + borderOnForeground: false, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6).r, + side: BorderSide(color: Colors.white, width: 1.4.r), + ), + child: Container( + constraints: BoxConstraints(minWidth: double.infinity), + padding: EdgeInsets.only(bottom: 12.h), + child: Stack( + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: scrollController, + child: Column( + children: [ + Container( + height: 36.h, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 17.w), + decoration: BoxDecoration( + color: Color.fromRGBO(240, 243, 255, 1), + borderRadius: BorderRadius.only(topLeft: Radius.circular(6).r, topRight: Radius.circular(6).r), + ), + child: Row( + children: titles.map((e) { + return Container( + width: e.widthNum ?? 100.w, + alignment: e.alignment, + child: quickText(e.title, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400), + ); + }).toList()), + ), + + /// 数据. + ...datas.map((e) { + return Container( + padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 17.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color.fromRGBO(240, 243, 255, 1), width: 0.5.r), + ), + child: Row( + children: titles.map((titleModel) { + String val = e[titleModel.titleKey]?.toString() ?? ''; + Function? callFun = titleModel.call; + Alignment? alignment = titleModel.alignment; + MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start; + + if (alignment != null) { + mainAxisAlignment = MainAxisAlignment.center; + } + + return Container( + width: titleModel.widthNum ?? titleModel.minWidthNum ?? 100.w, + alignment: titleModel.alignment, + child: callFun != null + ? callFun(e, titleModel) + : Row(mainAxisAlignment: mainAxisAlignment, children: [ + Expanded( + child: Container( + alignment: titleModel.alignment, + child: quickText(val, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 6), + ), + ) + ]), + ); + }).toList(), + ), + ); + }), + ], + ), + ), + if (showScrollBox && theFixed) + SizedBox( + width: 86.w * titlesFixed.length, + child: + Column( + children: [ + Container( + height: 36.h, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.w), + decoration: BoxDecoration( + color: Color.fromRGBO(240, 243, 255, 1), + borderRadius: BorderRadius.only(topLeft: Radius.circular(6).r, topRight: Radius.circular(6).r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: titlesFixed.map((e) { + return Container( + alignment: Alignment.center, + child: quickText(e.title, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400), + ); + }).toList()), + ), + + /// 数据. + ...datas.map((e) { + return Container( + padding: EdgeInsets.symmetric(vertical: 6.h, horizontal: 17.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color.fromRGBO(240, 243, 255, 1), width: 0.5.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: titlesFixed.map((titleModel) { + String val = e[titleModel.titleKey]?.toString() ?? ''; + Function? callFun = titleModel.call; + Alignment? alignment = titleModel.alignment; + MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start; + + if (alignment != null) { + mainAxisAlignment = MainAxisAlignment.center; + } + + return Container( + width: 50.w, + alignment: Alignment.center, + child: callFun != null + ? callFun(e, titleModel) + : Row(mainAxisAlignment: mainAxisAlignment, children: [ + Expanded( + child: Container( + alignment: titleModel.alignment, + child: quickText(val, size: 12.sp, color: Color.fromRGBO(80, 87, 103, 1), fontWeight: FontWeight.w400, maxLines: 6), + ), + ) + ]), + ); + }).toList(), + ), + ); + }), + ], + ), + ), + ], + )), + ); +} diff --git a/marking_app/lib/pages/reports/reports/bar_chart_histogram.dart b/marking_app/lib/pages/reports/reports/bar_chart_histogram.dart new file mode 100644 index 0000000..b811238 --- /dev/null +++ b/marking_app/lib/pages/reports/reports/bar_chart_histogram.dart @@ -0,0 +1,207 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:marking_app/common/model/report/report_histogram_model.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; + +// ignore: must_be_immutable +class _BarChart extends StatelessWidget { + List tagComparisonList; + _BarChart(this.tagComparisonList); + + @override + Widget build(BuildContext context) { + return BarChart( + BarChartData( + barTouchData: barTouchData, + titlesData: titlesData, + + borderData: borderData, + barGroups: barGroups, + gridData: FlGridData( + show: true, + drawHorizontalLine: true, // 绘制横线 + drawVerticalLine: false, + // horizontalInterval: 10, // 设置横线间隔 + getDrawingHorizontalLine: (value) { + return FlLine( + color: Color.fromRGBO(229, 234, 255, 1), // 设置横线颜色 + strokeWidth: 0.8, // 设置横线宽度 + ); + }, + ), + alignment: BarChartAlignment.spaceAround, + // maxY: 20, + ), + ); + } + + BarTouchData get barTouchData => BarTouchData( + enabled: false, + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: Colors.transparent, + tooltipPadding: EdgeInsets.zero, + tooltipMargin: 6.h, + getTooltipItem: (BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex) { + return BarTooltipItem( + rod.toY.round().toString(), + TextStyle(color: const Color.fromRGBO(46, 91, 255, 1), fontWeight: FontWeight.w500, fontSize: 12.sp), + ); + }, + ), + ); + + Widget getTitles(double value, TitleMeta meta) { + final style = TextStyle( + color: const Color.fromRGBO(46, 91, 255, 1), + fontSize: 12.sp, + ); + ReportHistogramModel _model = tagComparisonList[value.toInt()]; + + String name = _model.name; + List newNames = []; + bool multiLine = name.length > 5; + if (multiLine) { + List nameList = name.split(''); + int nameListLength = nameList.length; + int fiveRemainder = nameListLength % 5; // 5数取余 + bool hasFiveRemainder = fiveRemainder > 0; // 是否有余数 + + int step = 5; + int i = 0; + while (i < nameListLength) { + int oldI = i; + i += hasFiveRemainder && i + step > nameListLength ? fiveRemainder : step; + newNames.add(nameList.sublist(oldI, i).join('') + '\n'); + } + } + + toPrint(val: '数据:' + newNames.toList().join('')); + return SizedBox( + height: 300.h, + child: SideTitleWidget( + axisSide: meta.axisSide, + space: 6, + child: multiLine + ? + // Text(newNames.toList().join(''), style: style, maxLines: null, textAlign: TextAlign.center) + RichText( + text: TextSpan( + children: newNames.map((e) { + toPrint(val: e); + return TextSpan(text: e, style: style); + }).toList()), + ) + : Text(name, style: style, maxLines: null, textAlign: TextAlign.center), + ), + ); + } + + Widget leftTitles(double value, TitleMeta meta) { + if (value == meta.max) { + return Container(); + } + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + meta.formattedValue, + style: TextStyle( + fontSize: 12.sp, + color: Color.fromRGBO(148, 163, 182, 1), + fontWeight: FontWeight.w400, + ), + ), + ); + } + + FlTitlesData get titlesData => FlTitlesData( + show: true, + bottomTitles: AxisTitles( + axisNameSize: 200.w, + drawBehindEverything: false, + sideTitles: SideTitles( + showTitles: true, + // margin: 16, // 设置底部标题的边距 + reservedSize: 30, + getTitlesWidget: getTitles, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: leftTitles, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ); + + FlBorderData get borderData => FlBorderData( + show: false, + ); + + LinearGradient get _barsGradient => LinearGradient( + colors: [ + Color.fromRGBO(46, 91, 255, 0.1), + Color.fromRGBO(46, 91, 255, 1), + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + ); + + List get barGroups => tagComparisonList.asMap().keys.map((i) { + ReportHistogramModel _model = tagComparisonList[i]; + return BarChartGroupData( + x: i, + barRods: [ + BarChartRodData( + toY: _model.val, + gradient: _barsGradient, + ) + ], + showingTooltipIndicators: [0], + ); + }).toList(); +} + +// ignore: must_be_immutable +class BarChartHistogram extends StatefulWidget { + List tagComparisonList; + bool horizontal; + double aspectRatio; + double singleWidth; + double heightVal; + BarChartHistogram(this.tagComparisonList, {this.singleWidth = 220, this.aspectRatio = 3, this.horizontal = false, this.heightVal = 200, super.key}); + + @override + State createState() => BarChartSample3State(); +} + +class BarChartSample3State extends State { + @override + Widget build(BuildContext context) { + if (!widget.horizontal) + return Container( + width: widget.tagComparisonList.length * widget.singleWidth.w, + height: widget.heightVal, + child: _BarChart(widget.tagComparisonList), + ); + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Container( + width: widget.tagComparisonList.length * widget.singleWidth.w, + child: AspectRatio( + aspectRatio: widget.aspectRatio, + child: _BarChart(widget.tagComparisonList), + ), + ), + ); + } +} diff --git a/marking_app/lib/pages/reports/services/report_home_services.dart b/marking_app/lib/pages/reports/services/report_home_services.dart new file mode 100644 index 0000000..980a88e --- /dev/null +++ b/marking_app/lib/pages/reports/services/report_home_services.dart @@ -0,0 +1,511 @@ +import 'package:achievement_view/achievement_view.dart'; +import 'package:easy_stepper/easy_stepper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_page_data_report.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/report/marked_item.dart'; +import 'package:marking_app/common/model/report/marked_item_params.dart'; +import 'package:marking_app/common/model/user/user_info_report.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; +import 'package:marking_app/utils/easy_refresh/mixin/refresh_data_handle_report.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; + +part 'report_home_services.g.dart'; + +// 用户身份和考试切换 +class SwitchIdentityExamsController with CommonMixin, RefreshDataHandleReport { + ValueNotifier positionIndex; // 职位下标 + ValueNotifier examIndex; // 考试下标 + ValueNotifier stepIndex; // 步骤下标 + ValueNotifier examId; // 考试ID + + ValueNotifier examParams; // 考试请求参数 + ValueNotifier?> exams; // 考试数据 + + // EasyRefreshController refreshController; + List positons; + + double? positionDy; // 考试Y轴位置 + + SwitchIdentityExamsController({ + required this.positionIndex, + required this.examIndex, + required this.stepIndex, + required this.examId, + required this.examParams, + required this.exams, + // required this.refreshController, + required this.positons, + }); + + void dispose() { + positons = []; + // refreshController.dispose(); + + exams.dispose(); + examParams.dispose(); + positionIndex.dispose(); + examIndex.dispose(); + stepIndex.dispose(); + } + + static SwitchIdentityExamsController use( + {required int position, + required int exam, + required int? initExamId, + int step = 0, + required List positons}) { + final positionuseState = useState(position); + final examState = useState(exam); + final stepState = useState(step); + final initExamIdState = useState(initExamId ?? -1); + + final examParamsState = + useState(MarkedItemParams(1, 10, roleId: positons.isNotEmpty ? positons[positionuseState.value].id : 0)); + final examsState = useState?>(null); + + // EasyRefreshController _easyRefresh = EasyRefreshController(); + SwitchIdentityExamsController example = SwitchIdentityExamsController( + positionIndex: positionuseState, + examIndex: examState, + stepIndex: stepState, + examId: initExamIdState, + examParams: examParamsState, + exams: examsState, + // refreshController: _easyRefresh, + positons: positons, + ); + + return example; + } + + void setIndexData({required int indexExam, required int indexPosition}) { + examIndex.value = indexExam; + examId.value = exams.value?[indexExam].id ?? -1; + positionIndex.value = indexPosition; + } + + /// temPositionIndex 当前选中的步骤 + void nextStepButtonCall(int temPositionIndex, EasyRefreshController refreshController, state) { + int oldPositionIndex = positionIndex.value; // 旧职位小标 + positionIndex.value = temPositionIndex; + + examParams.value.roleId = positons[positionIndex.value].id; // 用户身份职位更新 + // 职务没有改变,不用再发起请求直接跳转到考试 + state(() => stepIndex.value = 1); + + if (exams.value == null || oldPositionIndex != temPositionIndex) { + // 需要发起请求 + examId.value = -1; + examIndex.value = -1; + exams.value?.clear(); + positionDy = null; // 清空确定位置 + setTimeOut(200, () => refreshController.callRefresh()); + } + } + + Future getExamData( + {required BuildContext context, + required state, + required EasyRefreshController refreshController, + bool isRefresh = true}) async { + MarkedItemParams params = examParams.value; + if (isRefresh) { + // 更新数据 + examParams.value.roleId = positons[positionIndex.value].id; + } else { + // 数据页自加 + params.page += 1; + } + + RestClientReport clientReport = await getClientReport(); + BasePageDataReport? results = await toRefreshData( + refreshController, + api: clientReport.getexamsbyuser, + params: examParams.value, + isReFresh: isRefresh, + context: context, + ); + if (results != null) { + if (isRefresh) exams.value?.clear(); + + if (exams.value == null) { + exams.value = results.items; + } else { + exams.value!.addAll(results.items); + } + int theExamId = examId.value; + if (theExamId != -1) { + MarkedItem? item = exams.value?.firstWhere((element) => element.id == theExamId, + orElse: () => MarkedItem(-1, -1, '', -1, '', '', '', -1, '')); + if (item != null && item.id != -1) { + examId.value = item.id; + examIndex.value = exams.value!.indexOf(item); + } else { + examId.value = -1; + examIndex.value = -1; + } + } + state(() => {}); + } + } + + Future openSwitch( + {required BuildContext context, required int currentExamId, required int currentPositionIndex}) async { + var size = MediaQuery.of(context).size; //获取屏幕宽高和Widget大小 + ScrollController _scroller = ScrollController(); + EasyRefreshController _easyRefresh = EasyRefreshController(); + Future future = showModalBottomSheet( + backgroundColor: Colors.transparent, + //颜色空白 + isDismissible: true, + //能否点击消失 + isScrollControlled: true, + //能否拖动消失 + context: context, + //接受cotext + builder: (context) { + // int temPositionIndex = positionIndex.value; + // int temExamIndex = examIndex.value; + + int temPositionIndex = currentPositionIndex; + int temExamIndex = examIndex.value; + + return StatefulBuilder(builder: (context, state) { + MarkedItem? currentExam = + (exams.value?.isEmpty ?? true) || temExamIndex == -1 ? null : exams.value![temExamIndex]; + if (currentExam == null && temExamIndex == -1 && exams.value != null) { + temExamIndex = exams.value!.indexWhere((element) => element.id == currentExamId); + if (temExamIndex != -1) { + currentExam = exams.value![temExamIndex]; + } + } + final Positions currentPosition = positons[temPositionIndex]; + bool roleBody = stepIndex.value == 0; + + return Stack( + alignment: const FractionalOffset(0.96, 0.02), + children: [ + Container( + width: size.width, + height: size.height * 0.8, //使用屏幕宽高比设置大小 + padding: EdgeInsets.only(top: 12.h, left: 20.w, right: 20.w, bottom: 21.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + //圆角 + topLeft: Radius.circular(20), //左上角的圆角 + topRight: Radius.circular(20), //右上角的圆角 + ), + ), + child: Column( + children: [ + $EasyStepper(stepIndex.value, Theme.of(context).primaryColor.withOpacity(0.3), (int stepVal) { + int oldStep = stepIndex.value; + if (oldStep == stepVal) return; + + stepIndex.value = stepVal; + if (stepVal == 1) { + // 角色 =》 考试 + nextStepButtonCall(temPositionIndex, _easyRefresh, state); + } else { + // 考试 =》 角色 + state(() => {}); + } + }), + Expanded( + child: $ChooseBox( + roleBody, + roleBody + ? + // 角色body + ScrollConfiguration( + behavior: EUMNoScrollBehavior(), + child: positons.isEmpty + ? Center(child: MyEmptyWidget(padding: EdgeInsets.only(top: 80.h))) + : ListView( + children: positons.map((e) { + bool isCurrent = currentPosition.id == e.id; + return InkWell( + onTap: () { + state(() => temPositionIndex = positons.indexOf(e)); + }, + child: $ItemContainer(isCurrent, e.name, context), + ); + }).toList(), + ), + ) + : + // 考试body + $ExamsContainer( + refreshController: _easyRefresh, + widgets: (exams.value == null || exams.value!.isEmpty) + ? [Center(child: MyEmptyWidget(padding: EdgeInsets.only(top: 80.h)))] + : exams.value!.map((e) { + bool isCurrent = + currentExam == null ? e.id == currentExamId : currentExam.id == e.id; + return GestureDetector( + onTapUp: (TapUpDetails details) { + Offset position = details.localPosition; + positionDy = position.dy; + state(() => temExamIndex = exams.value!.indexOf(e)); + }, + child: $ItemContainer(isCurrent, e.name, context), + ); + }).toList(), + toGetDatacall: (bool isRefresh) => getExamData( + context: context, + state: state, + refreshController: _easyRefresh, + isRefresh: isRefresh), + ), + ), + ), + if (roleBody) // 下一步按钮 + $NextStepButton(temPositionIndex, temExamIndex, context, + () => nextStepButtonCall(temPositionIndex, _easyRefresh, state)) + else // 确认按钮 + $ConfirmButton(temPositionIndex, temExamIndex, context, () async { + if (temExamIndex == -1) { + return AchievementView( + elevation: 0.5, + duration: Duration(seconds: 1), + title: "提示", + subTitle: "请选择想要查看的考试", + color: Theme.of(context).primaryColor, + ).show(context); + } + try { + ToastUtils.showLoading(); + setIndexData(indexExam: temExamIndex, indexPosition: temPositionIndex); + RestClientReport clientReport = await getClientReport(); + BaseStructureResultReport baseRest = + await clientReport.switchposition(currentPosition.id); + if (!baseRest.success || baseRest.data == null || baseRest.data == false) { + return ToastUtils.showError(baseRest.message ?? '身份切换错误,请重试'); + } + Navigator.of(context).pop(true); + } catch (e) { + toPrint(val: '请求报错....,$e'); + } finally { + ToastUtils.dismiss(); + } + }), + ], + ), + ), + InkWell( + onTap: () { + Navigator.of(context).pop(false); + }, + child: Icon(Icons.close, size: 30.sp, color: Color.fromRGBO(148, 163, 182, 1)), + ) + ], + ); + }); + }, + ); +// .then((value) => stepIndex.value = 0) + bool? flag = await future.whenComplete(() { + _scroller.dispose(); + _easyRefresh.dispose(); + }); // 参数初始化 + stepIndex.value = 0; + + if (flag != null && flag) return examId.value; + if (flag == null) { + if (positionIndex.value != 0) positionIndex.value = 0; + } + return null; + } +} + +@swidget +Widget $examsContainer( + {required EasyRefreshController refreshController, + required List widgets, + required Function(bool isRefresh) toGetDatacall}) { + return EasyRefresh( + firstRefresh: false, + taskIndependence: true, + enableControlFinishLoad: true, + enableControlFinishRefresh: true, + controller: refreshController, + header: MaterialHeader(), + footer: MaterialFooter(), + onRefresh: () async => toGetDatacall(true), + onLoad: () async => toGetDatacall(false), + child: ListView(children: widgets), + ); +} + +@swidget +Widget $itemContainer(bool isCurrent, String name, BuildContext context) { + return Container( + height: 44.h, + decoration: BoxDecoration( + color: isCurrent ? Color.fromRGBO(235, 239, 255, 1) : Color.fromRGBO(248, 249, 255, 1), + borderRadius: BorderRadius.all(Radius.circular(6.sp)), + border: isCurrent ? Border.all(color: Theme.of(context).primaryColor) : null, + ), + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(vertical: 3.h, horizontal: 17.w), + margin: EdgeInsets.only(bottom: 12.h), + child: Row( + children: [ + Expanded( + child: quickText(name, + size: 16.sp, color: isCurrent ? Theme.of(context).primaryColor : Color.fromRGBO(45, 56, 76, 1)), + ), + if (isCurrent) Icon(Icons.check_circle, color: Theme.of(context).primaryColor, size: 20.sp) + ], + ), + ); +} + +@swidget +Widget $chooseBox(bool isRole, Widget bodyWidget) { + return Column( + children: [ + // Row( + // children: [ + // Image.asset( + // isRole ? 'assets/images/role_selection.png' : 'assets/images/exam_selection.png', + // width: 29.w, + // height: 29.h, + // ), + // quickText(isRole ? '选择角色' : '选择考试', size: 18.sp, color: Color.fromRGBO(45, 56, 76, 1)), + // ], + // ), + SizedBox(height: 16.h), + Expanded(child: bodyWidget), + ], + ); +} + +@swidget +Widget $confirmButton(temPositionIndex, temExamIndex, context, ontapCall) { + return GestureDetector( + onTap: () => easyThrottle('confirmRoleExamCall', () => ontapCall()), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 20.h), + Container( + width: double.infinity, + height: 46.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(92.sp)), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.50).withOpacity(0.2), // 阴影的颜色 + offset: Offset(4.w, 6.h), // 阴影与容器的距离 + blurRadius: 6.0, // 高斯的标准偏差与盒子的形状卷积。 + spreadRadius: 5, // 在应用模糊之前,框应该膨胀的量。 + ), + ], + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [Color.fromRGBO(54, 86, 255, 0.99), Color.fromRGBO(93, 128, 255, 1)], + ), + ), + alignment: Alignment.center, + child: quickText('确 认', color: Colors.white, size: 18.sp, fontWeight: FontWeight.w400), + ) + ], + ), + ); +} + +@swidget +Widget $nextStepButton(temPositionIndex, temExamIndex, context, ontapCall) { + return GestureDetector( + onTap: () => easyThrottle('nextRoleToExamCall', () => ontapCall()), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 20.h), + Container( + width: double.infinity, + height: 46.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(92.sp)), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(46, 91, 255, 0.50).withOpacity(0.2), // 阴影的颜色 + + offset: Offset(4.w, 6.h), // 阴影与容器的距离 + blurRadius: 6.0, // 高斯的标准偏差与盒子的形状卷积。 + spreadRadius: 5, // 在应用模糊之前,框应该膨胀的量。 + ), + ], + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [Color.fromRGBO(54, 86, 255, 0.99), Color.fromRGBO(93, 128, 255, 1)], + ), + ), + alignment: Alignment.center, + child: quickText('下一步', color: Colors.white, size: 18.sp, fontWeight: FontWeight.w400), + ) + ], + ), + ); +} + +@swidget +Widget $easyStepper(int activeStep, Color finishedStepBackgroundColor, OnStepReached oncall) { + return EasyStepper( + alignment: Alignment.centerLeft, + disableScroll: true, + activeStep: activeStep, + lineLength: 170, + stepShape: StepShape.rRectangle, + stepBorderRadius: 15, + borderThickness: 2, + internalPadding: 10, + stepRadius: 28, + // finishedStepBorderColor: Colors.deepOrange, + // finishedStepTextColor: Colors.deepOrange, + finishedStepBackgroundColor: finishedStepBackgroundColor, + // activeStepIconColor: Colors.deepOrange, + showLoadingAnimation: true, + steps: [ + EasyStep( + customStep: ClipRRect( + borderRadius: BorderRadius.circular(15).r, + child: Opacity( + opacity: activeStep == 0 ? 1 : 0.3, + child: Image.asset('assets/images/role_selection.png'), + ), + ), + customTitle: const Text( + '选择角色', + textAlign: TextAlign.center, + ), + ), + EasyStep( + customStep: ClipRRect( + borderRadius: BorderRadius.circular(15).r, + child: Opacity( + opacity: activeStep == 1 ? 1 : 0.3, + child: Image.asset('assets/images/exam_selection.png', width: 80.w, height: 80.h), + ), + ), + customTitle: const Text( + '选择考试', + textAlign: TextAlign.center, + ), + ), + ], + onStepReached: oncall, + ); +} diff --git a/marking_app/lib/provider/annotation_graffiti_switch_provider.dart b/marking_app/lib/provider/annotation_graffiti_switch_provider.dart new file mode 100644 index 0000000..2d84537 --- /dev/null +++ b/marking_app/lib/provider/annotation_graffiti_switch_provider.dart @@ -0,0 +1,78 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'package:marking_app/common/model/marking/annotation_graffiti_switch.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; + +// 涂鸦批注开关 +final annotationGraffitiSwitchProvider = + StateNotifierProvider( + (ref) => AnnotationGraffitiSwitchProviderHandle(AnnotationGraffitiSwitch())); + +class AnnotationGraffitiSwitchProviderHandle extends StateNotifier with CommonMixin { + AnnotationGraffitiSwitchProviderHandle(AnnotationGraffitiSwitch progress) : super(progress); + + void init() { + setSwitch(false); + } + + // 批注涂鸦开关 + void setSwitch(bool flag) { + if (state.annotationSwitch == flag) return; + + state.annotationSwitch = flag; + // state.openBrush = false; + // state.openEraser = false; + // state.openClearAll = false; + // state.magnifier = false; + // state.trajectoryDisplay = false; + state = AnnotationGraffitiSwitch.fromJson(state.toJson()); + } + + // 设置画笔 + void setSwitchBrush() { + state.openBrush = !state.openBrush; + state.openEraser = false; + state.openClearAll = false; + state.magnifier = false; + state.trajectoryDisplay = false; + state = AnnotationGraffitiSwitch.fromJson(state.toJson()); + } + + // 设置橡皮擦 + void setSwitchEraser() { + state.openBrush = false; + state.openEraser = !state.openEraser; + state.openClearAll = false; + state.magnifier = false; + state.trajectoryDisplay = false; + state = AnnotationGraffitiSwitch.fromJson(state.toJson()); + } + + // 设置放大镜 + void setMagnifier() { + state.openBrush = false; + state.openEraser = false; + state.openClearAll = false; + state.trajectoryDisplay = false; + state.magnifier = !state.magnifier; + state = AnnotationGraffitiSwitch.fromJson(state.toJson()); + } + + // 设置笔记回显 + void setTrajectory() { + state.openBrush = false; + state.openEraser = false; + state.openClearAll = false; + state.magnifier = false; + state.trajectoryDisplay = !state.trajectoryDisplay; + state = AnnotationGraffitiSwitch.fromJson(state.toJson()); + } +} diff --git a/marking_app/lib/provider/do_marking_provider.dart b/marking_app/lib/provider/do_marking_provider.dart new file mode 100644 index 0000000..3588c6e --- /dev/null +++ b/marking_app/lib/provider/do_marking_provider.dart @@ -0,0 +1,86 @@ +/* 当前评阅任务ID */ +import 'dart:convert'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/enum/KeyboardType.dart'; +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/utils/fast_data.dart'; +import 'package:marking_app/utils/index.dart'; + +// 阅卷键盘习惯偏好 +final markingKeyboardProvider = StateNotifierProvider( + (ref) => DoMarkingKeyboardHandle(DoMarkingKeyboardModel())); + +// 阅卷键盘习惯偏好 +class DoMarkingKeyboardHandle extends StateNotifier { + DoMarkingKeyboardHandle(DoMarkingKeyboardModel moedl) : super(moedl); + + // 初始化键盘 + void initMarkingKeyboard() async { + DoMarkingKeyboardModel? localModel = await FastData.getInstance().getPreferences(); + if (localModel != null) { + SortKeyboard? sort = localModel.sort; + // FULL_AND_AERO_TOP_INVERTED_ORDER 废弃 如果缓存中设置了这个需要自定义到默认设置项 + if (sort != null && sort == SortKeyboard.FULL_AND_AERO_TOP_INVERTED_ORDER) + localModel.sort = SortKeyboard.INVERTED_ORDER; + toggleKeyboard(localModel, synchro: false); + } + } + + // 辅助键盘打关闭切换 + Future changeOpenAuxiliary(bool? val) async { + try { + if (state.keyboard != KeyboardType.RIGHT_SELECTION) { + return null; + } + state.openAuxiliary = val ?? !state.openAuxiliary; + toggleKeyboard(DoMarkingKeyboardModel.fromJson(state.toJson())); + return state.openAuxiliary; + } catch (e) { + return null; + } + } + + // 切换键盘 + Future toggleKeyboard(DoMarkingKeyboardModel item, {bool synchro = true}) async { + if (!synchro) { + state = item; + return true; + } + bool flag = false; + if (synchro) { + flag = await FastData.getInstance().setPreferences(item); + } + if (flag) state = item; + return flag; + } + + // 关闭引导页 + void closeTheBootPage() { + state.guidePageDisplay = false; + } + + // 清空 + void clean() { + toggleKeyboard(DoMarkingKeyboardModel()); + FastData.getInstance().cleanPreferences(); + } +} + +// 阅卷小题切换监听 +final markingSubtopicSwitchingProvider = + StateNotifierProvider((ref) => MarkingSubtopicSwitchingHandle(-1)); + +// 阅卷小题切换监听 +class MarkingSubtopicSwitchingHandle extends StateNotifier { + MarkingSubtopicSwitchingHandle(int moedl) : super(moedl); + + void setVal(int index) { + state = index; + } + + // 清空 + void clean() { + state = -1; + } +} diff --git a/marking_app/lib/provider/review_provider.dart b/marking_app/lib/provider/review_provider.dart new file mode 100644 index 0000000..45b5a59 --- /dev/null +++ b/marking_app/lib/provider/review_provider.dart @@ -0,0 +1,25 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-09-27 14:34:36 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 16:14:17 + * @FilePath: \marking_app\lib\provider\review_provider.dart + * @Description: 当前评阅任务 + */ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/model/marking/current_review_task.dart'; + +/* 当前评阅任务ID */ +final currentTaskIdProvider = StateNotifierProvider((ref) => TaskIdProviderHandle(CurrentReviewTask())); + +class TaskIdProviderHandle extends StateNotifier { + TaskIdProviderHandle(CurrentReviewTask task) : super(task); + + setDoTaskEntity(CurrentReviewTask model){ + state = model; + } + + void clean(){ + state = CurrentReviewTask(); + } +} diff --git a/marking_app/lib/provider/upgrade_provider.dart b/marking_app/lib/provider/upgrade_provider.dart new file mode 100644 index 0000000..0c3be56 --- /dev/null +++ b/marking_app/lib/provider/upgrade_provider.dart @@ -0,0 +1,24 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP更新下载进度条 + */ + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final upgradeProvider = StateNotifierProvider((ref) => UpgradeProviderHandle(0.0)); + +class UpgradeProviderHandle extends StateNotifier { + UpgradeProviderHandle(double progress) : super(progress); + + setVal(val){ + state = val; + } + + void clean(){ + state = 0.0; + } +} diff --git a/marking_app/lib/provider/upload_file_provider.dart b/marking_app/lib/provider/upload_file_provider.dart new file mode 100644 index 0000000..6b85b02 --- /dev/null +++ b/marking_app/lib/provider/upload_file_provider.dart @@ -0,0 +1,160 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 16:17:33 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: APP上传文件状态 + */ + +import 'dart:io'; + +import 'package:dio/dio.dart'; +// import 'package:minio/io.dart'; +// import 'package:minio/minio.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/utils/request/rest_client.dart'; + +// API + +final uploadFileProvider = StateNotifierProvider( + (ref) => UploadFileProviderHandle(UploadImgSecretKey())); + +class UploadFileProviderHandle extends StateNotifier with CommonMixin { + // Minio? _minio; + UploadFileProviderHandle(UploadImgSecretKey progress) : super(progress); + + Future initConfig() async { + try { + RestClient client = await getClient(); + BaseStructureResult res = await client.getImageUploadKey(); + if (res.success && res.data != null) { + setVal(res.data!); + } + } catch (e) {} + } + + void setVal(UploadImgSecretKey val) { + // state = val; + + // if (![val.account, val.password, val.ip, val.endPoint, val.bucketName].contains(null)) { + // _minio ??= Minio( + // accessKey: val.account!, + // secretKey: val.password!, + // endPoint: val.ip!, + // port: val.endPoint, + // ); + // } + } + + void clean() { + state = UploadImgSecretKey(); + } + + // 安装规则上传图片 + /// addFileSuffix 文件后缀 + // Future _getFileName(String imgUrl, String addFileSuffix) async { + // if (_minio == null) { + // await initConfig(); + // } + // imgUrl.replaceAll(RegExp('https?:\\/\\/.+?\\/.+?\\/'), ''); + + // // List imgUrlArr = imgUrl.split('${state.bucketName!}/'); + // // String oneVal = imgUrlArr[1]; + // String oneVal = imgUrl.replaceAll(RegExp('https?:\\/\\/.+?\\/.+?\\/'), ''); + // String prefixUrl = oneVal.substring(0, oneVal.lastIndexOf('/') + 1); + // String suffixFileName = oneVal.substring(oneVal.lastIndexOf('/') + 1); + // List suffixFileNameArr = suffixFileName.split('.'); + // String prefix = suffixFileNameArr[0]; + // if (prefix.contains('-cmt')) { + // prefix = prefix.split('-cmt')[0]; + // } + // String suffix = suffixFileNameArr[1]; + // return '$prefixUrl${'$prefix-cmt-$addFileSuffix.'}$suffix'; + // } + + Future _upload(String path, String myObjectPath) async { + try { + // await _minio!.fPutObject(state.bucketName!, myObjectPath, path); + return true; + } catch (e) { + toPrint(val: '错误输出:....,$e'); + return false; + } + } + + Future uploadFile(String path, String fileName, String addFileSuffix) async { + // try { + // String newFileName = await _getFileName(fileName, addFileSuffix); + // FileResult fileModel = FileResult(myObject: newFileName); + // fileModel.path = path; + // bool uploadFlag = await _upload(path, fileModel.myObject); + // if (uploadFlag) { + // bool uploadFlag = await _upload(path, fileModel.myObject); + // // 上传图片成功 + // fileModel.url = 'https://${state.ip}:${state.endPoint!}/${state.bucketName!}/${fileModel.myObject}'; + // fileModel.success = true; + // } + // return fileModel; + // } catch (e) { + // toPrint(val: '上传报错....,$e'); + // return null; + // } + } + + Future getUploadFileConfig(UploadFileInterfaceConfig uploadConfig, File fileData) async { + Dio dio = Dio(); + try { + late Response response; + final bytes = await fileData.readAsBytes(); + + if (uploadConfig.uploadMethod == "PUT") { + response = await dio.put(uploadConfig.uploadUri!, + data: Stream.fromIterable(bytes.map((e) => [e])), + options: Options(headers: { + Headers.contentLengthHeader: bytes.length, // Set the content-length. + HttpHeaders.contentTypeHeader: 'image/png', + })); + } else if (uploadConfig.uploadMethod == "POST") { + response = await dio.post(uploadConfig.uploadUri!, + data: Stream.fromIterable(bytes.map((e) => [e])), + options: Options(headers: { + Headers.contentLengthHeader: bytes.length, // Set the content-length. + HttpHeaders.contentTypeHeader: 'image/png', + })); + } + print('文件上传成功:${response.data}'); + if ([200, 201, 204].contains(response.statusCode)) { + return FileResult(myObject: '', success: true, url: uploadConfig.downloadUri); + } + } catch (e) { + print('文件上传失败:$e'); + } finally { + dio.close(); + } + } + + /// 获取上传文件的URL + Future getFileUrl(String myObject, {bool failedDelete = false}) async { + // try { + // return await _minio!.presignedGetObject(state.bucketName!, myObject); + // } catch (e) { + // // 获取url失败 删除图片 + // if (failedDelete) removeFile(myObject); + // } + } + + // Future removeFile(String myObject) async { + // try { + // await _minio!.removeObject(state.bucketName!, myObject); + // return true; + // } catch (e) { + // return false; + // } + // } +} diff --git a/marking_app/lib/provider/user_provider.dart b/marking_app/lib/provider/user_provider.dart new file mode 100644 index 0000000..4cb81ca --- /dev/null +++ b/marking_app/lib/provider/user_provider.dart @@ -0,0 +1,116 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 18:16:06 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 15:33:02 + * @FilePath: \marking_app\lib\provider\user_provider.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/common/mixin/common.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/user/user_info.dart'; +import 'package:marking_app/common/model/user/user_info_report.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_client_report.dart'; + +const Map emptyUser = { + 'id': '', + 'userName': '', + 'loginName': '', + 'subjectIds': [], + 'positionNames': [], + 'schoolId': 0, + 'schoolName': '', + 'avatar': '', +}; +final userProvider = StateNotifierProvider((ref) => UserProviderHandle(UserInfo.fromJson(emptyUser))); + +class UserProviderHandle extends StateNotifier { + UserProviderHandle(UserInfo user) : super(user); + + // 初始化用户数据 + void initUserInfo() async { + String? userJsonStr = await FastData.getInstance().getUser(); + if (userJsonStr != null) { + debugPrint('本地用户信息$userJsonStr'); + state = UserInfo.fromJson(jsonDecode(userJsonStr)); + } + } + + Future clean() async { + state = UserInfo.fromJson(emptyUser); + FastData.getInstance().cleanUser(); + } +} + +/// ---------------- 用户TOKEN ------------------------------- + +final userTokenProvider = StateNotifierProvider((ref) => UserTokenHandle(ref, '')); + +class UserTokenHandle extends StateNotifier { + final Ref ref; + UserTokenHandle(this.ref, String token) : super(token); + + // 初始化用户数据 + Future initToken() async { + String? theToken = await FastData.getInstance().getToken(); + + if (theToken != null && theToken != '') { + state = theToken; + return true; + } + + clean(); + ref.read(userProvider.notifier).clean(); + return false; + } + + void clean() { + // 缓存清空 + FastData fastData = FastData.getInstance(); + fastData + ..cleanToken() + ..cleanUser() + ..cleanInputKeyboardGuidePage() + ..cleanUserReport(); + + state = ''; + } +} + +/// 报告用户信息 +final userReportProvider = StateNotifierProvider((ref) => UserReportHandle(ref, UserInfoReport(positions: [], positionIndex: 0))); + +class UserReportHandle extends StateNotifier with CommonMixin { + final Ref ref; + UserReportHandle(this.ref, UserInfoReport infoReport) : super(infoReport); + + // 初始化用户数据 + Future initUserReport() async { + try { + RestClientReport client = await getClientReport(); + BaseStructureResultReport res = await client.getReportSysUserinfo(); + if (res.success && res.data != null) { + state = res.data!..normal = true; + } else { + throw new Error(); + } + return res.success; + } catch (e) {} + return false; + } + + void setPositionIndex(int thePositionIndex) { + state.positionIndex = thePositionIndex; + } + + void clean() { + // 缓存清空 + FastData fastData = FastData.getInstance(); + fastData.cleanUserReport(); + } +} diff --git a/marking_app/lib/routes/RouterManager.dart b/marking_app/lib/routes/RouterManager.dart new file mode 100644 index 0000000..c8eb776 --- /dev/null +++ b/marking_app/lib/routes/RouterManager.dart @@ -0,0 +1,249 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-05 16:50:28 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-22 16:51:19 + * @FilePath: \marking_app\lib\routes\RouterManager.dart + * @Description: 路由 + */ + +import 'dart:convert'; + +import 'package:fluro/fluro.dart'; +import 'package:flutter/material.dart'; +import 'package:marking_app/common/model/enum/marking_list_type.dart'; +import 'package:marking_app/pages/common/startUpPage.dart'; +import 'package:marking_app/pages/homework_correction/review_job.dart'; + +import 'package:marking_app/pages/login/index.dart'; +import 'package:marking_app/pages/mainPage.dart'; +import 'package:marking_app/pages/marking/do_papers.dart'; +import 'package:marking_app/pages/homework_correction/do_papers_job.dart' as DoPapersHomework; +import 'package:marking_app/pages/marking/progress.dart'; +import 'package:marking_app/pages/marking/progress_abnormal.dart'; +import 'package:marking_app/pages/marking/review.dart'; +import 'package:marking_app/pages/mine/index.dart'; +import 'package:marking_app/pages/mine/other_pages/index.dart'; +import 'package:marking_app/pages/other/agreement_page.dart'; +import 'package:marking_app/pages/reports/report_class_teacher.dart'; +import 'package:marking_app/pages/reports/report_personal_subject.dart'; +import 'package:marking_app/pages/reports/report_subject_teacher.dart'; +import 'package:marking_app/utils/const_text.dart'; +import 'package:marking_app/utils/the_global.dart'; + +import '../utils/index.dart'; + +class RouterManager { + static const String root = '/'; + static const String startUpPath = '/startUpPath'; + static const String loginPath = '/login'; + static const String markingProgressPath = '/marking/progress'; + static const String markingReviewPath = '/marking/review'; + static const String markingJobReviewPath = '/marking/job/review'; + static const String markingReviewAbnormalPath = '/marking/review/abnormal'; + static const String markingDoPath = '/marking/do_papers'; + static const String markingHomeworkDoPath = '/marking_homework/do_papers'; + static const String agreementPath = 'other/agreement_page'; + static const String ohterMainPagePath = 'other/index'; + + static const String reportClassTeacherPath = 'report/details/reportClassTeacher'; + static const String reportSubjectTeacherPath = 'report/details/reportSubjectTeacher'; + static const String reportPersonalSubjectPath = 'report/details/reportPersonalSubject'; + static const String userMinePath = 'user/mine/index'; + // TheMine + + static final FluroRouter router = FluroRouter(); + + static void initRouter() { + _defineRoutes(); + } + + static Route? generator(RouteSettings routeSettings) { + var context = TheGlobal.navigatorKey.currentContext; + if (context != null) { + var settings = ModalRoute.of(context)?.settings; + if (settings != null) { + print('路由信息:'); + print(settings.name); + } + print('ssssssssss'); + } + return router.generator(routeSettings); + } + + // 启动页 + static final _startUpPageHandler = + Handler(handlerFunc: (BuildContext? context, Map> params) => const StartUpPage()); + // 主页 + static final _mainPageHandler = + Handler(handlerFunc: (BuildContext? context, Map> params) => const TheMainPage()); + // 登录页 + static final _loginPageHandler = + Handler(handlerFunc: (BuildContext? context, Map> params) => const TheLogin()); + // 阅卷进度页面 + static final _progressPageHandler = Handler( + handlerFunc: (BuildContext? context, Map> params) => + Progress(int.parse(params['examSubjectId']![0]), params['name']![0])); + // 回评页面 + static final _reviewPageHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { + int markingUserId = int.parse(params['markingUserId']![0]); + int examSubjectId = int.parse(params['examSubjectId']![0]); + + String examName = params['examName']![0]; + return Review(markingUserId, examSubjectId, examName); + }); + static final _reviewJobPageHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { + int markingUserId = int.parse(params['markingUserId']![0]); + int examSubjectId = int.parse(params['examSubjectId']![0]); + + String examName = params['examName']![0]; + return ReviewJob(markingUserId, examSubjectId, examName); + }); + + // 回评异常页面 + static final _reviewAbnormalPageHandler = Handler( + handlerFunc: (BuildContext? context, Map> params) => ProgressAbnormal( + int.parse(params['examSubjectId']![0]), + params['markingId']![0], + params['name']![0], + params['questionNum']![0])); + // 阅卷页面 + static final _markingDoPageHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { + try { + int markingtype = int.parse(params['markingtype']?[0] ?? '-1'); + int markingUserId = int.parse(params['markingUserId']![0]); + int examSubjectId = int.parse(params['examSubjectId']![0]); + // ignore: unrelated_type_equality_checks + bool isReview = params['isReview']?[0] == '1'; + bool exceptional = params['exceptional']?[0] == '1'; + + String? questionNum = params['questionNum']?[0]; + + int? pageOper; + if (params['pageOper'] != null && params['pageOper']![0] != null && params['pageOper']![0] != 'null') { + pageOper = int.parse(params['pageOper']![0]); + } + + int? detailId; + if (params['detailId'] != null && params['detailId']![0] != null && params['detailId']![0] != 'null') { + detailId = int.parse(params['detailId']![0]); + } + MarkingListType? markType; + if (markingtype != -1) { + markType = MarkingListType.values[markingtype]; + } + + return DoPapers( + markingUserId, + markingtype: markType, + examSubjectId: examSubjectId, + isReview: isReview, + pageOper: pageOper ?? 2, + exceptional: exceptional, + markingUserDetailId: detailId ?? 0, + questionNum: questionNum, + ); + } catch (e) { + toPrint(val: '进入跳转报错啦,$e'); + } + }); +// 批阅作业页面 + static final _markingHomeworkDoPageHandler = + Handler(handlerFunc: (BuildContext? context, Map> params) { + try { + int taskId = int.parse(params['taskId']![0]); + String taskName = params['taskName']![0]; + String className = params['className']![0]; + return DoPapersHomework.DoPapersJob(taskId: taskId, taskName: taskName, className: className); + } catch (e) { + toPrint(val: '进入跳转报错啦,$e'); + } + }); + + static final _agreementPageHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { + AGREEMENT_KEY key = AGREEMENT_KEY.values.byName(params['type']![0]); + return AgreementPage(type: key); + }); + static final _ohterMainPageHandler = Handler( + handlerFunc: (BuildContext? context, Map> params) => const OhterPage(), + ); + + static final _reportClassTeacherPageHandler = + Handler(handlerFunc: (BuildContext? context, Map> params) { + int examId = int.parse(params['examId']![0]); + + return ReportClassTeacher(examId: examId); + }); + + static final _reportSubjectTeacherPageHandler = Handler( + handlerFunc: (BuildContext? context, Map> params) { + int examId = int.parse(params['examId']![0]); + int classId = int.parse(params['classId']![0]); + int subject = int.parse(params['subject']![0]); + String subjectName = params['subjectName']![0]; + return ReportSubjectTeacher(examId: examId, classId: classId, subject: subject, subjectName: subjectName); + }, + ); + + static final _userMinePageHandler = Handler( + handlerFunc: (BuildContext? context, Map> params) => TheMine(), + ); + + static final _reportPersonalSubjectPageHandler = Handler( + handlerFunc: (BuildContext? context, Map> params) { + int examId = int.parse(params['examId']![0]); + int userId = int.parse(params['userId']![0]); + int subject = int.parse(params['subject']![0]); + String subjectName = params['subjectName']![0]; + String studentName = params['studentName']![0]; + + return ReportPersonalSubject( + examId: examId, + userId: userId, + subject: subject, + studentName: studentName, + subjectName: subjectName, + ); + }, + ); + + // 开始阅卷页面 + // static final _doMarkingPapers = Handler(handlerFunc: (BuildContext? context, Map> params) => MarkingPapers()); + + //未授权页面处理器 + // static final permissionDeniedHandler = + // Handler(handlerFunc: (BuildContext context, Map params) { + // return PermissionDenied(); + // }); + + static void _defineRoutes() { + router.define(root, handler: _mainPageHandler, transitionType: TransitionType.material); + router.define(startUpPath, handler: _startUpPageHandler, transitionType: TransitionType.material); + router.define(loginPath, handler: _loginPageHandler); + router.define(markingProgressPath, handler: _progressPageHandler, transitionType: TransitionType.material); + router.define(markingReviewPath, handler: _reviewPageHandler, transitionType: TransitionType.material); + router.define(markingJobReviewPath, handler: _reviewJobPageHandler, transitionType: TransitionType.material); + router.define(markingDoPath, handler: _markingDoPageHandler, transitionType: TransitionType.material); + router.define(markingHomeworkDoPath, + handler: _markingHomeworkDoPageHandler, transitionType: TransitionType.material); + router.define(markingReviewAbnormalPath, + handler: _reviewAbnormalPageHandler, transitionType: TransitionType.material); + router.define(agreementPath, handler: _agreementPageHandler, transitionType: TransitionType.material); + router.define(ohterMainPagePath, handler: _ohterMainPageHandler, transitionType: TransitionType.material); + + router.define(reportClassTeacherPath, + handler: _reportClassTeacherPageHandler, transitionType: TransitionType.material); + router.define(reportSubjectTeacherPath, + handler: _reportSubjectTeacherPageHandler, transitionType: TransitionType.material); + router.define(reportPersonalSubjectPath, + handler: _reportPersonalSubjectPageHandler, transitionType: TransitionType.material); + router.define(userMinePath, handler: _userMinePageHandler, transitionType: TransitionType.material); + +// getTransition() + + router.notFoundHandler = Handler(handlerFunc: (BuildContext? context, Map> params) { + toPrint(val: "ROUTE WAS NOT FOUND !!!"); + return; + }); + } +} diff --git a/marking_app/lib/utils/anti_shake_throttling.dart b/marking_app/lib/utils/anti_shake_throttling.dart new file mode 100644 index 0000000..df8a90d --- /dev/null +++ b/marking_app/lib/utils/anti_shake_throttling.dart @@ -0,0 +1,51 @@ +// debounce.dart + +import 'dart:async'; + +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/material.dart'; + +/// 函数防抖 +/// +/// [func]: 要执行的方法 +/// [delay]: 要迟延的时长 +VoidCallback debounce(Function func, [Duration delay = const Duration(milliseconds: 2000)]) { + Timer? timer; + VoidCallback target = () { + if (timer?.isActive ?? false) { + timer?.cancel(); + } + timer = Timer(delay, () { + func.call(); + }); + }; + return target; +} + +/// 函数节流 +/// +/// [func]: 要执行的方法 +VoidCallback throttle(Future Function() func) { + bool enable = true; + VoidCallback target = () { + if (enable == true) { + enable = false; + func().whenComplete(() { + enable = true; + }); + } + }; + return target; +} + +/// 函数节流 +/// +/// [func]: 要执行的方法 +bool easyThrottle(String tagId,EasyThrottleCallback onExecute, {Duration duration = const Duration(milliseconds: 300),EasyThrottleCallback? onAfter}) { + return EasyThrottle.throttle( + tagId, // <-- An ID for this particular throttler + duration, // <-- The throttle duration + onExecute, // <-- The target method + onAfter:onAfter // <-- Optional callback, called after the duration has passed + ); +} diff --git a/marking_app/lib/utils/app_upgrade/DownloadApk.dart b/marking_app/lib/utils/app_upgrade/DownloadApk.dart new file mode 100644 index 0000000..2aeda91 --- /dev/null +++ b/marking_app/lib/utils/app_upgrade/DownloadApk.dart @@ -0,0 +1,126 @@ +/* + * @Descripttion: 下载APK + * @version: DownloadApk + * @Author: wy + * @Date: 2020-07-30 15:54:40 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-02 15:12:21 + */ +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:install_plugin/install_plugin.dart'; +import 'package:marking_app/provider/upgrade_provider.dart'; +// import 'package:install_plugin_v2/install_plugin_v2.dart'; +import 'package:marking_app/utils/app_upgrade/model/UpdateAppEvent.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:path_provider/path_provider.dart'; + +class DownloadApk { + /// 下载安卓更新包 + static Future _downloadAndroid(context, UpdateAppEvent event, WidgetRef ref) async { + /// 创建存储文件 + Directory? storageDir = await getExternalStorageDirectory(); + final storagePath = storageDir?.path ?? '/'; + String version = event.version.replaceAll(".", "-"); + File file = new File('$storagePath/${event.appName}v$version.apk'); + if (await file.exists()) await file.delete(); + if (!file.existsSync()) file.createSync(); + try { + /// 发起下载请求 + Response response = await Dio().get( + event.link, + onReceiveProgress: (num received, num total) { + showDownloadProgress(context, received, total, ref); + }, + options: Options( + responseType: ResponseType.bytes, + followRedirects: false, + ), + ); + file.writeAsBytesSync(response.data); + return file; + } catch (e) { + print(e); + // toPrint(val: e); + } + } + + // 安装apk + static Future installApk(context, UpdateAppEvent event, WidgetRef ref) async { + File? _apkFile = await _downloadAndroid(context, event, ref); + if (_apkFile == null) return false; + String _apkFilePath = _apkFile.path; + if (_apkFilePath.isEmpty) { + debugPrint('make sure the apk file is set'); + return false; + } + + Map statuses = await [Permission.storage].request(); + + if (statuses[Permission.storage]!.isGranted) { + // String? result = await InstallPlugin.installApk(_apkFilePath, appId: event.packageName).catchError((error) { + // debugPrint('install apk error: $error'); + // }); + // debugPrint('install apk $result'); + // ToastUtils.getFluttertoast(context: context, msg: 'install apk $result'); + try { + final result = await InstallPlugin.installApk(_apkFilePath, appId: event.packageName); + print('这是是执行安装的程序:' + result.runtimeType.toString()); + if (result['isSuccess'] ?? false) { + ToastUtils.showSuccess('install apk $result'); + RestartWidget.restartApp(context); // 安装成功 重启APP + return true; + } + } catch (e) {} + } else { + debugPrint('Permission request fail!'); + } + + return false; + } + + /// 展示下载进度 + static void showDownloadProgress(context, num received, num total, WidgetRef ref) { + if (total != -1) { + double progress = double.parse((received / total).toStringAsFixed(2)); + debugPrint('下载进度$progress'); + ref.read(upgradeProvider.notifier).setVal(progress); + } + } +} + +class RestartWidget extends StatefulWidget { + final Widget child; + + RestartWidget({required this.child}); + + static restartApp(BuildContext context) { + final _RestartWidgetState? state = context.findAncestorStateOfType<_RestartWidgetState>(); + state?.restartApp(); + } + + @override + _RestartWidgetState createState() => _RestartWidgetState(); +} + +class _RestartWidgetState extends State { + Key key = UniqueKey(); + + void restartApp() { + setState(() { + key = UniqueKey(); + }); + } + + @override + Widget build(BuildContext context) { + return KeyedSubtree( + key: key, + child: widget.child, + ); + } +} diff --git a/marking_app/lib/utils/app_upgrade/UpdateDialog.dart b/marking_app/lib/utils/app_upgrade/UpdateDialog.dart new file mode 100644 index 0000000..f16a0f9 --- /dev/null +++ b/marking_app/lib/utils/app_upgrade/UpdateDialog.dart @@ -0,0 +1,193 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-29 10:11:54 + * @FilePath: \marking_app\lib\utils\app_upgrade\UpdateDialog.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +/* + * @Descripttion: 版本更新提示弹窗 + * @version: + * @Author: wy + * @Date: 2020-07-30 14:03:28 + * @LastEditors: Please set LastEditors + * @LastEditTime: 2021-01-12 15:08:43 + */ +import 'dart:async'; +import 'package:clipboard/clipboard.dart'; +import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:marking_app/provider/upgrade_provider.dart'; +import 'package:marking_app/utils/app_upgrade/DownloadApk.dart'; +import 'package:marking_app/utils/app_upgrade/UpgradePermission.dart'; +import 'package:marking_app/utils/app_upgrade/model/UpdateAppEvent.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/my_text.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class UpdateDialog extends Dialog { + final UpdateAppEvent updateAppEvent; + final String deviceInfo; + + const UpdateDialog({required this.updateAppEvent, required this.deviceInfo}); + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + width: 319.w, + height: 440.h, + padding: EdgeInsets.symmetric(vertical: 8.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(18.sp), + image: const DecorationImage( + alignment: Alignment.topCenter, + image: AssetImage("assets/images/upgrade_dialog_bgc.png"), + fit: BoxFit.fitWidth, + ), + ), + child: Column( + children: [ + Expanded( + child: ListView( + physics: const BouncingScrollPhysics(), + padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 0), + children: [ + Container( + alignment: Alignment.center, + margin: EdgeInsets.only(top: 128.h, bottom: 10.h), + child: quickText( + updateAppEvent.title, + size: 18.sp, + fontWeight: FontWeight.w600, + color: const Color.fromRGBO(58, 90, 159, 1), + ), + ), + HtmlWidget( + updateAppEvent.description, + customStylesBuilder: (element) { + return {'color': '#666666', 'font-weight': 'normal', 'text-decoration': 'none'}; + }, + onLoadingBuilder: (context, element, loadingProgress) => const CircularProgressIndicator(), + renderMode: RenderMode.column, + textStyle: TextStyle(fontSize: 14.sp, color: Colors.black87), + ) + ], + ), + ), + const DownloadProgress(), + DownloadButton(updateAppEvent, deviceInfo: deviceInfo), + ], + ), + ), + ); + } + + // 调起 + static showUpdateDialog(BuildContext context, UpdateAppEvent updateAppEvent) { + return showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return WillPopScope( + onWillPop: () async => false, + child: UpdateDialog(updateAppEvent: updateAppEvent, deviceInfo: updateAppEvent.deviceInfo), + ); + }, + ); + } +} + +class DownloadProgress extends ConsumerWidget { + const DownloadProgress({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(upgradeProvider); + var str = (count * 100).toString().split('.')[0]; + if (count <= 0) return Container(); + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LinearPercentIndicator( + alignment: MainAxisAlignment.center, + width: 245.w, + animation: false, + lineHeight: 20.0.h, + percent: count, + center: quickText( + "$str%", + size: 14.sp, + align: TextAlign.center, + color: Colors.white, + ), + linearStrokeCap: LinearStrokeCap.roundAll, + progressColor: Theme.of(context).primaryColor, + ), + Container( + height: 6.h, + ), + quickText('更新中...', size: 12.sp, fontWeight: FontWeight.w500, color: const Color.fromRGBO(90, 90, 90, 1)) + ], + ); + } +} + +// 下载按钮 +class DownloadButton extends ConsumerWidget { + final UpdateAppEvent updateAppEvent; + final String deviceInfo; + const DownloadButton(this.updateAppEvent, {required this.deviceInfo, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(upgradeProvider); + if (count > 0) return Container(); + + return Container( + height: 38.h, + width: 245.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(42.h)), + gradient: const LinearGradient(colors: [ + Color.fromRGBO(24, 70, 238, 1), + Color.fromRGBO(72, 122, 255, 1), + ]), + ), + child: MaterialButton( + onPressed: () => easyThrottle('DownloadButton_App_Upgrade', () async { + if (deviceInfo == "android" && updateAppEvent.equipment == Equipment.android) { + bool flag = await UpgradePermission(updateAppEvent.deviceInfo).checkPermission(); + if (flag) { + flag = await DownloadApk.installApk(context, updateAppEvent, ref); + + if (!flag) { + print('执行到了重置更新按钮的地方....'); + ref.read(upgradeProvider.notifier).clean(); // 更新失败重置 更新按钮 + } else { + print('更新成功重新打开APP..............'); + RestartWidget.restartApp(context); // 安装成功 重启APP + } + return; + } + await FlutterClipboard.copy(updateAppEvent.link); + setTimeOut(1000, () => ToastUtils.showInfo('下载链接已经复制到设备,可前往浏览器下载安装')); + } else if (deviceInfo == "ios" && updateAppEvent.equipment == Equipment.ios) { + try { + await launchUrlString(updateAppEvent.link); + } catch (e) { + toPrint(val: '进来更新报错$e'); + } + } + }), + child: quickText('立即体验', size: 16.sp, color: Colors.white, fontWeight: FontWeight.w500), + ), + ); + } +} diff --git a/marking_app/lib/utils/app_upgrade/UpgradePermission.dart b/marking_app/lib/utils/app_upgrade/UpgradePermission.dart new file mode 100644 index 0000000..1337eca --- /dev/null +++ b/marking_app/lib/utils/app_upgrade/UpgradePermission.dart @@ -0,0 +1,47 @@ +/* + * @Descripttion: 获取本地权限 + * @version: UpgradePermission + * @Author: wy + * @Date: 2020-07-30 15:41:39 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-01 14:08:57 + */ +import 'package:marking_app/utils/index.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class UpgradePermission { + final String _flatform; + const UpgradePermission(this._flatform); + + /// 检查是否有权限,用于安卓 + Future checkPermission() async { + // if (_flatform == 'android') { + + // return await Permission.storage.request().isGranted; + // } else { + // return true; + // } + + if (_flatform == 'android') { + final status = await Permission.storage.status; + if (status != PermissionStatus.granted) { + final result = await Permission.storage.request(); + if (result == PermissionStatus.granted) { + return true; + } + if (status == PermissionStatus.denied) { + ToastUtils.showError('拒绝了保存安装权限'); + } + + if (status == PermissionStatus.permanentlyDenied) { + ToastUtils.showError('用户永久拒绝保存安装权限,请前往设置权限'); + } + } else { + return true; + } + } else { + return true; + } + return false; + } +} diff --git a/marking_app/lib/utils/app_upgrade/model/UpdateAppEvent.dart b/marking_app/lib/utils/app_upgrade/model/UpdateAppEvent.dart new file mode 100644 index 0000000..2f53d7b --- /dev/null +++ b/marking_app/lib/utils/app_upgrade/model/UpdateAppEvent.dart @@ -0,0 +1,86 @@ +/* + * @Descripttion: + * @version: + * @Author: wy + * @Date: 2020-07-30 14:10:44 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-28 18:20:11 + */ + +import 'dart:convert'; + +class UpdateAppEvent { + final String version; + final String title; + final String description; + final String link; + final bool upgrade; + final String deviceInfo; + final String appName; + final String packageName; + final Equipment equipment; + num? v; + num? lv; + + UpdateAppEvent({ + required this.version, + required this.title, + required this.description, + required this.link, + required this.upgrade, + required this.deviceInfo, + required this.appName, + required this.packageName, + this.v, + this.lv, + int type = 0, + }) : equipment = Equipment.values[type]; + + factory UpdateAppEvent.fromJson(Map json, String localVersion, + String deviceInfo, String appName, String packageName, + {String keyStr = "version", String typeName = "packageNameType"}) { + String version = json[keyStr]; // 版本号 + String? descriptionStr = json["description"]; + + String newDescription = '系统有更新,请立即下载'; + bool upgrade = false; + num? v; + num? lv; + // 版本号 + if (version != '') { + v = num.parse(version.replaceAll(".", "")); + lv = num.parse(localVersion.replaceAll(".", "")); + + // if (lv < v) upgrade = true; + //当前版本不等于线上版本更新为线上版本 + if (lv != v) upgrade = true; + } + + // 升级说明 + if (descriptionStr != null && descriptionStr.isNotEmpty) { + newDescription = descriptionStr; + } + + return UpdateAppEvent( + version: version, // 版本号 + title: json["title"] ?? "发现新版本", // title + description: newDescription, //说明 + link: json["downloadPath"], // 链接地址 + upgrade: upgrade, + deviceInfo: deviceInfo, + appName: appName, + packageName: packageName, + type: json[typeName] ?? 0, + v: v, + lv: lv, + ); + } + + @override + String toString() { + return "UpdateAppEvent { version: $version, title: $title, description: $description, link: $link}"; + } +} + +// 设备枚举 +enum Equipment { other, android, ios } diff --git a/marking_app/lib/utils/app_upgrade/provide/ScheduleProvide.dart b/marking_app/lib/utils/app_upgrade/provide/ScheduleProvide.dart new file mode 100644 index 0000000..7226a33 --- /dev/null +++ b/marking_app/lib/utils/app_upgrade/provide/ScheduleProvide.dart @@ -0,0 +1,22 @@ +/* + * @Descripttion: + * @version: + * @Author: wy + * @Date: 2020-07-31 15:34:59 + * @LastEditors: sueRimn + * @LastEditTime: 2020-07-31 15:42:06 + */ +import 'package:flutter/material.dart'; + +class ScheduleProvide with ChangeNotifier { + double progress = 0.0; + + setProgress(double v) { + progress = v; + notifyListeners(); + } + + clean(double v) { + progress = v; + } +} diff --git a/marking_app/lib/utils/colorUtils.dart b/marking_app/lib/utils/colorUtils.dart new file mode 100644 index 0000000..adaef22 --- /dev/null +++ b/marking_app/lib/utils/colorUtils.dart @@ -0,0 +1,53 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-06 14:52:21 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-06 14:57:33 + * @FilePath: \marking_app\lib\utils\colorUtils.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:ui'; +import 'dart:ui'; +import 'package:flutter/material.dart'; + +//调用的时候需要把hex改一下,比如#223344 needs change to 0xFF223344 +//即把#换成0xFF即可 + +MaterialColor createMaterialColor(Color color) { + List strengths = [.05]; + Map swatch = {}; + final int r = color.red, g = color.green, b = color.blue; + + for (int i = 1; i < 10; i++) { + strengths.add(0.1 * i); + } + for (var strength in strengths) { + final double ds = 0.5 - strength; + swatch[(strength * 1000).round()] = Color.fromRGBO( + r + ((ds < 0 ? r : (255 - r)) * ds).round(), + g + ((ds < 0 ? g : (255 - g)) * ds).round(), + b + ((ds < 0 ? b : (255 - b)) * ds).round(), + 1, + ); + } + return MaterialColor(color.value, swatch); +} + +class CommonColors { + static const Color defaultColor = Color.fromRGBO(45, 56, 76, 1); + + static Map map = Map.from({ + 'text_black': ColorItem( + title: 'text_black', color: const Color.fromRGBO(45, 56, 76, 1)), + }); + + ColorItem getColorItem(String key, {Color color = defaultColor}) { + return map[key] ?? ColorItem(color: color,title: 'default_color'); + } +} + +class ColorItem { + String title; + Color color; + ColorItem({required this.color, required this.title}); +} diff --git a/marking_app/lib/utils/common_utils.dart b/marking_app/lib/utils/common_utils.dart new file mode 100644 index 0000000..e7bb2a8 --- /dev/null +++ b/marking_app/lib/utils/common_utils.dart @@ -0,0 +1,21 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 16:59:53 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-13 17:00:56 + * @FilePath: \marking_app\lib\utils\common_utils.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:convert'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; + +class CommonUtils { + // md5 加密 + static String generateMD5(String data) { + var content = new Utf8Encoder().convert(data); + var digest = md5.convert(content); + // 这里其实就是 digest.toString() + return hex.encode(digest.bytes); + } +} diff --git a/marking_app/lib/utils/const_text.dart b/marking_app/lib/utils/const_text.dart new file mode 100644 index 0000000..31a778b --- /dev/null +++ b/marking_app/lib/utils/const_text.dart @@ -0,0 +1,36 @@ +/*协议*/ +// ignore_for_file: constant_identifier_names +// ignore: camel_case_types +enum AGREEMENT_KEY { PRIVACY_GREEMENT, USER_AGREEMENT } + +// ignore: non_constant_identifier_names +final Map AGREEMENT_MAP = { + AGREEMENT_KEY.PRIVACY_GREEMENT: AgreementClass( + title: '隐私协议', + richText: '''

本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、 + 更有个性化的服务,本软件会按照本隐私权政策的规定使用和披露您的个人信息。但本软件将以高度的勤勉、审慎义务对待这些信息。 + 除本隐私权政策另有规定外,在未征得您事先许可的情况下,本软件不会将这些信息对外披露或向第三方提供。本软件会不时更新本隐私权政策。 + 您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分。 +

1.适用范围

a)在您使用本软件网络服务,本软件自动接收并记录的您的手机上的信息,包括但不限于您的健康数据、使用的语言、 + 访问日期和时间、软硬件特征信息及您需求的网页记录等数据;

2.信息的使用

a)在获得您的数据之后,本软件会将其上传至服务器, + 以生成您的排行榜数据,以便您能够更好地使用服务。

3.信息披露

a)本软件不会将您的信息披露给不受信任的第三方。 +

b)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露;

c)如您出现违反中国有关法律、 + 法规或者相关规则的情况,需要向第三方披露;

4.信息存储和交换

本软件收集的有关您的信息和资料将保存在本软件及(或)其关联公司的服务器上, + 这些信息和资料可能传送至您所在国家、地区或本软件收集信息和资料所在地的境外并在境外被访问、存储和展示。 +

5.信息安全

a)在使用本软件网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或者邮政地址。 + 请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,请您立即联络本软件客服,以便本软件采取相应措施。 +


''', + ), + AGREEMENT_KEY.USER_AGREEMENT: AgreementClass( + title: '用户协议', + richText: + '''

用户在使用技术开发方(即,以下统称“技术开发方”)提供的各项服务之前,应仔细阅读本《用户协议》(以下简称“本协议”)。用户一旦登录或使用技术开发方的服务,即视为用户已了解并明示同意本协议各项内容,本协议立即在用户与本技术开发方之间产生法律效力。用户登录、使用本产品服务的全部活动将受到本协议的约束并承担相应的责任和义务。如用户不同意本协议任何内容的,请用户立即停止使用技术开发方所提供的全部服务。 根据《中华人民共和国网络安全法》、《电信和互联网用户个人信息保护规定》及相关法律法规的规定,同时依据技术开发方与其合作伙伴之间的相关协议,用户必须已明示授权技术开发方合作伙伴(以下简称“合作伙伴”)、在此明示授权并委托技术开发方及其关联公司通过官方或相关实名认证平台、信用信息平台(包括但不限于:征信机构、银行信用信息平台、网络借贷平台、消费金融平台、第三方支付平台、公积金平台、投资理财平台等)及其它相关平台查询、验证、存储用户的个人信用信息,并输出给合作伙伴对用户的个人信用进行评估与参考使用。用户理解并同意,具体的授权查询、验证、存储及输出的内容以合作伙伴要求查询、验证、存储、输出以及技术开发方及其关联公司实际查询、验证、存储、输出的信息为准。技术开发方及其关联公司会在授权范围内对相关个人信息予以处理(包括但不限于为保护用户个人信息而加密处理、掩码处理等一切为实现相关协议目的而进行的所有必要处理)并仅提供给合作伙伴使用,但用户与合作伙伴之间因授权的有效性、授权内容、授权范围、授权期限等发生的争议纠纷与技术开发方无关。如用户对合作伙伴的上述授权事项有任何异议或争议的,应立即停止使用技术开发方所提供的全部服务。用户使用本服务的,即表明用户已明示对合作伙伴、技术开发方及其关联公司授权查询、验证、处理、存储、在约定范围内使用其个人信息,并对授权的效力、查询验证的内容、查询验证平台、处理方式、使用范围等相关事项无任何事实或法律上的异议或争议。 鉴于用户须授权合作伙伴并由该合作伙伴告知本服务后方能进入、登录并使用本服务,用户登录或使用本服务时起即视为用户与技术开发方的合作伙伴之间已存在合法的、充分的、必要的、不可撤销的授权,且用户已清楚知晓其授权提供相关信息可能对其造成的相关不利影响,如负面的信用评价等。为保护用户个人信息,技术开发方会采取合理措施对用户信息进行严格保密,同时督促并要求该特定合作伙伴进行严格保密。未经用户授权,技术开发方及其关联公司不会将用户信息提供给任何其他方。

一、协议主体

本协议是用户与技术开发方关于用户使用本服务所订立的协议。

二、关于本服务


    1. 本服务内容是指技术开发方通过本应用程序向其合作伙伴及用户提供的相关服务(简称“本服务”)。


    1. 对用户使用的本服务,技术开发方会不断丰富用户使用本服务的终端、形式等。


    1. 许可的范围:
      -(1)技术开发方授予用户一项个人的、不可转让及非排他性的许可,以使用本应用程序。
      -(2)本条及本协议其他条款未明示授权的其他一切知识产权权利仍由技术开发方保留。技术开发方如果未行使前述任何权利,并不构成对该权利的放弃。

三、账号登录

为使用本应用程序用户可能需要输入个人信息进行登录与使用。 用户清楚知晓,其向技术开发方提供的账户仅限本人使用,否则,用户可能会对用户或他人造成侵权。用户承诺并同意,使用同一设备、同一身份证号或账号使用本协议项下服务的,均视为用户本人的行为。用户应妥善保管自身设备、身份证件及账号密码信息,审慎交由他人使用或使用他人设备、身份证件或账户信息,如用户违反本约定给用户或他人造成损失的,技术开发方不应也不会承担任何法律责任。用户若因此给技术开发方造成损失的,应承担技术开发方所遭受的全部损失。

四、应用程序的使用

如果用户从非技术开发方合作伙伴的应用程序或非技术开发方合作伙伴处获取本应用程序或与本应用程序名称相同的安装程序,技术开发方无法保证该应用程序能够正常使用,并对因此给用户造成的损失不予负责。

五、应用程序的更新

  1. 为了增进用户体验、完善服务内容,技术开发方将不断努力为用户时时提供应用程序更新(这些更新可能会采取应用程序替换、修改、功能强化、版本升级等形式)。

  2. 为了改善用户体验,并保证服务的安全性和功能的一致性,技术开发方有权不经向用户特别通知而对应用程序进行更新,或者对应用程序的部分功能效果进行改变或限制。

六、用户个人信息保护


    1. 保护用户个人信息是技术开发方的一项基本原则,技术开发方将会采取合理的措施保护用户的个人信息。除法律法规规定及用户授权的情形外,未经用户许可技术开发方不会向任何第三方公开、透露用户个人信息。


    1. 用户在登录账号或使用本服务的过程中,可能需要填写一些必要的信息。若用户填写的信息不真实或不完整,则可能无法正常使用本服务。


    1. 一般情况下,用户可随时浏览、修改自己提交的信息,但出于安全性和身份识别的考虑,用户可能无法修改注册时提供的初始注册信息及其他验证信息。


    1. 技术开发方将运用各种安全技术和程序建立完善的管理制度来保护用户的个人信息,以免遭受未经授权的访问、使用或披露。

七、授权事项及行为规范


    1. 授权事项
      -(1)用户充分理解并同意:用户在使用本服务时,可能需要使用用户终端设备的相关权限、接口及相关设备信息等才能实现相应的功能。
      -(2)用户可以选择不向技术开发方提供用户的某些信息,但因此可能会导致相关服务功能无法实现。
      -(3)为实现本协议目的为合作伙伴及用户提供更加优质、安全的服务,用户同意并明示授权技术开发方及其关联公司对用户的相关个人信息进行查询、验证、存储、处理并在约定范围内使用(提供给用户已授权的合作伙伴对用户进行信用评估及参考使用)。技术开发方及其关联公司对用户的个人信息进行严格保密。本协议项下的授权为不可撤销的授权。
      -(4)用户知晓并明示授权同意技术开发方及其关联公司依据相关法律法规的规定,受合作伙伴委托向第三方征信机构或数据机构等合法查询、验证、审核用户信息,上述信息包括但不限于个人基本信息、特征信息(包括但不限于用户的法院失信信息、网络失信信息、是否为羊毛党信息、是否曾使用通讯小号及可疑设备信息等)、关联信息(即用户的身份证信息、手机号、手机设备及银行卡之间的关联关系,以判断用户信息是否有异常,该关联关系不涉及具体的个人敏感信息)、借贷交易信息、网络投资理财信息、公积金信息、公用事业信息、央行征信报告、个人网络数据信息等合作伙伴需要验证或参考使用的相关用户信息。技术开发方具体查询、验证及审核的信息最终以合作伙伴需要验证、需要参考使用及实际验证与使用的信息为准。技术开发方对所获取的信息,仅在用户与合作伙伴之间有关个人信用信息评估等合作伙伴业务相关工作中使用。技术开发方及其关联公司将对所获取的信息向该合作伙伴进行提供,除此之外,未经用户授权,技术开发方及其关联公司不得也不会向其他机构或个人泄露、披露或提供用户的信息


    1. 用户禁止行为除非法律允许或技术开发方书面许可,用户不得从事下列行为:
      -(1)删除本应用程序及其副本上关于著作权的信息。
      -(2)对本应用程序进行反向工程、反向汇编、反向编译,或者以其他方式尝试发现本应用程序的源代码。
      -(3)对技术开发方拥有知识产权的内容进行使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版等。
      -(4)通过修改或伪造应用程序运行中的指令、数据,增加、删减、变动应用程序的功能或运行效果,或者将用于上述用途的应用程序、方法进行运营或向公众传播,无论这些行为是否为商业目的。
      -(5)自行、授权他人或利用第三方应用程序对本应用程序及其组件、模块、数据等进行干扰。
      -(6)其他未经技术开发方明示授权的行为。

  1. 对自己行为负责用户充分了解并同意,用户必须为自己对合作伙伴的授权(包括但不限于授权方式、授权内容及授权期限等)以及其账户下的相关行为负责。技术开发方会督促合作伙伴获取用户的授权后方能对相关信息进行查询、验证或使用并要求合作伙伴对用户的信息进行严格保密,但用户应对使用本服务时接触到的内容自行加以判断,如对授权相关事项及信息安全有任何异议或争议的,应立即停止使用本服务。技术开发方无法且不会对用户与合作伙伴之间的任何争议或纠纷而承担责任,用户未按约定要求立即停止使用本服务的,技术开发方对非因技术开发方的原因而产生的任何风险或损失将不承担任何责任。

八、知识产权声明


    1. 技术开发方是本应用程序的知识产权权利人。本应用程序的著作权、商标权、专利权、商业秘密等知识产权,以及与本应用程序相关的所有信息内容(包括但不限于文字、图片、音频、视频、图表、界面设计、版面框架、有关数据或电子文档等)均受中华人民共和国法律法规和相应的国际条约保护,技术开发方依法享有上述相关知识产权,但相关权利人依照法律规定应享有的权利除外。


    1. 未经技术开发方或相关权利人书面同意,用户不得为任何商业或非商业目的自行或许可任何第三方实施、利用、转让上述知识产权。

九、终端安全责任


    1. 用户理解并同意,本应用程序或本服务同大多数互联网应用程序、服务一样,可能会受多种因素影响(包括但不限于用户原因、网络服务质量、社会环境等);也可能会受各种安全问题的侵扰(包括但不限于他人非法利用用户资料,进行现实中的骚扰;用户下载安装的其他应用程序或访问的其他网站中可能含有病毒、木马程序或其他恶意程序,威胁用户终端的信息和数据的安全,继而影响本应用程序、本服务的正常使用等)。因此,用户应加强信息安全及个人信息的保护意识,注意密码保护,以免遭受损失。出现上述情况时技术开发方将努力在第一时间与相关方配合,及时进行修复,但是由此给用户造成的损失技术开发方在法律允许的范围内免责。


    1. 用户不得制作、发布、使用、传播用于窃取技术开发方账号及他人个人信息、财产的恶意程序。


    1. 维护应用程序安全与正常使用是技术开发方和用户的共同责任,技术开发方将按照行业标准合理审慎地采取必要技术措施保护用户的终端信息和数据安全。


    1. 在法律允许的范围内,技术开发方对以下情形导致的服务中断或受阻不承担责任:
      -(1)受到计算机病毒、木马或其他恶意程序、黑客攻击的破坏。
      -(2)用户或技术开发方的电脑软件、系统、硬件和通信线路出现故障。
      -(3)用户操作不当。
      -(4)用户通过非技术开发方授权的方式使用本服务。
      -(5)其他技术开发方无法控制或合理预见的情形。

十、不可抗力及合理免责

“不可抗力”是指在本协议签订后发生的、受影响一方无法预见、无法避免并无法克服的客观情况。此等事件包括但不限于水灾、火灾、旱灾、台风、地震及其它自然灾害、罢工、骚动、暴乱及战争以及政府部门的作为或不作为、法律法规或政策调整、数据来源变更(包括但不限于其服务内容及形式的变更)、国内数据渠道瘫痪、黑客攻击、计算机病毒侵入、新型病毒爆发、因电信运营商问题导致网络中断服务器不可访问、停电、系统故障、传输线路、通信故障等技术开发方无法控制的因素。因受不可抗力影响而不能履行或不能完全履行本协议的不视为违约,不应承担相应违约责任 。

十一、其他


    1. 用户使用本应用程序或本服务即视为用户已阅读并同意受本协议的约束。技术开发方有权在必要时修改本协议条款。用户可以在本应用程序、本服务的最新版本中查阅相关协议条款。本协议条款变更后,如果用户继续登录、使用本应用程序、本服务,即视为用户已接受修改后的协议。如果用户不接受修改后的协议,应当停止使用本应用程序。


    1. 本协议的成立、生效、履行、解释及纠纷解决,适用中华人民共和国大陆地区法律(不包括冲突法)。


    1. 若用户和技术开发方之间发生任何纠纷或争议,首先应友好协商解决;协商不成的,用户同意将纠纷或争议提交被告方所在地人民法院管辖。


    1. 本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。5. 本协议条款无论因何种原因部分无效或不可执行,其余条款仍有效,对双方具有约束力。




''', + ) +}; + +/* TYPE */ +class AgreementClass { + String title; + String richText; + AgreementClass({required this.title, required this.richText}); +} diff --git a/marking_app/lib/utils/dashed_line.dart b/marking_app/lib/utils/dashed_line.dart new file mode 100644 index 0000000..51c1f81 --- /dev/null +++ b/marking_app/lib/utils/dashed_line.dart @@ -0,0 +1,35 @@ + +import 'package:flutter/material.dart'; + +class DashedLine extends StatelessWidget { + final Axis axis; + final double dashedWidth; + final double dashedHeight; + final int count; + final Color color; + + const DashedLine({ + required this.axis, + required this.dashedWidth, + required this.dashedHeight, + this.color = Colors.red, + this.count = 10 + }) ; + + @override + Widget build(BuildContext context) { + return Flex( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + direction: axis, + children: List.generate(count, ( _) { + return SizedBox( + width: dashedWidth, + height: dashedHeight, + child: DecoratedBox( + decoration: BoxDecoration(color: color,) + ), + ); + }), + ); + } +} diff --git a/marking_app/lib/utils/drawer_util/auto_drawer.dart b/marking_app/lib/utils/drawer_util/auto_drawer.dart new file mode 100644 index 0000000..09793fd --- /dev/null +++ b/marking_app/lib/utils/drawer_util/auto_drawer.dart @@ -0,0 +1,70 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + + class AutoDrawer extends StatefulWidget { + + final double elevation; + final Widget child; + final String? semanticLabel; + //定义显示宽度百分百比 默认60% + final double widthPercent; + final DrawerCallback? callback; + const AutoDrawer({ + Key? key, + this.elevation = 16.0, + required this.child, + this.semanticLabel, + this.widthPercent = 0.6, + this.callback, + }) :super(key: key); + @override + _AutoDrawerState createState() => _AutoDrawerState(); + } + + class _AutoDrawerState extends State { + + @override + void initState() { + if(widget.callback!=null){ + //打开侧边栏 触发 + widget.callback!(true); + } + super.initState(); + } + @override + void dispose() { + if(widget.callback!=null){ + // 关闭侧边栏触发 + widget.callback!(false); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterialLocalizations(context)); + String? label = widget.semanticLabel; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + label = widget.semanticLabel; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + label = widget.semanticLabel ?? MaterialLocalizations.of(context)?.drawerLabel; + } + final double _width = MediaQuery.of(context).size.width * widget.widthPercent; + return Semantics( + scopesRoute: true, + namesRoute: true, + explicitChildNodes: true, + label: label, + child: ConstrainedBox( + constraints: BoxConstraints.expand(width: _width), + child: Material( + elevation: widget.elevation, + child: widget.child, + ), + ), + ); + } + } \ No newline at end of file diff --git a/marking_app/lib/utils/easy_refresh/MyEasyRefresh.dart b/marking_app/lib/utils/easy_refresh/MyEasyRefresh.dart new file mode 100644 index 0000000..57b3100 --- /dev/null +++ b/marking_app/lib/utils/easy_refresh/MyEasyRefresh.dart @@ -0,0 +1,245 @@ +/* + * @Descripttion: 上拉刷新下拉加载页面封装 + * @version: + * @Author: wy + * @Date: 2020-08-25 14:41:31 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 09:58:56 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:marking_app/utils/easy_refresh/MyEasyRefreshService.dart'; +import 'package:marking_app/utils/easy_refresh/MyEmptyWidget.dart'; + +/* + * 普通使用 + */ + +// MyEasyRefresh( +// true, +// _controller, +// getItem, +// url: CommonUtil.teachers, +// param: { +// "studentId": _currentchild.userId, +// } + +class MyEasyRefresh extends StatefulWidget { + final EasyRefreshController _controller; + final bool firstRefresh; + final bool isBase; + final String url; + final Map? param; + final Header? header; + final Footer? footer; + final Function? getData; + final List? data; + final Function itemWidget; + final bool operation; + final String? idkey; + final BuildContext? myContext; + final bool widthToken; + final Function? listReturn; + final String? emptyImagPath; + final String? emptityTextVal; + Function? callFunc; + MyEasyRefresh( + this.isBase, + this._controller, + this.itemWidget, { + required Key key, + required this.url, + this.data, + this.firstRefresh = true, + this.getData, + this.header, + this.footer, + + this.param, + this.operation = false, + this.idkey, + this.myContext, + this.widthToken = false, + this.listReturn, + this.callFunc, + this.emptyImagPath, + this.emptityTextVal, + }) : super(key: key); + + @override + _MyEasyRefreshState createState() => _MyEasyRefreshState(); +} + +class _MyEasyRefreshState extends State { + final Map _resData = { + "page": { + "currentPage": 1, + "pageSize": 10, + "isLastPage": false, + }, + "data": [] + }; + + Future _getData(context, bool isRefresh) async { + Map _page = _resData["page"]; + List _list = _resData["data"]; + int newPageNumber; + if (isRefresh) + newPageNumber = 1; + else + newPageNumber = _page["currentPage"] + 1; + + Map _paramMap = Map(); + Map? param = widget.param; + if (param!=null && param.isNotEmpty) { + _paramMap.addAll(param); + } + + _paramMap["pageNum"] = newPageNumber; + _paramMap["pageSize"] = _page["pageSize"]; + + MyEasyRefreshService.findList( + context, + widget._controller, + isRefresh, + widget.url, + _paramMap, + widget.widthToken, + (List res, bool flag) { + if (flag) { + if (isRefresh) { + _page["isLastPage"] = false; + _list.clear(); + } + if (res != null && res.length > 0) { + _list.addAll(res); + _page["currentPage"] = newPageNumber; + } else { + _page["isLastPage"] = true; + } + // 数据回传 + if (widget.listReturn != null) widget.listReturn!(_list, isRefresh); + } + setState(() {}); + }, + ); + } + + Future _deleteAll() async { + setState(() { + _resData['data'] = []; + _resData['page'] = { + "currentPage": 1, + "pageSize": 10, + "isLastPage": false, + }; + }); + } + + Future _deleteOne(int id) async { + List list = _resData['data']; + String keystr = widget.idkey ?? "id"; + for (var i = 0; i < list.length; i++) { + Map item = list[i]; + int itemId = item[keystr]; + if (itemId == id) { + list.removeAt(i); + break; + } + } + setState(() { + _resData['data'] = list; + }); + } + + // Future _deleteList(List ids) { + // List list = _resData['data']; + // String keystr = widget.idkey ?? "id"; + // List newList = list + // .where( + // (e) => !ids.contains(e[keystr]), + // ) + // .toList(); + // setState(() => _resData['data'] = newList); + // } + + @override + Widget build(BuildContext context) { + String msg; + bool flag = true; + if (!widget.isBase) { + // 非普通上拉下拉 + if (widget.getData == null || widget.data == null) { + msg = "自定义方法或数据异常"; + flag = false; + } + } else { + // 普通上拉下拉 + if (widget.url == null) { + msg = "普通类型参数,或地址异常"; + flag = false; + } + } + + if (!flag) { + return Container(); + } + Widget? emptyWidget=null; + List items; + if (widget.isBase) { + if (_resData['data'].length == 0){ + + emptyWidget = MyEmptyWidget(imgAssetPath: widget.emptyImagPath,textVal: widget.emptityTextVal??null); + } + items = _resData['data']; + } else { + if (widget.data?.length == 0){ + emptyWidget = MyEmptyWidget(imgAssetPath: widget.emptyImagPath,textVal: widget.emptityTextVal??null); + } + items = widget.data??[]; + } + + return EasyRefresh( + firstRefresh: widget.firstRefresh, + taskIndependence: true, + enableControlFinishRefresh: true, + enableControlFinishLoad: true, + controller: widget._controller, + header: widget.header ?? MaterialHeader(), + footer: widget.footer ?? MaterialFooter(), + emptyWidget: emptyWidget, + onRefresh: () async { + if (widget.getData != null) + await widget.getData!(true); + else + await _getData(context, true); + }, + onLoad: () async { + if (widget.getData != null) + await widget.getData!(false); + else + await _getData(context, false); + }, + child: Container( + child: Column( + children: widget.operation + ? items.map( + (e) { + return widget.itemWidget( + e, + deall: _deleteAll, + de: _deleteOne, + ); + }, + ).toList() + : items.map( + (e) { + return widget.itemWidget(e); + }, + ).toList(), + ), + ), + ); + } +} diff --git a/marking_app/lib/utils/easy_refresh/MyEasyRefreshService.dart b/marking_app/lib/utils/easy_refresh/MyEasyRefreshService.dart new file mode 100644 index 0000000..f7f63f0 --- /dev/null +++ b/marking_app/lib/utils/easy_refresh/MyEasyRefreshService.dart @@ -0,0 +1,47 @@ +/* + * @Descripttion: 我的常规EasyRefresh请求接口 + * @version: + * @Author: wy + * @Date: 2020-08-25 16:58:32 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-18 21:09:08 + */ + +import 'package:flutter_easyrefresh/easy_refresh.dart'; + +class MyEasyRefreshService{ + // MyEasyRefreshService._() : super(); + + // static MyEasyRefreshService _instance; + + // static MyEasyRefreshService getInstance() { + // if (_instance == null) { + // _instance = MyEasyRefreshService._(); + // } + // return _instance; + // } + + static Future findList( + context, + EasyRefreshController _controller, + bool isRefresh, + String url, + Map params, + bool widthToken, + Function call, + {bool conMonitor = true}) async { + if (widthToken) { + // await Request.post(context, url, params).then((res) { + // List list = res["result"]; + // call(list, res["code"] == 0); + // BaseService.controlController(context, isRefresh, _controller, list); + // }); + } else { + // await Request.postNotToken(context, url, params).then((res) { + // List list = res["result"]; + // call(list, res["code"] == 0); + // BaseService.controlController(context, isRefresh, _controller, list); + // }); + } + } +} diff --git a/marking_app/lib/utils/easy_refresh/MyEmptyWidget.dart b/marking_app/lib/utils/easy_refresh/MyEmptyWidget.dart new file mode 100644 index 0000000..514a877 --- /dev/null +++ b/marking_app/lib/utils/easy_refresh/MyEmptyWidget.dart @@ -0,0 +1,57 @@ +/* + * @Descripttion: + * @version: + * @Author: wy + * @Date: 2020-07-01 15:45:15 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-19 11:14:07 + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class MyEmptyWidget extends StatelessWidget { + static const String defText = "抱歉,暂无内容"; + static const String defimg = "assets/images/not_data_bgm.png"; + final String? textVal; + final String? imgAssetPath; + final AlignmentGeometry alignment; + final EdgeInsets? padding; + final double? imgWidth; + final double? imgHeight; + const MyEmptyWidget( + {this.imgWidth, + this.imgHeight, + this.textVal, + this.padding, + this.imgAssetPath, + this.alignment = Alignment.center, + Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: padding, + alignment: alignment, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + imgAssetPath ?? defimg, + fit: BoxFit.cover, + width: imgWidth ?? 130.w, + height: imgHeight ?? 130.w, + ), + Padding( + padding: EdgeInsets.only(top: 15.h), + child: Text( + textVal ?? defText, + style: TextStyle(fontSize: 16.sp, color: Colors.grey), + ), + ) + ], + ), + ); + } +} diff --git a/marking_app/lib/utils/easy_refresh/mixin/refresh_data_handle.dart b/marking_app/lib/utils/easy_refresh/mixin/refresh_data_handle.dart new file mode 100644 index 0000000..0ed51b1 --- /dev/null +++ b/marking_app/lib/utils/easy_refresh/mixin/refresh_data_handle.dart @@ -0,0 +1,122 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-19 15:34:22 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 15:26:56 + * @FilePath: \marking_app\lib\utils\easy_refresh\ss.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'package:achievement_view/achievement_view.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_dio.dart'; +import 'package:marking_app/common/model/common/base_page.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; + +mixin RefreshDataHandle { + // 获取DIO + Future getDio() async => await RestDio().getDio(); + + void toDataCatchHandle(EasyRefreshController controller, bool isReFresh, U params) { + setTimeOut(100, () { + if (!isReFresh) { + params.page -= 1; // 加载出错,返回一页 + controller.finishLoad(); + } else { + controller.finishRefresh(); + } + }); + } + + void toDataSuccessHandle( + BuildContext context, EasyRefreshController controller, bool isReFresh, U params, List data) { + bool noMore = data.isEmpty || data.length < params.limit; + setTimeOut(300, () { + if (isReFresh) { + controller.finishRefresh(); + if (noMore) { + controller.finishLoad(noMore: noMore); + } + controller.resetLoadState(); + } else { + controller.finishLoad(noMore: noMore); + if (noMore) + AchievementView( + duration: Duration(seconds: 1), + elevation: 1, + title: "提示", + subTitle: "没有更多数据了", + color: Theme.of(context).primaryColor, + ).show(context); + } + }); + } + + // ignore: avoid_shadowing_type_parameters + Future?> toRefreshData(EasyRefreshController controller, + {required Future>> Function(U) api, + required U params, + required BuildContext context, + List? pathParams, + bool isReFresh = false}) async { + if (isReFresh) { + params.page = RequestConfig.basePage.page; + // params.limit = RequestConfig.basePage.limit; + } + + try { + BaseStructureResult> result = await api(params); + if (result.success) { + BasePageData pageData = result.data!; + + toDataSuccessHandle(context, controller, isReFresh, params, pageData.items); + + return pageData; + } + throw Exception(result.message ?? '上拉加载下拉刷新失败'); + } catch (e) { + debugPrint('上拉下拉错误===: ${e.toString()}'); + toDataCatchHandle(controller, isReFresh, params); + + if (e is DioError) { + ExceptionHandle.exceptionPrompt(context, e.error); + } + } + return null; + } + + // ignore: avoid_shadowing_type_parameters + Future?> toRefreshDataWithPath(EasyRefreshController controller, + {required Future>> Function(String, U) api, + required U params, + required BuildContext context, + required String pahtVal, + bool isReFresh = false}) async { + if (isReFresh) { + params.page = RequestConfig.basePage.page; + params.limit = RequestConfig.basePage.limit; + } + + try { + BaseStructureResult> result = await api(pahtVal, params); + if (result.code == 200) { + BasePageData pageData = result.data!; + toDataSuccessHandle(context, controller, isReFresh, params, pageData.items); + + return pageData; + } + throw Exception(result.message ?? '上拉加载下拉刷新失败'); + } catch (e) { + debugPrint("上拉下拉执行异常,${e}"); + toDataCatchHandle(controller, isReFresh, params); + if (e is DioError) { + ExceptionHandle.exceptionPrompt(context, e.error); + } + } + } +} diff --git a/marking_app/lib/utils/easy_refresh/mixin/refresh_data_handle_report.dart b/marking_app/lib/utils/easy_refresh/mixin/refresh_data_handle_report.dart new file mode 100644 index 0000000..37b3dc7 --- /dev/null +++ b/marking_app/lib/utils/easy_refresh/mixin/refresh_data_handle_report.dart @@ -0,0 +1,104 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-19 15:34:22 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 15:26:56 + * @FilePath: \marking_app\lib\utils\easy_refresh\ss.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/model/common/base_page_data_report.dart'; +import 'package:marking_app/common/model/common/base_page_report.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/request/rest_dio.dart'; + +mixin RefreshDataHandleReport { + // 获取DIO + Future getDio() async => await RestDio().getDio(); + + void toDataCatchHandle(EasyRefreshController controller, bool isReFresh, U params) { + setTimeOut(100, () { + if (!isReFresh) { + params.page -= 1; // 加载出错,返回一页 + controller.finishLoad(); + } else { + controller.finishRefresh(); + } + }); + } + + void toDataSuccessHandle(EasyRefreshController controller, bool isReFresh, U params, List data) { + bool noMore = data.isEmpty || data.length < params.limit; + setTimeOut(100, () { + if (isReFresh) { + controller.finishRefresh(); + if (noMore) { + controller.finishLoad(noMore: noMore); + } + } else { + controller.finishLoad(noMore: noMore); + } + }); + } + + // ignore: avoid_shadowing_type_parameters + Future?> toRefreshData(EasyRefreshController controller, + {required Future>> Function(U) api, + required U params, + required BuildContext context, + List? pathParams, + bool isReFresh = false}) async { + if (isReFresh) { + params.page = RequestConfig.basePage.page; + params.limit = RequestConfig.basePage.limit; + } + + try { + BaseStructureResultReport> result = await api(params); + if (result.code == 200) { + BasePageDataReport pageData = result.data!; + + toDataSuccessHandle(controller, isReFresh, params, pageData.items); + + return pageData; + } + throw Exception(result.message ?? '上拉加载下拉刷新失败'); + } catch (e) { + debugPrint('上拉下拉错误===: ${e.toString()}'); + toDataCatchHandle(controller, isReFresh, params); + } + return null; + } + + // ignore: avoid_shadowing_type_parameters + Future?> toRefreshDataWithPath(EasyRefreshController controller, + {required Future>> Function(String, U) api, + required U params, + required BuildContext context, + required String pahtVal, + bool isReFresh = false}) async { + if (isReFresh) { + params.page = RequestConfig.basePage.page; + params.limit = RequestConfig.basePage.limit; + } + + try { + BaseStructureResultReport> result = await api(pahtVal, params); + if (result.code == 200) { + BasePageDataReport pageData = result.data!; + toDataSuccessHandle(controller, isReFresh, params, pageData.items); + + return pageData; + } + throw Exception(result.message ?? '上拉加载下拉刷新失败'); + } catch (e) { + debugPrint("上拉下拉执行异常,${e}"); + toDataCatchHandle(controller, isReFresh, params); + } + } +} diff --git a/marking_app/lib/utils/event_bus_utils.dart b/marking_app/lib/utils/event_bus_utils.dart new file mode 100644 index 0000000..8c74669 --- /dev/null +++ b/marking_app/lib/utils/event_bus_utils.dart @@ -0,0 +1,29 @@ +import 'package:event_bus/event_bus.dart'; + + + +//订阅者回调签名 +typedef EventCallback = void Function(dynamic arg); + +///事件总线 +class EventBusUtils { + late final EventBus _eventBus; + + // 单例模式 + static final EventBusUtils _instance = EventBusUtils._internal(); + factory EventBusUtils() => _instance; + EventBusUtils._internal() { + _eventBus = EventBus(); + } + + // 获取实例 + EventBus getEventBus(){ + return _eventBus; + } + + // 发起事件 + void toFire(T model){ + _eventBus.fire(model); + } +} + diff --git a/marking_app/lib/utils/fast_data.dart b/marking_app/lib/utils/fast_data.dart new file mode 100644 index 0000000..e8d0e8d --- /dev/null +++ b/marking_app/lib/utils/fast_data.dart @@ -0,0 +1,271 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-14 11:56:52 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 15:22:41 + * @FilePath: \marking_app\lib\utils\fast_data.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:convert'; + +import 'package:marking_app/common/model/marking/do_marking_keyboard_model.dart'; +import 'package:marking_app/common/model/marking/marking_history_zoom_info.dart'; +import 'package:marking_app/common/model/marking/marking_keyboard_sliding_position.dart'; +import 'package:marking_app/common/model/marking/marking_zoom.dart'; +import 'package:marking_app/common/model/user/user_info.dart'; +import 'package:marking_app/common/model/user/user_info_report.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class FastData { + // token + static const String _TOKEN = "APP:TOKEN"; + // 用户信息 + static const String _USER = "APP:USER"; + // 用户信息 报告 + static const String _USER_INFO_REPORT = "APP:USER:REPORT"; + // 用户密码 + static const String _USER_PWD_ACCOUNT = "APP:USER:PWD_ACCOUNT"; + // 键盘引导页 + static const String _INPUT_KEYBOARD_GUIDE_PAGE = "APP:MARKING:GUIDE_PAGE"; + // 阅卷 ==> 键盘下拉滚动位置 + static const String _MARKING_KEYBOARD_SLIDING_POSITION = "APP:MARKING:KEYBOARD_SLIDING_POSITION"; + // 阅卷 ==> 缩放大小历史记录 + static const String _MARKING_ZOOM_SCALE_AND_POSITION = "APP:MARKING:ZOOM_SCALE_AND_POSITION"; + // 阅卷偏好设置缓存 + static const String _MARKING_PREFERENCES = "APP:MARKING:PREFERENCES"; + // 阅卷 ==》 阅卷试题图缩放和位置 + static const String _MARKING_IMAGE_ZOOM = "APP:MARKING:IMAGE_ZOOM"; + // 阅卷偏好设置缓存 ==》 作业 + static const String _MARKING_PREFERENCES_WORK = "APP:MARKING:PREFERENCES:WORK"; + // 键盘引导页 ==》 作业 + static const String _INPUT_KEYBOARD_GUIDE_PAGE_WORK = "APP:MARKING:GUIDE_PAGE:WORK"; + + static SharedPreferences? _prefs; + + // 单例模式固定格式 + FastData._(); + + // 单例模式固定格式 + static FastData? _instance; + + // 单例模式固定格式 + static FastData getInstance() { + _instance ??= FastData._(); + + _instance!.getSharedInstance(); + return _instance!; + } + + Future getSharedInstance() async { + _prefs ??= await SharedPreferences.getInstance(); + return _prefs!; + } + + void cleanShared() { + _prefs?.clear(); + } + + // token + Future setToken(String token) async { + SharedPreferences thePrefs = await getSharedInstance(); + + return await thePrefs.setString(_TOKEN, token); + } + + Future getToken() async { + SharedPreferences thePrefs = await getSharedInstance(); + return thePrefs.getString(_TOKEN); + } + + void cleanToken() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_TOKEN); + } + + //用户名 + Future setUser(UserInfo user) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setString(_USER, jsonEncode(user.toJson())); + } + + void cleanUser() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_USER); + } + + Future getUser() async { + SharedPreferences thePrefs = await getSharedInstance(); + return thePrefs.getString(_USER); + } + + /// 用户信息 报告 + Future setUserReport(UserInfoReport user) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setString(_USER_INFO_REPORT, jsonEncode(user.toJson())); + } + + // 清空用户信息 + void cleanUserReport() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_USER_INFO_REPORT); + } + + Future getUserReport() async { + SharedPreferences thePrefs = await getSharedInstance(); + return thePrefs.getString(_USER_INFO_REPORT); + } + + //用户密码和账号 + Future setUserPwdAndAccount(Map mapData) async { + SharedPreferences thePrefs = await getSharedInstance(); + + return await thePrefs.setString(_USER_PWD_ACCOUNT, jsonEncode(mapData)); + } + + Future getUserPwd() async { + SharedPreferences thePrefs = await getSharedInstance(); + return thePrefs.getString(_USER_PWD_ACCOUNT); + } + + /* 输入型键盘引导页 */ + Future setInputKeyboardGuidePage(bool val) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setBool(_INPUT_KEYBOARD_GUIDE_PAGE, val); + } + + Future getInputKeyboardGuidePage() async { + SharedPreferences thePrefs = await getSharedInstance(); + return thePrefs.getBool(_INPUT_KEYBOARD_GUIDE_PAGE) ?? true; + } + + void cleanInputKeyboardGuidePage() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_INPUT_KEYBOARD_GUIDE_PAGE); + } + + /** 偏好设置 */ + Future setPreferences(DoMarkingKeyboardModel val) async { + SharedPreferences thePrefs = await getSharedInstance(); + String jsonVal = jsonEncode(val.toJson()); + bool flag = await thePrefs.setString(_MARKING_PREFERENCES, jsonVal); + return flag; + } + + Future getPreferences() async { + SharedPreferences thePrefs = await getSharedInstance(); + String? preferencesStr = thePrefs.getString(_MARKING_PREFERENCES); + if (preferencesStr != null) { + return DoMarkingKeyboardModel.fromJson(jsonDecode(preferencesStr)); + } + return null; + } + + void cleanPreferences() async { + cleanInputKeyboardGuidePage(); + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_MARKING_PREFERENCES); + } + + /* 偏好设置 =》 作业 */ + Future setPreferencesForWork(DoMarkingKeyboardModel val) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setString(_MARKING_PREFERENCES_WORK, jsonEncode(val.toJson())); + } + + Future getPreferencesForWork() async { + SharedPreferences thePrefs = await getSharedInstance(); + String? preferencesStr = thePrefs.getString(_MARKING_PREFERENCES_WORK); + if (preferencesStr != null) { + return DoMarkingKeyboardModel.fromJson(jsonDecode(preferencesStr)); + } + return null; + } + + void cleanPreferencesForWork() async { + cleanInputKeyboardGuidePageForWork(); + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_MARKING_PREFERENCES_WORK); + } + + /* 输入型键盘引导页 ==》作业 */ + Future setInputKeyboardGuidePageForWork(bool val) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setBool(_INPUT_KEYBOARD_GUIDE_PAGE_WORK, val); + } + + Future getInputKeyboardGuidePageForWork() async { + SharedPreferences thePrefs = await getSharedInstance(); + return thePrefs.getBool(_INPUT_KEYBOARD_GUIDE_PAGE_WORK) ?? true; + } + + void cleanInputKeyboardGuidePageForWork() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_INPUT_KEYBOARD_GUIDE_PAGE_WORK); + } + + /* 阅卷 ==》 题型图片缩放比例和位置 */ + Future setMarkingPapersImageZoom(MarkingZoom zoomIamge) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setString(_MARKING_IMAGE_ZOOM, jsonEncode(zoomIamge.toJson())); + } + + Future getMarkingPapersImageZoom([SharedPreferences? thePrefs]) async { + if (thePrefs == null) thePrefs = await getSharedInstance(); + String? valueStr = thePrefs.getString(_MARKING_IMAGE_ZOOM); + if (valueStr != null) { + try { + return MarkingZoom.fromJson(jsonDecode(valueStr)); + } catch (e) {} + } + return null; + } + + void cleanMarkingPapersImageZoom() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_MARKING_IMAGE_ZOOM); + } + + /* 阅卷 ==》 键盘滑动位置 */ + Future setMarkingKeyboardSlidingPosition(MarkingKeyboardSlidingPosition position) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setString(_MARKING_KEYBOARD_SLIDING_POSITION, jsonEncode(position.toJson())); + } + + Future getMarkingKeyboardSlidingPosition([SharedPreferences? thePrefs]) async { + if (thePrefs == null) thePrefs = await getSharedInstance(); + String? valueStr = thePrefs.getString(_MARKING_KEYBOARD_SLIDING_POSITION); + if (valueStr != null) { + try { + return MarkingKeyboardSlidingPosition.fromJson(jsonDecode(valueStr)); + } catch (e) {} + } + return null; + } + + void cleanMarkingKeyboardSlidingPosition() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_MARKING_KEYBOARD_SLIDING_POSITION); + } + + /* 阅卷 ==> 缩放组件历史位置 */ + Future setMarkingZoomInfo(MarkingHistoryZoomInfo info) async { + SharedPreferences thePrefs = await getSharedInstance(); + return await thePrefs.setString(_MARKING_ZOOM_SCALE_AND_POSITION, jsonEncode(info.toJson())); + } + + Future getMarkingZoomInfo([SharedPreferences? thePrefs]) async { + if (thePrefs == null) thePrefs = await getSharedInstance(); + String? valueStr = thePrefs.getString(_MARKING_ZOOM_SCALE_AND_POSITION); + if (valueStr != null) { + try { + return MarkingHistoryZoomInfo.fromJson(jsonDecode(valueStr)); + } catch (e) {} + } + return null; + } + + void cleanMarkingMarkingZoomInfo() async { + SharedPreferences thePrefs = await getSharedInstance(); + thePrefs.remove(_MARKING_ZOOM_SCALE_AND_POSITION); + } +} diff --git a/marking_app/lib/utils/fluro/index.dart b/marking_app/lib/utils/fluro/index.dart new file mode 100644 index 0000000..f43887c --- /dev/null +++ b/marking_app/lib/utils/fluro/index.dart @@ -0,0 +1,31 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class SkewTransition extends AnimatedWidget { + const SkewTransition({ + Key? key, + required Animation turns, + this.alignment = Alignment.center, + required this.child, + }) : assert(turns != null), + super(key: key, listenable: turns); + + Animation get turns => listenable as Animation; + + final Alignment alignment; + + final Widget child; + + @override + Widget build(BuildContext context) { + final double turnsValue = turns.value; + final Matrix4 transform = + Matrix4.skew(turnsValue * pi * 2.0, turnsValue * pi * 2.0); + return Transform( + transform: transform, + alignment: alignment, + child: child, + ); + } +} diff --git a/marking_app/lib/utils/image/EsoImageCacheManager.dart b/marking_app/lib/utils/image/EsoImageCacheManager.dart new file mode 100644 index 0000000..709d76f --- /dev/null +++ b/marking_app/lib/utils/image/EsoImageCacheManager.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; + +/// 缓存管理 +class EsoImageCacheManager extends CacheManager { + static const key = 'libEsoCachedImageData'; + + static final EsoImageCacheManager _instance = EsoImageCacheManager._internal(); + factory EsoImageCacheManager() => _instance; + EsoImageCacheManager._internal() : super(Config(key, fileService: EsoHttpFileService())); +} + +class EsoHttpFileService extends FileService { + HttpClient? _httpClient; + EsoHttpFileService({HttpClient? httpClient}) { + _httpClient = httpClient ?? HttpClient(); + _httpClient!.badCertificateCallback = (cert, host, port) => true; + } + + @override + Future get(String url, {Map? headers = const {}}) async { + final Uri resolved = Uri.base.resolve(url); + final HttpClientRequest req = await _httpClient!.getUrl(resolved); + headers?.forEach((key, value) { + req.headers.add(key, value); + }); + final HttpClientResponse httpResponse = await req.close(); + final http.StreamedResponse _response = http.StreamedResponse( + httpResponse.timeout(Duration(seconds: 60)), + httpResponse.statusCode, + contentLength: httpResponse.contentLength, + reasonPhrase: httpResponse.reasonPhrase, + isRedirect: httpResponse.isRedirect, + ); + return HttpGetResponse(_response); + } +} diff --git a/marking_app/lib/utils/image/NetworkImageSSL.dart b/marking_app/lib/utils/image/NetworkImageSSL.dart new file mode 100644 index 0000000..33d5615 --- /dev/null +++ b/marking_app/lib/utils/image/NetworkImageSSL.dart @@ -0,0 +1,77 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-10-22 18:18:58 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-10-22 18:20:20 + * @FilePath: \sell_ticket_app\lib\utils\img_util\NetworkImageSSL.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui' as ui show instantiateImageCodec, Codec; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart'; + +/// Image.network方法显示HTTPS图片时忽略证书 +class NetworkImageSSL extends ImageProvider { + const NetworkImageSSL(this.url, {this.scale = 1.0, this.headers}) + : assert(url != null), + assert(scale != null); + + final String url; + + final double scale; + + final Map? headers; + + @override + Future obtainKey(ImageConfiguration configuration) { + return new SynchronousFuture(this); + } + + @override + ImageStreamCompleter load(NetworkImageSSL key, DecoderCallback decode) { + return MultiFrameImageStreamCompleter(codec: _loadAsync(key), scale: key.scale); + } + + static final HttpClient _httpClient = new HttpClient() + ..badCertificateCallback = + ((X509Certificate cert, String host, int port) => true); + + Future _loadAsync(NetworkImageSSL key) async { + assert(key == this); + + final Uri resolved = Uri.base.resolve(key.url); + final HttpClientRequest request = await _httpClient.getUrl(resolved); + headers?.forEach((String name, String value) { + request.headers.add(name, value); + }); + final HttpClientResponse response = await request.close(); + if (response.statusCode != HttpStatus.ok) + throw new Exception( + 'HTTP请求失败,状态码: ${response.statusCode}, $resolved'); + + final Uint8List bytes = await consolidateHttpClientResponseBytes(response); + if (bytes.lengthInBytes == 0) + throw new Exception('NetworkImageSSL是一个空文件: $resolved'); + + return await ui.instantiateImageCodec(bytes); + } + + @override + bool operator ==(dynamic other) { + if (other.runtimeType != runtimeType) return false; + final NetworkImageSSL typedOther = other; + return url == typedOther.url && scale == typedOther.scale; + } + + @override + int get hashCode => hashValues(url, scale); + + @override + String toString() => '$runtimeType("$url", scale: $scale)'; +} + + diff --git a/marking_app/lib/utils/image/gallery_example_item.dart b/marking_app/lib/utils/image/gallery_example_item.dart new file mode 100644 index 0000000..8a0030a --- /dev/null +++ b/marking_app/lib/utils/image/gallery_example_item.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; +import 'package:marking_app/utils/image/gallery_example_item_model.dart'; + + +class GalleryExampleItemThumbnail extends StatelessWidget { + const GalleryExampleItemThumbnail({ + Key? key, + required this.galleryExampleItem, + required this.onTap, + }) : super(key: key); + + final GalleryExampleItemModel galleryExampleItem; + + final GestureTapCallback onTap; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: GestureDetector( + onTap: onTap, + child: Hero( + tag: galleryExampleItem.id, + child: Image.network(galleryExampleItem.resource), + ), + ), + ); + } +} diff --git a/marking_app/lib/utils/image/gallery_example_item_model.dart b/marking_app/lib/utils/image/gallery_example_item_model.dart new file mode 100644 index 0000000..1c0c04e --- /dev/null +++ b/marking_app/lib/utils/image/gallery_example_item_model.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'gallery_example_item_model.g.dart'; + + +@JsonSerializable() + class GalleryExampleItemModel extends Object { + + final String id; + final String resource; + final bool isSvg; + + + GalleryExampleItemModel({ + required this.id, + required this.resource, + this.isSvg = false, + }); + + factory GalleryExampleItemModel.fromJson(Map srcJson) => _$GalleryExampleItemModelFromJson(srcJson); + + Map toJson() => _$GalleryExampleItemModelToJson(this); + +} + + diff --git a/marking_app/lib/utils/image/image_utils.dart b/marking_app/lib/utils/image/image_utils.dart new file mode 100644 index 0000000..dc5550f --- /dev/null +++ b/marking_app/lib/utils/image/image_utils.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:marking_app/utils/image/gallery_example_item.dart'; +import 'package:marking_app/utils/image/gallery_example_item_model.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; + +class GalleryPhotoViewWrapper extends StatefulWidget { + GalleryPhotoViewWrapper({ + this.loadingBuilder, + this.backgroundDecoration, + this.minScale, + this.maxScale, + this.initialIndex = 0, + required this.galleryItems, + this.scrollDirection = Axis.horizontal, + }) : pageController = PageController(initialPage: initialIndex); + + final LoadingBuilder? loadingBuilder; + final BoxDecoration? backgroundDecoration; + final dynamic minScale; + final dynamic maxScale; + final int initialIndex; + final PageController pageController; + final List galleryItems; + final Axis scrollDirection; + + @override + State createState() { + return _GalleryPhotoViewWrapperState(); + } +} + +class _GalleryPhotoViewWrapperState extends State { + late int currentIndex = widget.initialIndex; + + void onPageChanged(int index) { + setState(() { + currentIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: widget.backgroundDecoration, + constraints: BoxConstraints.expand( + height: MediaQuery.of(context).size.height, + ), + child: Stack( + alignment: Alignment.bottomRight, + children: [ + PhotoViewGallery.builder( + scrollPhysics: const BouncingScrollPhysics(), + builder: _buildItem, + itemCount: widget.galleryItems.length, + loadingBuilder: widget.loadingBuilder, + backgroundDecoration: widget.backgroundDecoration, + pageController: widget.pageController, + onPageChanged: onPageChanged, + scrollDirection: widget.scrollDirection, + ), + Container( + padding: const EdgeInsets.all(20.0), + child: Text( + "整卷: ${currentIndex + 1}", + style: const TextStyle( + color: Colors.white, + fontSize: 17.0, + decoration: null, + ), + ), + ), + Positioned( + left:0, + top: 20.h, + child: InkWell( + onTap: (){ + Navigator.pop(context); + }, + child: Icon(Icons.chevron_left,size: 40.sp,color: Colors.white), + ) + ) + ], + ), + ), + ); + } + + PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) { + final GalleryExampleItemModel item = widget.galleryItems[index]; + return item.isSvg + ? PhotoViewGalleryPageOptions.customChild( + child: Container( + width: 300, + height: 300, + child: SvgPicture.network( + item.resource, + height: 200.0, + ), + ), + childSize: const Size(300, 300), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained * (0.5 + index / 10), + maxScale: PhotoViewComputedScale.covered * 4.1, + heroAttributes: PhotoViewHeroAttributes(tag: item.id), + ) + : PhotoViewGalleryPageOptions( + imageProvider: NetworkImage(item.resource), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained * (0.5 + index / 10), + maxScale: PhotoViewComputedScale.covered * 4.1, + heroAttributes: PhotoViewHeroAttributes(tag: item.id), + ); + } +} \ No newline at end of file diff --git a/marking_app/lib/utils/index.dart b/marking_app/lib/utils/index.dart new file mode 100644 index 0000000..6b9db0c --- /dev/null +++ b/marking_app/lib/utils/index.dart @@ -0,0 +1,134 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-15 11:21:21 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 17:02:25 + * @FilePath: \marking_app\lib\utils\index.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-15 11:21:21 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-18 11:16:22 + * @FilePath: \marking_app\lib\utils\index.dart + * @Description: 工具页面 + */ +library utils; + +import 'dart:convert'; +import 'dart:math'; + +import 'package:fluro/fluro.dart'; +import 'package:flutter/material.dart'; +import 'package:marking_app/common/config/request_config.dart'; + +export "./colorUtils.dart"; +export "./fast_data.dart"; +export "./common_utils.dart"; +export './toast_utils.dart'; +export './my_future_builder.dart'; +export './dashed_line.dart'; +export './anti_shake_throttling.dart'; + +typedef TheVoidCallback = void Function(VoidCallback); + +Future setTimeOut(int milliseconds, call) => Future.delayed(Duration(milliseconds: milliseconds), call); + +bool isInteger(num value) => (value % 1) == 0; + +void toUpState(TheVoidCallback setState, VoidCallback fn, bool mounted) { + if (mounted) setState(fn); +} + +/// 单纯的Json格式输出打印 +void printJson(Object object) { + try { + JsonEncoder encoder = const JsonEncoder.withIndent(' '); + var encoderString = encoder.convert(object); + // print(encoderString); + // 不使用print()方法是因为这是单条输出,如果过长无法显示全 + // 所以使用debugPrint() + debugPrint(encoderString); + // 下面这语句的效果与debugPrint 相同 + //encoderString.split('\n').forEach((element) => print(element)); + } catch (e) { + toPrint(val: e); + } +} + +String getDoubleRemoveZero(double? val, [String? defaultVal]) { + try { + if (val == null) throw Exception('数据为空'); + List _valArr = val.toString().split('.'); + if (_valArr.length >= 2) { + if (int.parse(_valArr[1]) == 0) { + return val.toInt().toString(); + } + return val.toString(); + } + return val.toInt().toString(); + } catch (e) { + return defaultVal ?? ''; + } +} + +/// 获取指定范围的随机数 +int getRandomNumbers(int minNumber, int maxNumber) { + int next(int min, int max) => min + Random().nextInt(max - min); + return next(minNumber, maxNumber); +} + +/// 获取页面随机跳转 +TransitionType getTransition() { + try { + List transitions = TransitionType.values; + int theRandomNumber = getRandomNumbers(0, transitions.length - 1); + + TransitionType val = transitions[theRandomNumber]; + if (val == TransitionType.custom) { + val = getTransition(); + } + return val; + } catch (e) { + return getTransition(); + } +} + +/// 去除小数点 +String doubleToStringAsFixed(double val, {int fractionDigits = 2}) { + return val.toStringAsFixed(fractionDigits).replaceAll(RegExp(r'\.0*$'), ''); +} + +void toPrint({required dynamic val, bool toPrintJson = false}) { + bool printSwitch = RequestConfig.printSwitch; + if (printSwitch && val != null) toPrintJson ? printJson(val) : print(val); +} + +class EUMNoScrollBehavior extends ScrollBehavior { + @override + Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) { + switch (getPlatform(context)) { + case TargetPlatform.iOS: + return child; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + return GlowingOverscrollIndicator( + child: child, + // 不显示头部水波纹 + showLeading: false, + // 不显示尾部水波纹 + showTrailing: false, + axisDirection: axisDirection, + color: Theme.of(context).colorScheme.secondary, + ); + case TargetPlatform.linux: + break; + case TargetPlatform.macOS: + break; + case TargetPlatform.windows: + break; + } + return child; + } +} diff --git a/marking_app/lib/utils/marking_utils/index.dart b/marking_app/lib/utils/marking_utils/index.dart new file mode 100644 index 0000000..cb8ab84 --- /dev/null +++ b/marking_app/lib/utils/marking_utils/index.dart @@ -0,0 +1,14 @@ +import 'dart:ui'; + +// 同步得分回调 +typedef SynchroScoreCallback = Future Function( + {required double score, required bool continueScoring, required bool hasSubtopic, bool allWrong, bool cleanScore}); + +// +typedef OffsetOriginCall = void Function(Offset offset); + +// +typedef VoidCallbackFuture = Future Function(); + +// +typedef VoidCallbackFutureBool = Future Function(bool flag); diff --git a/marking_app/lib/utils/my_future_builder.dart b/marking_app/lib/utils/my_future_builder.dart new file mode 100644 index 0000000..3e14d26 --- /dev/null +++ b/marking_app/lib/utils/my_future_builder.dart @@ -0,0 +1,143 @@ +/* + * @Descripttion: + * @version: + * @Author: wy + * @Date: 2020-06-16 19:35:24 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 16:12:13 + */ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class MyFutureBuilder { + static Widget toBuilder(context, Future future, Function call) { + return FutureBuilder( + future: future, + builder: (context, snapshot) { + return call(snapshot); + }, + ); + } + + // static Widget toBuilderCallMapData(context1, url, call, + // {Map paras, Widget loding, bool widtToken = true}) { + // return FutureBuilder( + // future: widtToken + // ? Request.post(context1, url, paras) + // : Request.postNotToken(context1, url, paras), + // builder: (context, snapshot) { + // if (snapshot.hasData) { + // Map map = _checkAndGetDataMap(context, snapshot); + // return call(map); + // } else { + // return loding ?? getBldLoad(context1); + // } + // }, + // ); + // } + + static FutureBuilder> buildFutureBuilder(BuildContext context, Future> future, Function(List) call) { + return FutureBuilder>( + builder: (context, AsyncSnapshot> async) { + //在这里根据快照的状态,返回相应的widget + if (async.connectionState == ConnectionState.active || async.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (async.connectionState == ConnectionState.done) { + // if (async.hasError) { + // return const Center(child: Text("ERROR")); + // } else + if (async.hasData) { + List list = async.data ?? []; + return call(list); + } + } + return getBldLoad(context); + }, + future: future, + ); + } + + static FutureBuilder buildFutureBuilderOfSingleInstance(BuildContext context, Future future, Function(T?) call, {Widget? widget}) { + return FutureBuilder( + builder: (context, AsyncSnapshot async) { + //在这里根据快照的状态,返回相应的widget + if (async.connectionState == ConnectionState.active || async.connectionState == ConnectionState.waiting) { + return widget ?? const Center(child: CircularProgressIndicator()); + } + if (async.connectionState == ConnectionState.done) { + // if (async.hasData) { + // T? data = async.data; + // return call(data); + // } + + T? data = async.data; + return call(data); + } + return getBldLoad(context); + }, + future: future, + ); + } + + // static Widget toBuilderCallListData(context, url, paras, call, + // {Widget loding, bool widtToken = true}) { + // return FutureBuilder( + // future: widtToken + // ? Request.post(context, url, paras) + // : Request.postNotToken(context, url, paras), + // builder: (context, snapshot) { + // if (snapshot.hasData) { + // List list = _checkAndGetDataList(context, snapshot); + // return call(list); + // } else { + // return getBldLoad(context); + // } + // }, + // ); + // } + + static Widget getBldLoad(context) { + return Container( + alignment: Alignment.center, + child: Center( + child: SpinKitCircle( + color: Theme.of(context).primaryColor, + size: 100.w, + ), + ), + ); + } + + // static Map _checkAndGetDataMap(context, snapshot) { + // int code = snapshot.data['code']; + // if (code == 0) { + // var res = snapshot.data['result']; + // if (!(res is Map)) { + // Map resMap = Map(); + // resMap['data'] = res; + // return resMap; + // } else { + // return res; + // } + // } else { + // Request.getCodeToMas( + // context, code.toString(), "${snapshot.data["message"]}", + // delayed: true); + // return null; + // } + // } + + // static List _checkAndGetDataList(context, snapshot) { + // int code = snapshot.data['code']; + // if (code == 0) { + // return snapshot.data['result']; + // } else { + // Request.getCodeToMas( + // context, code.toString(), "${snapshot.data["message"]}", + // delayed: true); + // return null; + // } + // } +} diff --git a/marking_app/lib/utils/my_text.dart b/marking_app/lib/utils/my_text.dart new file mode 100644 index 0000000..c971f27 --- /dev/null +++ b/marking_app/lib/utils/my_text.dart @@ -0,0 +1,33 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-08-01 14:11:37 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-08-02 15:34:34 + * @FilePath: \marking_app\lib\utils\my_text.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import 'package:flutter/cupertino.dart'; +import 'package:marking_app/utils/colorUtils.dart'; + +// 快捷Text使用 +Text quickText(text, + {double? size, + Color color = CommonColors.defaultColor, + TextAlign? align, + FontWeight? fontWeight, + TextOverflow overflow = TextOverflow.ellipsis, + int maxLines = 1, + TextDecoration decoration = TextDecoration.none}) { + return Text( + text.toString(), + textAlign: align, + maxLines: maxLines, + overflow: overflow, + style: TextStyle( + decoration: decoration, + fontSize: size, + color: color, + fontWeight: fontWeight, + ), + ); +} diff --git a/marking_app/lib/utils/print_utils.dart b/marking_app/lib/utils/print_utils.dart new file mode 100644 index 0000000..868291d --- /dev/null +++ b/marking_app/lib/utils/print_utils.dart @@ -0,0 +1,6 @@ +// +class PrintUtils{ + + // const bool inProduction = const bool.fromEnvironment("dart.vm.product"); + +} \ No newline at end of file diff --git a/marking_app/lib/utils/progressDialog.dart b/marking_app/lib/utils/progressDialog.dart new file mode 100644 index 0000000..3d5d3ec --- /dev/null +++ b/marking_app/lib/utils/progressDialog.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +///加载弹框 +class ProgressDialog { + static bool _isShowing = false; + + ///展示 {Widget child = const CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(Colors.red),)} + static void showProgress(BuildContext context) { + _isShowing = false; + if (!_isShowing) { + _isShowing = true; + Navigator.push( + context, + _PopRoute( + child: _Progress( + child: new Padding( + padding: const EdgeInsets.all(12.0), + child: new Center( + //保证控件居中效果 + child: new SizedBox( + width: 120.0, + height: 120.0, + child: new Container( + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(8.0), + ), + ), + ), + child: new Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + new CircularProgressIndicator( + backgroundColor: Colors.white, + strokeWidth: 4.0, + valueColor: new AlwaysStoppedAnimation(Colors.grey), + ), + new Padding( + padding: const EdgeInsets.only( + top: 20.0, + ), + child: new Text( + "加载中...", + style: new TextStyle(fontSize: 22, color: Colors.grey), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + } + + ///隐藏 + static void hideProgress(BuildContext context) { + if (_isShowing) { + Navigator.of(context).pop(); + _isShowing = false; + } + } +} + +///Widget +class _Progress extends StatelessWidget { + final Widget child; + + _Progress({ + Key? key, + required this.child, + }) : assert(child != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Center( + child: child, + )); + } +} + +///Route +class _PopRoute extends PopupRoute { + final Duration _duration = Duration(milliseconds: 300); + Widget child; + + _PopRoute({required this.child}); + + @override + Color get barrierColor => Colors.black54; + + @override + bool get barrierDismissible => true; + + @override + String? get barrierLabel => null; + + @override + Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { + return child; + } + + @override + Duration get transitionDuration => _duration; +} diff --git a/marking_app/lib/utils/request/rest_client.dart b/marking_app/lib/utils/request/rest_client.dart new file mode 100644 index 0000000..56eb8b5 --- /dev/null +++ b/marking_app/lib/utils/request/rest_client.dart @@ -0,0 +1,250 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 11:29:05 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 17:51:16 + * @FilePath: \marking_app\lib\config\RequestClient.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'package:dio/dio.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/common/model/common/base_page_data.dart'; +import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/common/upload_img_secret_key.dart'; +import 'package:marking_app/common/model/job/job_collect_params.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student.dart'; +import 'package:marking_app/common/model/job/job_concerned_with_student_params.dart'; +import 'package:marking_app/common/model/job/job_note_taking_trajectory.dart'; +import 'package:marking_app/common/model/job/job_page_tab.dart'; +import 'package:marking_app/common/model/job/job_review_submission.dart'; +import 'package:marking_app/common/model/job/job_task_item.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job_params.dart'; +import 'package:marking_app/common/model/job/marking_text_question_job_tab_params.dart'; +import 'package:marking_app/common/model/job/review_again_list_params.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config.dart'; +import 'package:marking_app/common/model/job/upload_file_interface_config_params.dart'; +import 'package:marking_app/common/model/marking/marking_abnormal_res.dart'; +import 'package:marking_app/common/model/marking/marking_item.dart'; +import 'package:marking_app/common/model/marking/marking_list_params.dart'; +import 'package:marking_app/common/model/marking/marking_statistics.dart'; +import 'package:marking_app/common/model/marking/marking_tag_single_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab_params.dart'; +import 'package:marking_app/common/model/marking/marking_text_question_tab_step_size.dart'; +import 'package:marking_app/common/model/marking/rating_progress_model.dart'; +import 'package:marking_app/common/model/marking/review_records_item.dart'; +import 'package:marking_app/common/model/marking/review_records_params.dart'; +import 'package:marking_app/common/model/marking/submit_exam_abnormal_params.dart'; +import 'package:marking_app/common/model/marking/submit_exam_params.dart'; +import 'package:marking_app/common/model/progress/progress_item.dart'; +import 'package:marking_app/common/model/progress/progress_page_params.dart'; +import 'package:marking_app/common/model/progress/progress_statistics.dart'; +import 'package:marking_app/common/model/review/review_item.dart'; +import 'package:marking_app/common/model/review/review_page_params.dart'; +import 'package:marking_app/common/model/review/review_tab.dart'; +import 'package:marking_app/common/model/sys/system_version.dart'; +import 'package:marking_app/common/model/user/user_info.dart'; +import 'package:retrofit/retrofit.dart' as the_retrofit; +import 'package:marking_app/common/model/user/user_login_params.dart'; + +part 'rest_client.g.dart'; + +@the_retrofit.RestApi() +@the_retrofit.Headers({"Content-Type": "application/json"}) +abstract class RestClient { + factory RestClient(Dio dio, {String baseUrl}) = _RestClient; + + // 最新版本 + @the_retrofit.GET("/api/version/latest?mobileTypeEnum={mobileTypeEnum}") + Future> getLatestVersion( + @the_retrofit.Path("mobileTypeEnum") int mobileTypeEnum); // 1 安卓 2 ios + + // 用户登录 /auth/login/exam-marking/user-mobile + @the_retrofit.POST("/auth/login/exam-marking/user") + Future> toLogin(@the_retrofit.Body() UserLoginParams params); + + //获取图片上传秘钥 + @the_retrofit.GET("/api/img-svr/minio-cfg") + Future> getImageUploadKey(); + + // 获取用户信息 + @the_retrofit.GET("/auth/info/cur-user") + Future> getUserInfo(@the_retrofit.Header("Authorization") String token); + + // 阅卷列表 => 分页获取 + @the_retrofit.GET("/api/marking/list") + Future>> getMarkingsByPage( + @the_retrofit.Queries() MarkingListParams params); + + // /api/marking/list + + // 进度 => 进度详情 分页获取 + @the_retrofit.GET("/api/marking/teacher-overview") + Future>> getProgressByPage( + @the_retrofit.Path("markingId") String id, @the_retrofit.Queries() ProgressPageParams params); + + // 进度 => 进度详情 统计数据 + @the_retrofit.GET("/api/marking/{examSubjectId}/overview") + Future> getProgressStatistics(@the_retrofit.Path("examSubjectId") String id); + + // 回评 => 获取tab + @the_retrofit.GET("/api/marking/{markingUserId}/review/tab") + Future>> getReviewTab( + @the_retrofit.Path("markingUserId") String id, @the_retrofit.Query("markingUserId") int markingUserId); + + // 回评 => 获取tab 下分页 + @the_retrofit.GET("/api/marking/{markingUserId}/review/paged") + Future>> getReviewPage( + @the_retrofit.Path("markingUserId") String id, @the_retrofit.Queries() ReviewPageParams params); + + // 阅卷 => 获取考试试题 + @the_retrofit.GET("/api/marking/question") + Future> getTestQuestionsOfExam( + @the_retrofit.Queries() MarkingTextQuestionParams params); + + // 阅卷 => 获取考试tab(试题批次) + @the_retrofit.GET("/api/marking/{markingUserId}/tab") + Future>> getTestQuestionsOfTab( + @the_retrofit.Path("markingUserId") int markingUserId); + + // 阅卷 => 单个试题Tag阅卷详情 + @the_retrofit.GET("/api/marking/{markingUserId}/progress") + Future> getMarkingTagSingleDetails( + @the_retrofit.Path("markingUserId") int markingUserId, @the_retrofit.Queries() MarkingTagSingleParams params); + + // 阅卷 => 获取考试tabs 下所有步长 + @the_retrofit.GET("/api/marking/q/interval") + Future>> getTestQuestionsOfTabStepSize( + @the_retrofit.Query("examSubjectId") int examSubjectId); + + // 阅卷 => 获取考试tab下某次试题 + @the_retrofit.GET("/api/marking/{markingUserId}/detail") + Future> getTabOfExam(@the_retrofit.Path("markingUserId") int markingUserId, + @the_retrofit.Queries() MarkingTextQuestionTabParams params); + + // 阅卷 => 提交考试试题 + @the_retrofit.PUT("/api/marking") + Future> submitTestQuestionsOfExam(@the_retrofit.Body() SubmitExamParams params); + + @the_retrofit.PUT("/api/marking/error") + Future> submitTestQuestionsOfExamAbnormal( + @the_retrofit.Body() SubmitExamAbnormalParams params); + + // 阅卷 => 提交考试试题 + @the_retrofit.PUT("/api/marking/review") + Future> submitTestQuestionsOfExamReview(@the_retrofit.Body() SubmitExamParams params); + + // 阅卷 => 结束阅卷 + @the_retrofit.PUT("/api/marking/{markingUserId}/end") + Future> endMarkingTask(@the_retrofit.Path("markingUserId") String markingUserId); + + // 阅卷 => 结束阅卷 + @the_retrofit.GET("/api/marking/original-paper") + Future>> getViewOriginalVolume( + @the_retrofit.Query("markingUserDetailId") String markingUserDetailId); + + // 阅卷 => 查看答案 + @the_retrofit.GET("/api/marking/answer") + Future> getStandardAnswer( + @the_retrofit.Query("examSubjectId") int examSubjectId, @the_retrofit.Query("questionNum") String questionNum); + + // 阅卷 => 获取任务列表种类统计信息 + @the_retrofit.GET("/api/marking/list/count") + Future>> getMarkingStatistics(); + + // 阅卷 => 获取异常类型集合 + @the_retrofit.GET("/api/marking/errortypes") + Future> getAbnormalTypes(); + + // 阅卷 => 获取异常类型集合 + @the_retrofit.GET("/api/marking/{markingUserId}/exit") + Future exitMarking(@the_retrofit.Path("markingUserId") int markingUserId); + + // 阅卷 => 获取异常类型集合 + @the_retrofit.GET("/api/marking/{markingUserId}/records") + Future>> getMarkingRecords( + @the_retrofit.Path("markingUserId") String markingUserId, @the_retrofit.Queries() ReviewRecordsParams params); + + // 阅卷 => 获取异常详细信息 + @the_retrofit.GET("/api/marking/error-info") + Future> getMarkingQuestionsErrorInfo( + @the_retrofit.Query("markingUserDetailId") int id); + + // 阅卷 => 获取仲裁详细信息 + @the_retrofit.GET("/api/marking/history-score") + Future>> getArbitrateOfHistoryScore( + @the_retrofit.Query("markingUserDetailId") int id); + + // 阅卷 => 获取仲裁详细信息 + @the_retrofit.GET("/api/marking/rating-info") + Future>> getMarkingRatingInfo( + @the_retrofit.Query("markingUserId") int id); + + // ------------------------------------------ 作业 ------------------------------------------ + + // 作业 => 作业列表 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Task") + Future>> getJobsByPage( + @the_retrofit.Queries() MarkingListParams params); + + // 作业 => 批改获取tabs + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Task/tabs") + Future>> getJobOfTabs(@the_retrofit.Query("taskId") int taskId); + + // 作业 => 批改获取tabs + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Marking/students") + Future>> getJobWithStudents( + @the_retrofit.Queries() JobConcernedWithStudentParams params); + + // 作业 => 获取考试tab下某次试题 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Task/detail") + Future> getJobTabOfExam( + @the_retrofit.Queries() MarkingTextQuestionJobTabParams params); + + // 作业 => 提交 + @the_retrofit.POST("${RequestConfig.hwProxyKeywords}/api/Task") + Future toSubmitMarking(@the_retrofit.Body() JobReviewSubmission params); + + // 作业 => 结束作业任务 + @the_retrofit.PUT("${RequestConfig.hwProxyKeywords}/api/Task/end") + Future toEndReviewJob(@the_retrofit.Query("taskIds") List taskIds); + + // 作业 => 获取考试tab下某次试题 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Task/questions") + Future>> toGoreviewAgainPage( + @the_retrofit.Queries() ReviewAgainListParams params); + + // 作业 => 获取参考答案 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Task/answer") + Future> getAnswer( + @the_retrofit.Query("taskId") int taskId, + @the_retrofit.Query("questionNum") String questionNum, + ); + + // 作业 => 上传图片请求参数 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Upload") + Future> getUploadFile( + @the_retrofit.Queries() UploadFileInterfaceConfigParams params); + + // 作业 => 获取参考答案 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Dpc/studentAnswerHandwriting") + Future> getNoteTakingTrajectory( + @the_retrofit.Query("paperId") int paperId, @the_retrofit.Query("questionNo") String questionNo); + + // 作业 => 查询作业是否收藏 + @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Dpc/collect") + Future> getJobCollect( + @the_retrofit.Query("taskId") int taskId, @the_retrofit.Query("studentId") int studentId); + +// 作业 => 查询作业是否收藏 + @the_retrofit.POST("${RequestConfig.hwProxyKeywords}/api/Dpc/collect") + Future> toJobFavoriteCancel(@the_retrofit.Body() JobCollectParams params); + + // 作业 => 一键批阅 + @the_retrofit.POST("${RequestConfig.hwProxyKeywords}/api/Marking/auto") + Future> toJobOneClickReview(@the_retrofit.Field() int taskId); +} diff --git a/marking_app/lib/utils/request/rest_client_report.dart b/marking_app/lib/utils/request/rest_client_report.dart new file mode 100644 index 0000000..64f62b8 --- /dev/null +++ b/marking_app/lib/utils/request/rest_client_report.dart @@ -0,0 +1,70 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 11:29:05 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-09-27 17:51:16 + * @FilePath: \marking_app\lib\config\RequestClient.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'package:dio/dio.dart'; +import 'package:marking_app/common/model/common/base_page_data_report.dart'; +import 'package:marking_app/common/model/common/base_structure_result_report.dart'; +import 'package:marking_app/common/model/report/marked_item.dart'; +import 'package:marking_app/common/model/report/marked_item_params.dart'; +import 'package:marking_app/common/model/report/report_for_class_teacher_model.dart'; +import 'package:marking_app/common/model/report/report_for_class_teacher_params.dart'; +import 'package:marking_app/common/model/report/report_for_marking_pagerdetail_model.dart'; +import 'package:marking_app/common/model/report/report_for_marking_pagerdetail_params.dart'; +import 'package:marking_app/common/model/report/report_for_subject_student_model.dart'; +import 'package:marking_app/common/model/report/report_for_subject_student_params.dart'; +import 'package:marking_app/common/model/report/report_for_subject_teacher_model.dart'; +import 'package:marking_app/common/model/report/report_for_subject_teacher_params.dart'; +import 'package:marking_app/common/model/report/report_home_model.dart'; +import 'package:marking_app/common/model/user/user_info_report.dart'; +import 'package:retrofit/retrofit.dart' as the_retrofit; + +part 'rest_client_report.g.dart'; + +@the_retrofit.RestApi() +@the_retrofit.Headers({"Content-Type": "application/json"}) +abstract class RestClientReport { + factory RestClientReport(Dio dio, {String baseUrl}) = _RestClientReport; + + // 报告 => 获取用户信息 + @the_retrofit.GET("/api/user/users/getuserinfo") + Future> getReportSysUserinfo(); + + // 报告 => 用户相关考试列表集合 + @the_retrofit.GET("/api/user/exam/getexamsbyuser") + Future>> getexamsbyuser( + @the_retrofit.Queries() MarkedItemParams params); + + // 报告 => 用户相关考试列表集合 + @the_retrofit.GET("/api/user/exam/gethomedata") + Future> getReportHomeData(@the_retrofit.Query("examId") int? examId); + + // 报告 => 用户相关考试列表集合 + @the_retrofit.POST("/api/user/users/switchposition") + Future> switchposition(@the_retrofit.Query('positionId') int positionId); + + // 报告 => 学科老师报告 + @the_retrofit.GET("/api/user/template/getreportteacher") + Future> getreportsForSubjectTeacher( + @the_retrofit.Queries() ReportForSubjectTeacherParams params); + + // 报告 => 学科老师报告 + @the_retrofit.POST("/api/user/template/class/testteport") + Future> getreportsForClassTeacher( + @the_retrofit.Body() ReportForClassTeacherParams params); + + // 报告 => 学生个人报告 + @the_retrofit.POST("/api/user/template/student/stubjecttestteport") + Future> getReportSubjectStudent( + @the_retrofit.Body() ReportForSubjectStudentParams params); + + // 报告 => 查看原卷 + @the_retrofit.POST("/api/user/template/getmarkingpagerdetails") + Future> getMarkingPagerDetails( + @the_retrofit.Body() ReportForMarkingPagerdetailParams params); +} diff --git a/marking_app/lib/utils/request/rest_dio.dart b/marking_app/lib/utils/request/rest_dio.dart new file mode 100644 index 0000000..ef15e14 --- /dev/null +++ b/marking_app/lib/utils/request/rest_dio.dart @@ -0,0 +1,401 @@ +import 'dart:io'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:dio/dio.dart'; +import 'package:dio/adapter.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:marking_app/common/config/request_config.dart'; +import 'package:marking_app/routes/RouterManager.dart'; +import 'package:marking_app/utils/index.dart'; +import 'package:marking_app/utils/the_global.dart'; +import 'package:package_info/package_info.dart'; + +class RestDio { + late Dio _dio; + // 单例模式 + static final RestDio _instance = RestDio._internal(); + factory RestDio() => _instance; + RestDio._internal() { + init(); + } + + // 初始化请求配置 + init() { + BaseOptions options = BaseOptions( + connectTimeout: RequestConfig.connectTimeout, + receiveTimeout: RequestConfig.receiveTimeout, + ); + _dio = Dio(options); + _dio.interceptors.add(AuthInterceptor()); // 添加 token + _dio.interceptors.add(ResponseHandle()); // 添加 数据返回拦截 + _dio.interceptors.add(TheError()); // 添加 数据返回拦截 + const isProd = bool.fromEnvironment('dart.vm.product'); + if (!isProd && RequestConfig.requestDataPrinting) { + _dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); //添加日志 + } + // 添加https证书 + setHttpsPEM(); + // setHttpsPKCS12(); + + // 使用代理 + // setFindProxy(); + } + + String PEM = "XXXXX"; // certificate content + String PKCS12File = "XXXXX"; // certificate content + + //dio 基本方法 + Future getDio() async { + return _dio; + } + + //添加证书 + setHttpsPEM() async { + (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { + client.badCertificateCallback = (X509Certificate cert, String host, int port) { + // if (cert.pem == PEM) { + // // Verify the certificate + // return true; + // } + // return false; + return true; + }; + }; + } + + setHttpsPKCS12() async { + (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { + SecurityContext sc = new SecurityContext(); + //file is the path of certificate + sc.setTrustedCertificates(PKCS12File); + HttpClient httpClient = new HttpClient(context: sc); + return httpClient; + }; + } + + //设置代理 + setFindProxy() async { + // 设置请求拦截器 + _dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) { + // 判断 URI 的路径是否包含指定字符串 + if (options.uri.path.contains(RequestConfig.hwProxyKeywords)) { + // 设置代理地址 + _dio.httpClientAdapter = DefaultHttpClientAdapter(); + (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { + client.findProxy = (uri) { + print('进入开始代理...............'); + return RequestConfig.proBaseUrlOfHomework; + }; + }; + } + + return handler.next(options); // 继续发送请求 + })); + } +} + +/* +* +*AuthInterceptor +*添加header认证 +* */ +class AuthInterceptor extends Interceptor { + String PLATFORM = "android"; //可根据代码进行判断 + + @override + onRequest(RequestOptions options, RequestInterceptorHandler handler) async { + //获取app版本 + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + String version = packageInfo.version; + if (Platform.isIOS) { + PLATFORM = "ios"; + } else if (Platform.isAndroid) { + PLATFORM = "android"; + } else if (Platform.isWindows) { + PLATFORM = "Windows"; + } else if (Platform.isMacOS) { + PLATFORM = "macos"; + } else if (Platform.isLinux) { + PLATFORM = "Linux"; + } + + Map headers = {}; + headers["Accept-Charset"] = "utf-8"; + headers["Connection"] = "keep-alive"; + headers["Accept"] = "*/*"; + headers["x-version"] = version; //自己更改配置 + headers["x-platform"] = PLATFORM; + //获取存储数据 保存header token + String? token = await FastData.getInstance().getToken(); + + if (null != token && token.isNotEmpty) { + headers["Authorization"] = 'Bearer $token'; //添加自己项目中的请求头 进行保存 + } + + options.headers = headers; + Uri uri = options.uri; + print(uri.toString()); + // 判断请求的 URL 是否包含指定字符串 + if (uri.toString().contains(RequestConfig.hwProxyKeywords)) { + String newPath = options.path.replaceFirst(RegExp('^${RequestConfig.hwProxyKeywords}'), ''); + options.path = newPath; + // 修改请求的 URL + options.baseUrl = RequestConfig.proBaseUrlOfHomework; + } + + // print('请求参数query:' + jsonEncode(options.queryParameters)); + // print('请求参数data:' + jsonEncode(options.data)); + + return super.onRequest(options, handler); + } +} + +/* + * ResponseHandle + * 监听返回响应 + **/ +class ResponseHandle extends Interceptor { + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + const isProd = bool.fromEnvironment('dart.vm.product'); + if (!isProd && RequestConfig.requestDataPrinting) { + printJson(response.data); + } + super.onResponse(response, handler); + } +} + +/* + * ResponseHandle + * 监听返回响应 + **/ +class TheError extends Interceptor { + // 是否有网 + Future isConnected() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + return connectivityResult != ConnectivityResult.none; + } + + @override + Future onError(DioError err, ErrorInterceptorHandler handler) async { + // 自定义一个socket实例,因为dio原生的实例,message属于是只读的 + // 这里是我单独加的,因为默认的dio err实例,的几种类型,缺少无网络情况下的错误提示信息 + if (err.error is SocketException) { + err.error = MyDioSocketException( + err.message.contains('111') ? "服务器拒绝连接,请重试" : err.message, + osError: err.error?.osError, + address: err.error?.address, + port: err.error?.port, + ); + } + // dio默认的错误实例,如果是没有网络,只能得到一个未知错误,无法精准的得知是否是无网络的情况 + if (err.type == DioErrorType.other) { + bool isConnectNetWork = await isConnected(); + if (!isConnectNetWork && err.error is MyDioSocketException) { + err.error.message = "当前网络不可用,请检查您的网络"; + } + } + + // error统一处理 + AppException appException = AppException.create(err); + + // 错误提示 + // debugPrint('DioError===: ${appException.toString()}'); + err = appException; + + return super.onError(err, handler); + } +} + +String getDioErrorTypeStr(DioError err) { + DioErrorType errorType = err.type; + String str; + switch (errorType) { + case DioErrorType.connectTimeout: + str = '连接超时,请检查网络再重试'; + break; + case DioErrorType.sendTimeout: + str = '发送时间超时,请重试'; + break; + case DioErrorType.receiveTimeout: + str = '接收数据超时,请重试'; + break; + case DioErrorType.response: + str = '请求返回失败'; + break; + case DioErrorType.cancel: + str = '请求取消'; + break; + case DioErrorType.other: + str = '请求其他错误'; + break; + } + return str; +} + +/// 自定义异常 +class AppException extends DioError { + final String _message; + final int _code; + AppException(this._code, this._message, StackTrace? theStackTrace, + {required super.requestOptions, super.type, super.error}) { + super.stackTrace = theStackTrace; + } + + @override + String toString() { + return "$_code$_message" + super.toString(); + } + + String getMessage() { + return _message; + } + + factory AppException.create(DioError error) { + switch (error.type) { + case DioErrorType.cancel: + { + return AppException(-1, "请求取消", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case DioErrorType.connectTimeout: + { + return AppException(-1, "连接超时", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case DioErrorType.sendTimeout: + { + return AppException(-1, "请求超时", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case DioErrorType.receiveTimeout: + { + return AppException(-1, "响应超时", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case DioErrorType.response: + { + try { + int? errCode = error.response!.statusCode; + // String errMsg = error.response.statusMessage; + // return ErrorEntity(code: errCode, message: errMsg); + switch (errCode) { + case 400: + { + return AppException(errCode!, "请求语法错误", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 401: + { + var currentContext = TheGlobal.navigatorKey.currentState?.overlay?.context; + if (currentContext != null) { + var routePath = ModalRoute.of(currentContext)?.settings; + if (routePath != null) { + } else { + if (TheGlobal.navigatorKey.currentContext != null) { + ExceptionHandle.toLogin(TheGlobal.navigatorKey.currentContext); + } + } + } + return AppException(errCode!, "登录信息过期,请重新登录", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 403: + { + return AppException(errCode!, "服务器拒绝执行", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 404: + { + return AppException(errCode!, "无法连接服务器", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 405: + { + return AppException(errCode!, "请求方法被禁止", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 500: + { + return AppException(errCode!, "服务器内部错误", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 502: + { + return AppException(errCode!, "无效的请求", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 503: + { + return AppException(errCode!, "服务器挂了", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + case 505: + { + return AppException(errCode!, "不支持HTTP协议请求", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + default: + { + // return ErrorEntity(code: errCode, message: "未知错误"); + return AppException(errCode!, error.response!.statusMessage!, error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + } + } on Exception catch (_) { + return AppException(-1, "未知错误", error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + } + default: + { + return AppException(-1, error.error.message, error.stackTrace, + requestOptions: error.requestOptions, type: error.type, error: error.error); + } + } + } +} + +class ExceptionHandle { + // 异常处理信息 + static void exceptionPrompt(BuildContext context, AppException err, {bool logicHandle = true}) { + ToastUtils.getFluttertoast( + context: context, + msg: err._message, + backgroundColor: Colors.grey[350], + toastLength: Toast.LENGTH_LONG, + ); + if (!logicHandle) return; + switch (err._code) { + case 401: + toLogin(context); // 重新前往登录 + break; + default: + } + } + + static void toLogin(context, {int timeer = 800}) { + setTimeOut( + timeer, + () => RouterManager.router.navigateTo(context, RouterManager.loginPath, clearStack: true), + ); + } +} + +// 这里是一个我单独写得soket错误实例,因为dio默认生成的是不允许修改message内容的,我只能自定义一个使用 +class MyDioSocketException extends SocketException { + String message; + + MyDioSocketException( + this.message, { + osError, + address, + port, + }) : super( + message, + osError: osError, + address: address, + port: port, + ); +} diff --git a/marking_app/lib/utils/the_global.dart b/marking_app/lib/utils/the_global.dart new file mode 100644 index 0000000..63cb902 --- /dev/null +++ b/marking_app/lib/utils/the_global.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +// 全局 GlobalKey +class TheGlobal { + static GlobalKey navigatorKey = GlobalKey(); +} \ No newline at end of file diff --git a/marking_app/lib/utils/toast_utils.dart b/marking_app/lib/utils/toast_utils.dart new file mode 100644 index 0000000..73b7373 --- /dev/null +++ b/marking_app/lib/utils/toast_utils.dart @@ -0,0 +1,82 @@ +/* + * @Author: wangyang 1147192855@qq.com + * @Date: 2022-07-13 16:31:05 + * @LastEditors: wangyang 1147192855@qq.com + * @LastEditTime: 2022-07-28 11:56:13 + * @FilePath: \marking_app\lib\utils\toast_utils.dart + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +class ToastUtils { + static void getFluttertoast({ + required BuildContext context, + required String msg, + Toast? toastLength, + int timeInSecForIosWeb = 1, + double? fontSize, + ToastGravity? gravity, + Color? backgroundColor, + Color? textColor, + }) { + Fluttertoast.showToast( + msg: msg, + toastLength: toastLength ?? Toast.LENGTH_SHORT, + gravity: gravity ?? ToastGravity.CENTER, + timeInSecForIosWeb: timeInSecForIosWeb, + backgroundColor: backgroundColor ?? Theme.of(context).primaryColor, + textColor: textColor ?? Colors.white, + fontSize: fontSize ?? 16.sp, + ); + } + + static void getErrFluttertoast({ + required BuildContext context, + required String msg, + ToastGravity gravity = ToastGravity.BOTTOM, + Color backgroundColor = Colors.grey, + Toast? toastLength, + int timeInSecForIosWeb = 1, + double? fontSize, + Color? textColor, + }) { + ToastUtils.getFluttertoast( + context: context, + msg: msg, + toastLength: toastLength ?? Toast.LENGTH_SHORT, + gravity: gravity, + timeInSecForIosWeb: timeInSecForIosWeb, + backgroundColor: backgroundColor, + textColor: textColor ?? Colors.white, + fontSize: fontSize ?? 16.sp, + ); + } + + static showError(String showMsg) { + EasyLoading.showError(showMsg); + } + + static showLoading() { + EasyLoading.show(status: 'loading...'); + } + + static showInfo(String showMsg, {Duration? duration}) { + EasyLoading.showInfo(showMsg, duration: duration); + } + + static showInfoSimple(String showMsg, int microseconds) { + EasyLoading.showInfo(showMsg, duration: Duration(microseconds: microseconds)); + } + + static showSuccess(String showMsg, {Duration? duration}) { + EasyLoading.showSuccess(showMsg, duration: duration); + } + + static dismiss() { + EasyLoading.dismiss(); + } +} diff --git a/marking_app/linux/.gitignore b/marking_app/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/marking_app/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/marking_app/linux/CMakeLists.txt b/marking_app/linux/CMakeLists.txt new file mode 100644 index 0000000..dcb42e1 --- /dev/null +++ b/marking_app/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "marking_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.marking_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/marking_app/linux/flutter/CMakeLists.txt b/marking_app/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/marking_app/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/marking_app/linux/main.cc b/marking_app/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/marking_app/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/marking_app/linux/my_application.cc b/marking_app/linux/my_application.cc new file mode 100644 index 0000000..a08abc5 --- /dev/null +++ b/marking_app/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "marking_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "marking_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/marking_app/linux/my_application.h b/marking_app/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/marking_app/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/marking_app/macos/.gitignore b/marking_app/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/marking_app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/marking_app/macos/Flutter/Flutter-Debug.xcconfig b/marking_app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/marking_app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/marking_app/macos/Flutter/Flutter-Release.xcconfig b/marking_app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/marking_app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/marking_app/macos/Podfile b/marking_app/macos/Podfile new file mode 100644 index 0000000..dade8df --- /dev/null +++ b/marking_app/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.11' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/marking_app/macos/Runner.xcodeproj/project.pbxproj b/marking_app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..86e4193 --- /dev/null +++ b/marking_app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,632 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 278278CC760A4986C6833DB0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1B0DED05E0A6BF95ADD9279 /* Pods_Runner.framework */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2F600732A20048947F5A198D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* marking_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = marking_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5183485E5B73115D8E731E4D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 5FA8E2709FE83B79FCC7C70C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + B1B0DED05E0A6BF95ADD9279 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 278278CC760A4986C6833DB0 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 31BACF9F04F9841B53805EBE /* Pods */ = { + isa = PBXGroup; + children = ( + 2F600732A20048947F5A198D /* Pods-Runner.debug.xcconfig */, + 5FA8E2709FE83B79FCC7C70C /* Pods-Runner.release.xcconfig */, + 5183485E5B73115D8E731E4D /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 31BACF9F04F9841B53805EBE /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* marking_app.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B1B0DED05E0A6BF95ADD9279 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 30AE482DBE1B84F4B4864EF1 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + A8EEC5382DECFEC169BD12FF /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* marking_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 30AE482DBE1B84F4B4864EF1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + A8EEC5382DECFEC169BD12FF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/marking_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/marking_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/marking_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/marking_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/marking_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a428e67 --- /dev/null +++ b/marking_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/macos/Runner.xcworkspace/contents.xcworkspacedata b/marking_app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/marking_app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/marking_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/marking_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/marking_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/marking_app/macos/Runner/AppDelegate.swift b/marking_app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/marking_app/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..3c4935a Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..ed4cc16 Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..483be61 Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bcbf36d Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..9c0a652 Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..e71a726 Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..8a31fe2 Binary files /dev/null and b/marking_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/marking_app/macos/Runner/Base.lproj/MainMenu.xib b/marking_app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/marking_app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marking_app/macos/Runner/Configs/AppInfo.xcconfig b/marking_app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..515281b --- /dev/null +++ b/marking_app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = marking_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.markingApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. diff --git a/marking_app/macos/Runner/Configs/Debug.xcconfig b/marking_app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/marking_app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/marking_app/macos/Runner/Configs/Release.xcconfig b/marking_app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/marking_app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/marking_app/macos/Runner/Configs/Warnings.xcconfig b/marking_app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/marking_app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/marking_app/macos/Runner/DebugProfile.entitlements b/marking_app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/marking_app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/marking_app/macos/Runner/Info.plist b/marking_app/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/marking_app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/marking_app/macos/Runner/MainFlutterWindow.swift b/marking_app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/marking_app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/marking_app/macos/Runner/Release.entitlements b/marking_app/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/marking_app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/marking_app/pubspec.yaml b/marking_app/pubspec.yaml new file mode 100644 index 0000000..2073a64 --- /dev/null +++ b/marking_app/pubspec.yaml @@ -0,0 +1,174 @@ +name: marking_app +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.93 + +environment: + sdk: ">=2.17.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to woyrk. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + # flutter_screenutil: ^5.7.0 + fluro: ^2.0.5 + flutter_easyrefresh: ^2.2.2 + # easy_refresh: ^3.3.2+1 + retrofit: ^3.3.1 + logger: ^1.1.0 + + flutter_lints: ^2.0.0 + fluttertoast: ^8.2.3 + json_annotation: ^4.8.1 + crypto: ^3.0.2 + shared_preferences: ^2.0.15 + flutter_riverpod: ^2.2.0 + hooks_riverpod: ^2.2.0 + package_info: ^2.0.2 + # 进度条 + percent_indicator: ^4.2.3 + flutter_spinkit: ^5.1.0 + photo_view: ^0.14.0 + flutter_svg: ^1.0.0 + cached_network_image: ^3.3.1 + dotted_border: ^2.0.0+2 + # install_plugin_v2: ^1.0.0 + # permission_handler: ^10.0.0 + permission_handler: ^11.0.1 + flutter_widget_from_html_core: ^0.10.3 + + + # 事件总线 + event_bus: ^2.0.0 + image_picker: ^0.8.6 + flutter_easyloading: ^3.0.5 + wakelock: ^0.6.2 + # 键盘监听 + flutter_keyboard_visibility: ^5.4.0 + # orientation: ^1.3.0 + # audioplayers: ^3.0.1 + # 网络监控 + connectivity_plus: ^3.0.6 + # S3 图像上传 + # minio: ^3.5.0 + url_launcher: ^6.1.11 + collection: ^1.17.0 + path_provider: ^2.0.15 + uuid: ^3.0.7 + # flutter_neumorphic: ^3.0.3 + syncfusion_flutter_sliders: ^21.2.4 + achievement_view: ^1.0.1 + # 防抖节流 + easy_debounce: ^2.0.3 + # 图表 + fl_chart: ^0.62.0 + # flutter 可以使用的hooks + flutter_hooks: ^0.18.6 + # + functional_widget_annotation: ^0.10.0 + # 步骤组件 + easy_stepper: ^0.6.0 + flutter_card_swiper: ^5.1.0 + flutter_echarts: ^2.4.0 + graphic: ^2.2.0 + zoom_widget: ^2.0.1 + dropdown_search: ^5.0.6 + install_plugin: ^2.0.1 + flutter_screenutil: 5.7.0 + # get: ^4.6.5 + # multi_select_flutter: ^4.1.3 + dropdown_button2: ^2.3.9 + # copy + clipboard: ^0.1.3 + image: ^4.1.3 + awesome_dialog: ^3.1.0 + badges: ^3.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + retrofit_generator: ^4.0.1 + build_runner: ^2.4.8 + json_serializable: ^6.3.1 + # 分离样式 + functional_widget: ^0.10.1 + + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + assets: + - assets/images/ + - assets/images/2.0x/ + - assets/images/3.0x/ + - assets/images/4.0x/ + # - assets/sounds/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + fonts: + - family: AlibabaIcon + fonts: + - asset: assets/icons/iconfont.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/marking_app/web/favicon.png b/marking_app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/marking_app/web/favicon.png differ diff --git a/marking_app/web/icons/Icon-192.png b/marking_app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/marking_app/web/icons/Icon-192.png differ diff --git a/marking_app/web/icons/Icon-512.png b/marking_app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/marking_app/web/icons/Icon-512.png differ diff --git a/marking_app/web/icons/Icon-maskable-192.png b/marking_app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/marking_app/web/icons/Icon-maskable-192.png differ diff --git a/marking_app/web/icons/Icon-maskable-512.png b/marking_app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/marking_app/web/icons/Icon-maskable-512.png differ diff --git a/marking_app/web/index.html b/marking_app/web/index.html new file mode 100644 index 0000000..232bbec --- /dev/null +++ b/marking_app/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + marking_app + + + + + + + + + + diff --git a/marking_app/web/manifest.json b/marking_app/web/manifest.json new file mode 100644 index 0000000..613dbc5 --- /dev/null +++ b/marking_app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "marking_app", + "short_name": "marking_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/marking_app/windows/.gitignore b/marking_app/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/marking_app/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/marking_app/windows/CMakeLists.txt b/marking_app/windows/CMakeLists.txt new file mode 100644 index 0000000..5e993c8 --- /dev/null +++ b/marking_app/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(marking_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "marking_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/marking_app/windows/flutter/CMakeLists.txt b/marking_app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/marking_app/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/marking_app/windows/runner/CMakeLists.txt b/marking_app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..b9e550f --- /dev/null +++ b/marking_app/windows/runner/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/marking_app/windows/runner/Runner.rc b/marking_app/windows/runner/Runner.rc new file mode 100644 index 0000000..11f2df6 --- /dev/null +++ b/marking_app/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "marking_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "marking_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "marking_app.exe" "\0" + VALUE "ProductName", "marking_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/marking_app/windows/runner/flutter_window.cpp b/marking_app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b43b909 --- /dev/null +++ b/marking_app/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/marking_app/windows/runner/flutter_window.h b/marking_app/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/marking_app/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/marking_app/windows/runner/main.cpp b/marking_app/windows/runner/main.cpp new file mode 100644 index 0000000..c2e50fb --- /dev/null +++ b/marking_app/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"marking_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/marking_app/windows/runner/resource.h b/marking_app/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/marking_app/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/marking_app/windows/runner/resources/app_icon.ico b/marking_app/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/marking_app/windows/runner/resources/app_icon.ico differ diff --git a/marking_app/windows/runner/runner.exe.manifest b/marking_app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..c977c4a --- /dev/null +++ b/marking_app/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/marking_app/windows/runner/utils.cpp b/marking_app/windows/runner/utils.cpp new file mode 100644 index 0000000..f5bf9fa --- /dev/null +++ b/marking_app/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/marking_app/windows/runner/utils.h b/marking_app/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/marking_app/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/marking_app/windows/runner/win32_window.cpp b/marking_app/windows/runner/win32_window.cpp new file mode 100644 index 0000000..c10f08d --- /dev/null +++ b/marking_app/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/marking_app/windows/runner/win32_window.h b/marking_app/windows/runner/win32_window.h new file mode 100644 index 0000000..17ba431 --- /dev/null +++ b/marking_app/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_