Przeglądaj źródła

Initial commit: Flutter math tool project

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nh99sysrj6-blip 6 miesięcy temu
commit
f6fc01013e
100 zmienionych plików z 9898 dodań i 0 usunięć
  1. 46 0
      .gitignore
  2. 28 0
      analysis_options.yaml
  3. 15 0
      android/.gitignore
  4. 62 0
      android/app/build.gradle.kts
  5. 7 0
      android/app/src/debug/AndroidManifest.xml
  6. 29 0
      android/app/src/main/AndroidManifest.xml
  7. 5 0
      android/app/src/main/kotlin/com/example/mathtool2/MainActivity.kt
  8. 12 0
      android/app/src/main/res/drawable-v21/launch_background.xml
  9. 12 0
      android/app/src/main/res/drawable/launch_background.xml
  10. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  11. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  12. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  13. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  14. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  15. 18 0
      android/app/src/main/res/values-night/styles.xml
  16. 18 0
      android/app/src/main/res/values/styles.xml
  17. 7 0
      android/app/src/profile/AndroidManifest.xml
  18. 24 0
      android/build.gradle.kts
  19. 6 0
      android/gradle.properties
  20. 5 0
      android/gradle/wrapper/gradle-wrapper.properties
  21. 26 0
      android/settings.gradle.kts
  22. 0 0
      assets/javascripts/math.js
  23. 34 0
      ios/.gitignore
  24. 26 0
      ios/Flutter/AppFrameworkInfo.plist
  25. 1 0
      ios/Flutter/Debug.xcconfig
  26. 1 0
      ios/Flutter/Release.xcconfig
  27. 616 0
      ios/Runner.xcodeproj/project.pbxproj
  28. 7 0
      ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  29. 8 0
      ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  30. 8 0
      ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  31. 101 0
      ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  32. 7 0
      ios/Runner.xcworkspace/contents.xcworkspacedata
  33. 8 0
      ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  34. 8 0
      ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  35. 13 0
      ios/Runner/AppDelegate.swift
  36. 122 0
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  37. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  38. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  39. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  40. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  41. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  42. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  43. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  44. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  45. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  46. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  47. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  48. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  49. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  50. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  51. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  52. 23 0
      ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  53. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
  54. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
  55. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
  56. 5 0
      ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  57. 37 0
      ios/Runner/Base.lproj/LaunchScreen.storyboard
  58. 26 0
      ios/Runner/Base.lproj/Main.storyboard
  59. 49 0
      ios/Runner/Info.plist
  60. 1 0
      ios/Runner/Runner-Bridging-Header.h
  61. 12 0
      ios/RunnerTests/RunnerTests.swift
  62. 7 0
      lib/main.dart
  63. 453 0
      lib/math_tools_app.dart
  64. 852 0
      lib/tools/advanced_ide.dart
  65. 260 0
      lib/tools/binomial_distribution.dart
  66. 291 0
      lib/tools/bisection_method.dart
  67. 268 0
      lib/tools/chi_square.dart
  68. 137 0
      lib/tools/circle_equation.dart
  69. 453 0
      lib/tools/convolution_calculator.dart
  70. 108 0
      lib/tools/cuo_wei_xiang_jian.dart
  71. 354 0
      lib/tools/derivative_calculator.dart
  72. 449 0
      lib/tools/function_plotter.dart
  73. 276 0
      lib/tools/histogram.dart
  74. 269 0
      lib/tools/hypergeometric_distribution.dart
  75. 116 0
      lib/tools/mixed_variance.dart
  76. 408 0
      lib/tools/parametric_plotter.dart
  77. 240 0
      lib/tools/regression.dart
  78. 692 0
      lib/tools/statistical_charts.dart
  79. 269 0
      lib/tools/symmetry.dart
  80. 350 0
      lib/tools/vector_normal.dart
  81. 128 0
      lib/utils/math_utils.dart
  82. 769 0
      lib/utils/rationalsqrt2.dart
  83. 1 0
      linux/.gitignore
  84. 128 0
      linux/CMakeLists.txt
  85. 88 0
      linux/flutter/CMakeLists.txt
  86. 19 0
      linux/flutter/generated_plugin_registrant.cc
  87. 15 0
      linux/flutter/generated_plugin_registrant.h
  88. 25 0
      linux/flutter/generated_plugins.cmake
  89. 26 0
      linux/runner/CMakeLists.txt
  90. 6 0
      linux/runner/main.cc
  91. 144 0
      linux/runner/my_application.cc
  92. 18 0
      linux/runner/my_application.h
  93. 7 0
      macos/.gitignore
  94. 1 0
      macos/Flutter/Flutter-Debug.xcconfig
  95. 1 0
      macos/Flutter/Flutter-Release.xcconfig
  96. 18 0
      macos/Flutter/GeneratedPluginRegistrant.swift
  97. 705 0
      macos/Runner.xcodeproj/project.pbxproj
  98. 8 0
      macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  99. 99 0
      macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  100. 7 0
      macos/Runner.xcworkspace/contents.xcworkspacedata

+ 46 - 0
.gitignore

@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+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-dependencies
+.metadata
+.pub-cache/
+.pub/
+/build/
+/coverage/
+
+# 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

+ 28 - 0
analysis_options.yaml

@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at https://dart.dev/lints.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options

+ 15 - 0
android/.gitignore

@@ -0,0 +1,15 @@
+gradle-wrapper.jar
+/.gradle
+/.kotlin
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks

+ 62 - 0
android/app/build.gradle.kts

@@ -0,0 +1,62 @@
+import java.util.Properties
+import java.io.FileInputStream
+
+plugins {
+    id("com.android.application")
+    id("kotlin-android")
+    id("dev.flutter.flutter-gradle-plugin")
+}
+
+// 读取 keystore.properties 文件
+val keystorePropertiesFile = rootProject.file("key.properties")
+val keystoreProperties = Properties()
+if (keystorePropertiesFile.exists()) {
+    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
+}
+
+android {
+    namespace = "com.example.mathtool2"
+    compileSdk = flutter.compileSdkVersion
+    ndkVersion = flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_11
+        targetCompatibility = JavaVersion.VERSION_11
+    }
+
+    kotlinOptions {
+        jvmTarget = JavaVersion.VERSION_11.toString()
+    }
+
+    signingConfigs {
+        create("release") {
+            keyAlias = keystoreProperties.getProperty("keyAlias")
+            keyPassword = keystoreProperties.getProperty("keyPassword")
+            storeFile = file(keystoreProperties.getProperty("storeFile"))
+            storePassword = keystoreProperties.getProperty("storePassword")
+        }
+    }
+
+    defaultConfig {
+        applicationId = "com.nonvioce.mathtool2"
+        minSdk = flutter.minSdkVersion
+        targetSdk = flutter.targetSdkVersion
+        versionCode = flutter.versionCode
+        versionName = flutter.versionName
+    }
+
+    buildTypes {
+        release {
+            signingConfig = signingConfigs.getByName("release")
+            isMinifyEnabled = true
+            isShrinkResources = true
+        }
+        debug {
+            signingConfig = signingConfigs.getByName("debug")
+        }
+    }
+}
+
+flutter {
+    source = "../.."
+}

+ 7 - 0
android/app/src/debug/AndroidManifest.xml

@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>

+ 29 - 0
android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,29 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application android:label="某人的小工具" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">
+        <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme"/>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data android:name="flutterEmbedding" android:value="2"/>
+    </application>
+    <!-- Required to query activities that can process text, see:
+         https://developer.android.com/training/package-visibility and
+         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
+
+         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.PROCESS_TEXT"/>
+            <data android:mimeType="text/plain"/>
+        </intent>
+    </queries>
+</manifest>

+ 5 - 0
android/app/src/main/kotlin/com/example/mathtool2/MainActivity.kt

@@ -0,0 +1,5 @@
+package com.example.mathtool2
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()

+ 12 - 0
android/app/src/main/res/drawable-v21/launch_background.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>

+ 12 - 0
android/app/src/main/res/drawable/launch_background.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 18 - 0
android/app/src/main/res/values-night/styles.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>

+ 18 - 0
android/app/src/main/res/values/styles.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>

+ 7 - 0
android/app/src/profile/AndroidManifest.xml

@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>

+ 24 - 0
android/build.gradle.kts

@@ -0,0 +1,24 @@
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+val newBuildDir: Directory =
+    rootProject.layout.buildDirectory
+        .dir("../../build")
+        .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+    val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+    project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+    project.evaluationDependsOn(":app")
+}
+
+tasks.register<Delete>("clean") {
+    delete(rootProject.layout.buildDirectory)
+}

+ 6 - 0
android/gradle.properties

@@ -0,0 +1,6 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+systemProp.javax.net.ssl.trustStore=
+systemProp.javax.net.ssl.trustStorePassword=
+systemProp.javax.net.ssl.keyStore=
+systemProp.javax.net.ssl.keyStorePassword=

+ 5 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

+ 26 - 0
android/settings.gradle.kts

@@ -0,0 +1,26 @@
+pluginManagement {
+    val flutterSdkPath =
+        run {
+            val properties = java.util.Properties()
+            file("local.properties").inputStream().use { properties.load(it) }
+            val flutterSdkPath = properties.getProperty("flutter.sdk")
+            require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+            flutterSdkPath
+        }
+
+    includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+plugins {
+    id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+    id("com.android.application") version "8.9.1" apply false
+    id("org.jetbrains.kotlin.android") version "2.1.0" apply false
+}
+
+include(":app")

Plik diff jest za duży
+ 0 - 0
assets/javascripts/math.js


+ 34 - 0
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

+ 26 - 0
ios/Flutter/AppFrameworkInfo.plist

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>13.0</string>
+</dict>
+</plist>

+ 1 - 0
ios/Flutter/Debug.xcconfig

@@ -0,0 +1 @@
+#include "Generated.xcconfig"

+ 1 - 0
ios/Flutter/Release.xcconfig

@@ -0,0 +1 @@
+#include "Generated.xcconfig"

+ 616 - 0
ios/Runner.xcodeproj/project.pbxproj

@@ -0,0 +1,616 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 54;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+		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 */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+			remoteInfo = Runner;
+		};
+/* End PBXContainerItemProxy 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 */
+		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
+		331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
+		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		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 = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		331C8082294A63A400263BE5 /* RunnerTests */ = {
+			isa = PBXGroup;
+			children = (
+				331C807B294A618700263BE5 /* RunnerTests.swift */,
+			);
+			path = RunnerTests;
+			sourceTree = "<group>";
+		};
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+				331C8082294A63A400263BE5 /* RunnerTests */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+				331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		331C8080294A63A400263BE5 /* RunnerTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+			buildPhases = (
+				331C807D294A63A400263BE5 /* Sources */,
+				331C807F294A63A400263BE5 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				331C8086294A63A400263BE5 /* PBXTargetDependency */,
+			);
+			name = RunnerTests;
+			productName = RunnerTests;
+			productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+			);
+			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 = {
+				BuildIndependentTargetsInParallel = YES;
+				LastUpgradeCheck = 1510;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					331C8080294A63A400263BE5 = {
+						CreatedOnToolsVersion = 14.0;
+						TestTargetID = 97C146ED1CF9000F007C117D;
+					};
+					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 */,
+				331C8080294A63A400263BE5 /* RunnerTests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		331C807F294A63A400263BE5 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		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";
+		};
+		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";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		331C807D294A63A400263BE5 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 97C146ED1CF9000F007C117D /* Runner */;
+			targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		249021D3217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				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;
+				ENABLE_USER_SCRIPT_SANDBOXING = NO;
+				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 = 13.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;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		331C8088294A63A400263BE5 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+			};
+			name = Debug;
+		};
+		331C8089294A63A400263BE5 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+			};
+			name = Release;
+		};
+		331C808A294A63A400263BE5 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				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;
+				ENABLE_USER_SCRIPT_SANDBOXING = NO;
+				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 = 13.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;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				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;
+				ENABLE_USER_SCRIPT_SANDBOXING = NO;
+				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 = 13.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;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				331C8088294A63A400263BE5 /* Debug */,
+				331C8089294A63A400263BE5 /* Release */,
+				331C808A294A63A400263BE5 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		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 */;
+}

+ 7 - 0
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>

+ 8 - 0
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>

+ 101 - 0
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1510"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "331C8080294A63A400263BE5"
+               BuildableName = "RunnerTests.xctest"
+               BlueprintName = "RunnerTests"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      enableGPUValidationMode = "1"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 7 - 0
ios/Runner.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>

+ 8 - 0
ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>

+ 13 - 0
ios/Runner/AppDelegate.swift

@@ -0,0 +1,13 @@
+import Flutter
+import UIKit
+
+@main
+@objc class AppDelegate: FlutterAppDelegate {
+  override func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    GeneratedPluginRegistrant.register(with: self)
+    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+  }
+}

+ 122 - 0
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "Icon-App-1024x1024@1x.png",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png


+ 23 - 0
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png


+ 5 - 0
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md

@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

+ 37 - 0
ios/Runner/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>

+ 26 - 0
ios/Runner/Base.lproj/Main.storyboard

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>

+ 49 - 0
ios/Runner/Info.plist

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>CFBundleDevelopmentRegion</key>
+    <string>$(DEVELOPMENT_LANGUAGE)</string>
+    <key>CFBundleDisplayName</key>
+    <string>某人的小工具</string>
+    <key>CFBundleExecutable</key>
+    <string>$(EXECUTABLE_NAME)</string>
+    <key>CFBundleIdentifier</key>
+    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundleName</key>
+    <string>某人的小工具</string>
+    <key>CFBundlePackageType</key>
+    <string>APPL</string>
+    <key>CFBundleShortVersionString</key>
+    <string>$(FLUTTER_BUILD_NAME)</string>
+    <key>CFBundleSignature</key>
+    <string>????</string>
+    <key>CFBundleVersion</key>
+    <string>$(FLUTTER_BUILD_NUMBER)</string>
+    <key>LSRequiresIPhoneOS</key>
+    <true/>
+    <key>UILaunchStoryboardName</key>
+    <string>LaunchScreen</string>
+    <key>UIMainStoryboardFile</key>
+    <string>Main</string>
+    <key>UISupportedInterfaceOrientations</key>
+    <array>
+      <string>UIInterfaceOrientationPortrait</string>
+      <string>UIInterfaceOrientationLandscapeLeft</string>
+      <string>UIInterfaceOrientationLandscapeRight</string>
+    </array>
+    <key>UISupportedInterfaceOrientations~ipad</key>
+    <array>
+      <string>UIInterfaceOrientationPortrait</string>
+      <string>UIInterfaceOrientationPortraitUpsideDown</string>
+      <string>UIInterfaceOrientationLandscapeLeft</string>
+      <string>UIInterfaceOrientationLandscapeRight</string>
+    </array>
+    <key>CADisableMinimumFrameDurationOnPhone</key>
+    <true/>
+    <key>UIApplicationSupportsIndirectInputEvents</key>
+    <true/>
+  </dict>
+</plist>

+ 1 - 0
ios/Runner/Runner-Bridging-Header.h

@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"

+ 12 - 0
ios/RunnerTests/RunnerTests.swift

@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+  func testExample() {
+    // If you add code to the Runner application, consider adding tests here.
+    // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+  }
+
+}

+ 7 - 0
lib/main.dart

@@ -0,0 +1,7 @@
+// main.dart
+import 'package:flutter/material.dart';
+import 'math_tools_app.dart';
+
+void main() {
+  runApp(MathToolsApp());
+}

+ 453 - 0
lib/math_tools_app.dart

@@ -0,0 +1,453 @@
+// math_tools_app.dart
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart'; 
+import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // 添加这行
+
+import 'tools/cuo_wei_xiang_jian.dart';
+import 'tools/binomial_distribution.dart';
+import 'tools/hypergeometric_distribution.dart';
+import 'tools/chi_square.dart';
+import 'tools/regression.dart';
+import 'tools/mixed_variance.dart';
+import 'tools/histogram.dart';
+import 'tools/symmetry.dart';
+import 'tools/circle_equation.dart';
+import 'tools/vector_normal.dart';
+import 'tools/bisection_method.dart';
+import 'tools/function_plotter.dart';
+import 'tools/parametric_plotter.dart';
+import 'tools/derivative_calculator.dart';
+import 'tools/advanced_ide.dart';
+import 'tools/convolution_calculator.dart';
+import 'tools/statistical_charts.dart';
+
+class MathToolsApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: '某人的小工具',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+        visualDensity: VisualDensity.adaptivePlatformDensity,
+      ),
+      home: MainPage(),
+    );
+  }
+}
+
+class MainPage extends StatefulWidget {
+  @override
+  _MainPageState createState() => _MainPageState();
+}
+
+class _MainPageState extends State<MainPage> {
+  // 定义渐变色列表
+  final List<Color> colorGradient = [
+  Color(0xFFff6b6b), // 红色
+  Color(0xFFffa726), // 橙色
+  Color(0xFFffee58), // 黄色
+  Color(0xFF66bb6a), // 绿色
+  Color(0xFF42a5f5), // 蓝色
+];
+
+  final List<MathTool> allTools = [
+    MathTool('错位相减法', FontAwesomeIcons.subscript, CuoWeiXiangJianPage()),
+    MathTool('二项分布', FontAwesomeIcons.dice, BinomialDistributionPage()), // 改为骰子图标
+    MathTool('超几何分布', FontAwesomeIcons.chartPie, HypergeometricDistributionPage()),
+    MathTool('卷积分布列', FontAwesomeIcons.layerGroup, ConvolutionCalculatorPage()),
+    MathTool('卡方检验', FontAwesomeIcons.checkSquare, ChiSquarePage()),
+    MathTool('线性回归', FontAwesomeIcons.chartLine, RegressionPage()),
+    MathTool('混合方差', FontAwesomeIcons.balanceScale, MixedVariancePage()),
+    MathTool('直方图统计', FontAwesomeIcons.chartBar, HistogramPage()),
+    MathTool('统计图表', FontAwesomeIcons.chartArea, StatisticalChartsPage()),
+    MathTool('对称变换', FontAwesomeIcons.exchangeAlt, SymmetryPage()),
+    MathTool('圆的方程', FontAwesomeIcons.circle, CircleEquationPage()),
+    MathTool('三点法向量', FontAwesomeIcons.cube, VectorNormalPage()),
+    MathTool('二分法求根', FontAwesomeIcons.search, BisectionMethodPage()),
+    MathTool('函数图像绘制', FontAwesomeIcons.projectDiagram, FunctionPlotterPage()),
+    MathTool('参数方程绘图', FontAwesomeIcons.waveSquare, ParametricPlotterPage()),
+    MathTool('导数计算器', FontAwesomeIcons.calculator, DerivativeCalculatorPage()),
+    MathTool('高级', FontAwesomeIcons.code, AdvancedIdePage()),
+    MathTool('关于', FontAwesomeIcons.infoCircle, AboutPage()),
+  ];
+
+  List<MathTool> filteredTools = [];
+  TextEditingController searchController = TextEditingController();
+  FocusNode searchFocusNode = FocusNode();
+
+  @override
+  void initState() {
+    super.initState();
+    filteredTools = List.from(allTools);
+    searchController.addListener(_onSearchChanged);
+  }
+
+  @override
+  void dispose() {
+    searchController.dispose();
+    searchFocusNode.dispose();
+    super.dispose();
+  }
+
+  void _onSearchChanged() {
+    setState(() {
+      if (searchController.text.isEmpty) {
+        filteredTools = List.from(allTools);
+      } else {
+        filteredTools = allTools.where((tool) {
+          return tool.name.toLowerCase().contains(searchController.text.toLowerCase());
+        }).toList();
+      }
+    });
+  }
+
+  void _clearSearch() {
+    setState(() {
+      searchController.clear();
+      filteredTools = List.from(allTools);
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Row(
+          children: [
+            Text('某人的小工具', style: TextStyle(color: Colors.black)),
+            Expanded(
+              child: Container(
+                margin: EdgeInsets.only(left: 16),
+                height: 40,
+                child: TextField(
+                  controller: searchController,
+                  focusNode: searchFocusNode,
+                  decoration: InputDecoration(
+                    hintText: '搜索工具...',
+                    hintStyle: TextStyle(fontSize: 14),
+                    prefixIcon: Icon(Icons.search, size: 20, color: Colors.grey),
+                    suffixIcon: searchController.text.isNotEmpty
+                        ? IconButton(
+                            icon: Icon(Icons.clear, size: 20, color: Colors.grey),
+                            onPressed: _clearSearch,
+                          )
+                        : null,
+                    contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
+                    filled: true,
+                    fillColor: Colors.grey[100],
+                    border: OutlineInputBorder(
+                      borderRadius: BorderRadius.circular(20),
+                      borderSide: BorderSide.none,
+                    ),
+                    enabledBorder: OutlineInputBorder(
+                      borderRadius: BorderRadius.circular(20),
+                      borderSide: BorderSide.none,
+                    ),
+                    focusedBorder: OutlineInputBorder(
+                      borderRadius: BorderRadius.circular(20),
+                      borderSide: BorderSide(color: Colors.blue, width: 1),
+                    ),
+                  ),
+                  style: TextStyle(fontSize: 14),
+                ),
+              ),
+            ),
+          ],
+        ),
+        backgroundColor: Colors.white,
+        elevation: 1,
+      ),
+      body: filteredTools.isEmpty
+          ? Center(
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  FaIcon(FontAwesomeIcons.search, size: 60, color: Colors.grey[400]),
+                  SizedBox(height: 16),
+                  Text(
+                    '没有找到相关工具',
+                    style: TextStyle(
+                      fontSize: 18,
+                      color: Colors.grey[600],
+                    ),
+                  ),
+                  SizedBox(height: 8),
+                  Text(
+                    '请尝试使用其他关键词搜索',
+                    style: TextStyle(
+                      fontSize: 14,
+                      color: Colors.grey[500],
+                    ),
+                  ),
+                  SizedBox(height: 20),
+                  ElevatedButton(
+                    onPressed: _clearSearch,
+                    style: ElevatedButton.styleFrom(
+                      backgroundColor: Colors.blue,
+                      shape: RoundedRectangleBorder(
+                        borderRadius: BorderRadius.circular(20),
+                      ),
+                    ),
+                    child: Text('清空搜索', style: TextStyle(color: Colors.white)),
+                  ),
+                ],
+              ),
+            )
+          : GridView.builder(
+              padding: EdgeInsets.all(16),
+              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+                crossAxisCount: 2,
+                crossAxisSpacing: 16,
+                mainAxisSpacing: 16,
+                childAspectRatio: 1.2,
+              ),
+              itemCount: filteredTools.length,
+              itemBuilder: (context, index) {
+                // 根据索引计算渐变色
+                final colorIndex = (index / filteredTools.length * (colorGradient.length - 1)).clamp(0, colorGradient.length - 1).toInt();
+                final nextColorIndex = (colorIndex + 1).clamp(0, colorGradient.length - 1);
+                final progress = (index / filteredTools.length * (colorGradient.length - 1)) - colorIndex;
+                
+                final color = Color.lerp(colorGradient[colorIndex], colorGradient[nextColorIndex], progress)!;
+                
+                return _buildToolCard(filteredTools[index], color, context);
+              },
+            ),
+    );
+  }
+
+  Widget _buildToolCard(MathTool tool, Color color, BuildContext context) {
+  return Card(
+    elevation: 4,
+    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+    child: InkWell(
+      borderRadius: BorderRadius.circular(12),
+      onTap: () {
+        Navigator.push(
+          context,
+          MaterialPageRoute(builder: (context) => tool.page),
+        );
+      },
+      child: Container(
+        decoration: BoxDecoration(
+          borderRadius: BorderRadius.circular(12),
+          color: Colors.white, // 改为白色背景
+        ),
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            FaIcon(tool.icon, size: 40, color: color),
+            SizedBox(height: 8),
+            Text(
+              tool.name,
+              textAlign: TextAlign.center,
+              style: TextStyle(
+                fontSize: 16,
+                fontWeight: FontWeight.bold,
+                color: color,
+              ),
+            ),
+          ],
+        ),
+      ),
+    ),
+  );
+}
+}
+
+class MathTool {
+  final String name;
+  final IconData icon;
+  final Widget page;
+
+  MathTool(this.name, this.icon, this.page);
+}
+
+// 关于页面
+class AboutPage extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('关于', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+      ),
+      body: Padding(
+        padding: EdgeInsets.all(20),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Center(
+              child: FaIcon(
+                FontAwesomeIcons.calculator,
+                size: 80,
+                color: Colors.blue,
+              ),
+            ),
+            SizedBox(height: 20),
+            Center(
+              child: Text(
+                '某人的小工具',
+                style: TextStyle(
+                  fontSize: 28,
+                  fontWeight: FontWeight.bold,
+                  color: Colors.blue,
+                ),
+              ),
+            ),
+            SizedBox(height: 10),
+            Center(
+              child: Text(
+                '版本 1.1.0',
+                style: TextStyle(
+                  fontSize: 16,
+                  color: Colors.grey[600],
+                ),
+              ),
+            ),
+            SizedBox(height: 30),
+            Text(
+              '应用介绍',
+              style: TextStyle(
+                fontSize: 20,
+                fontWeight: FontWeight.bold,
+              ),
+            ),
+            SizedBox(height: 10),
+            Text(
+              '某人的小工具是一款专为高中教师设计的验算工具,'
+              '提供了多种实用的数学计算和统计工具,帮助您更轻松地解决数学问题。'
+              '由于是个人使用,主打的就是能用就行。',
+              style: TextStyle(fontSize: 16, height: 1.5),
+            ),
+            SizedBox(height: 20),
+            Text(
+              '功能特点',
+              style: TextStyle(
+                fontSize: 20,
+                fontWeight: FontWeight.bold,
+              ),
+            ),
+            SizedBox(height: 10),
+            Expanded(
+              child: ListView(
+                children: [
+                  _buildFeatureItem('统计概率', '分布列计算、数字特征'),
+                  _buildFeatureItem('数列计算', '错位相减'),
+                  _buildFeatureItem('解析几何', '对称变换、圆的方程'),
+                  _buildFeatureItem('函数导数', '图像绘制、导数求解'),
+                  _buildFeatureItem('高级功能', '手动编程'),
+                ],
+              ),
+            ),
+            SizedBox(height: 20),
+            Center(
+              child: Column(
+                children: [
+                  Text(
+                    '个人使用,严禁传播',
+                    style: TextStyle(
+                      fontSize: 14,
+                      color: Colors.grey[600],
+                    ),
+                    textAlign: TextAlign.center,
+                  ),
+                  SizedBox(height: 8),
+                  // 邮箱复制按钮
+                  InkWell(
+                    onTap: () {
+                      _copyToClipboard('nonvioce@qq.com', context);
+                    },
+                    child: Container(
+                      padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+                      decoration: BoxDecoration(
+                        color: Colors.blue[50],
+                        borderRadius: BorderRadius.circular(20),
+                        border: Border.all(color: Colors.blue[200]!),
+                      ),
+                      child: Row(
+                        mainAxisSize: MainAxisSize.min,
+                        children: [
+                          FaIcon(FontAwesomeIcons.envelope, size: 16, color: Colors.blue[700]),
+                          SizedBox(width: 6),
+                          Text(
+                            '反馈邮箱:nonvioce@qq.com',
+                            style: TextStyle(
+                              fontSize: 14,
+                              color: Colors.blue[700],
+                              fontWeight: FontWeight.w500,
+                            ),
+                          ),
+                          SizedBox(width: 4),
+                          FaIcon(FontAwesomeIcons.copy, size: 14, color: Colors.blue[700]),
+                        ],
+                      ),
+                    ),
+                  ),
+                  SizedBox(height: 10),
+                  Text(
+                    '©保留一切权利',
+                    style: TextStyle(
+                      fontSize: 12,
+                      color: Colors.grey[600],
+                      fontStyle: FontStyle.italic,
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildFeatureItem(String title, String description) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          FaIcon(FontAwesomeIcons.checkCircle, color: Colors.green, size: 20),
+          SizedBox(width: 10),
+          Expanded(
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: [
+                Text(
+                  title,
+                  style: TextStyle(
+                    fontSize: 16,
+                    fontWeight: FontWeight.w500,
+                  ),
+                ),
+                Text(
+                  description,
+                  style: TextStyle(
+                    fontSize: 14,
+                    color: Colors.grey[600],
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  // 复制到剪贴板的函数
+  void _copyToClipboard(String text, BuildContext context) {
+    Clipboard.setData(ClipboardData(text: text));
+    
+    // 显示复制成功的提示
+    ScaffoldMessenger.of(context).showSnackBar(
+      SnackBar(
+        content: Text('邮箱地址已复制到剪贴板'),
+        duration: Duration(seconds: 2),
+        behavior: SnackBarBehavior.floating,
+      ),
+    );
+  }
+}

+ 852 - 0
lib/tools/advanced_ide.dart

@@ -0,0 +1,852 @@
+// tools/advanced_ide.dart
+import 'package:flutter/material.dart';
+import 'package:flutter_code_editor/flutter_code_editor.dart';
+import 'package:flutter_js/flutter_js.dart';
+import 'package:flutter/services.dart';
+import 'package:highlight/languages/javascript.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:path/path.dart' as path;
+import 'package:file_picker/file_picker.dart';
+import 'dart:io';
+import 'dart:convert';
+import 'dart:typed_data';
+import 'package:flutter_highlight/themes/github.dart'; // 确保这个导入存在
+import 'package:flutter_highlight/themes/vs.dart'; // 添加更多主题选择
+import 'package:flutter_highlight/themes/darcula.dart';
+
+class AdvancedIdePage extends StatefulWidget {
+  @override
+  _AdvancedIdePageState createState() => _AdvancedIdePageState();
+}
+
+class _AdvancedIdePageState extends State<AdvancedIdePage> {
+  late CodeController _codeController;
+  JavascriptRuntime? _jsRuntime;
+  String _output = '';
+  bool _isExecuting = false;
+  bool _isInitialized = false;
+  Map<String, TextStyle> _currentTheme = githubTheme; // 默认主题
+
+  @override
+  void initState() {
+    super.initState();
+    _initializeCodeEditor();
+  }
+
+  Future<void> _initializeCodeEditor() async {
+    try {
+      _codeController = CodeController(
+        text: _getExampleCode(),
+        language: javascript,
+      );
+
+      setState(() {
+        _isInitialized = true;
+      });
+    } catch (e) {
+      print('初始化代码编辑器失败: $e');
+      setState(() {
+        _output = '初始化失败: $e';
+        _isInitialized = true;
+      });
+    }
+  }
+
+  // 切换主题的方法
+  void _changeTheme(String themeName) {
+    setState(() {
+      switch (themeName) {
+        case 'github':
+          _currentTheme = githubTheme;
+          break;
+        case 'vs':
+          _currentTheme = vsTheme;
+          break;
+        case 'darcula':
+          _currentTheme = darculaTheme;
+          break;
+        default:
+          _currentTheme = githubTheme;
+      }
+    });
+  }
+
+  // 在 build 方法中,修改代码编辑器部分:
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('JavaScript IDE', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+        actions: [
+          // 主题切换按钮
+          PopupMenuButton<String>(
+            icon: Icon(Icons.palette, color: Colors.purple, size: 20),
+            itemBuilder: (context) => [
+              PopupMenuItem(
+                value: 'github',
+                child: Row(
+                  children: [
+                    Icon(Icons.brightness_5, color: Colors.blue),
+                    SizedBox(width: 8),
+                    Text('GitHub 主题'),
+                  ],
+                ),
+              ),
+              PopupMenuItem(
+                value: 'vs',
+                child: Row(
+                  children: [
+                    Icon(Icons.brightness_5, color: Colors.grey),
+                    SizedBox(width: 8),
+                    Text('VS 主题'),
+                  ],
+                ),
+              ),
+              PopupMenuItem(
+                value: 'darcula',
+                child: Row(
+                  children: [
+                    Icon(Icons.brightness_2, color: Colors.green),
+                    SizedBox(width: 8),
+                    Text('Darcula 主题'),
+                  ],
+                ),
+              ),
+            ],
+            onSelected: _changeTheme,
+            tooltip: '切换主题',
+          ),
+          IconButton(
+            icon: Icon(Icons.save_as, color: Colors.blue, size: 20),
+            onPressed: _saveAs,
+            tooltip: '另存为',
+          ),
+          IconButton(
+            icon: Icon(Icons.folder_open, color: Colors.blue, size: 20),
+            onPressed: _loadFromFile,
+            tooltip: '从文件加载',
+          ),
+          IconButton(
+            icon: Icon(Icons.play_arrow, color: Colors.green, size: 20),
+            onPressed: _isInitialized ? _executeCode : null,
+            tooltip: '运行代码(全新环境)',
+          ),
+          IconButton(
+            icon: Icon(Icons.clear, color: Colors.red, size: 20),
+            onPressed: _clearOutput,
+            tooltip: '清空输出',
+          ),
+          IconButton(
+            icon: Icon(Icons.refresh, color: Colors.blue, size: 20),
+            onPressed: _resetCode,
+            tooltip: '重置代码',
+          ),
+        ],
+      ),
+      body: !_isInitialized
+      ? Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            CircularProgressIndicator(),
+            SizedBox(height: 16),
+            Text('正在初始化代码编辑器...'),
+          ],
+        ),
+      )
+      : Column(
+        children: [
+          Expanded(
+            flex: 3,
+            child: Container(
+              margin: EdgeInsets.all(8),
+              decoration: BoxDecoration(
+                border: Border.all(color: Colors.blue[300]!),
+                borderRadius: BorderRadius.circular(8),
+              ),
+              child: Column(
+                children: [
+                  Container(
+                    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+                    decoration: BoxDecoration(
+                      color: Colors.blue[50],
+                      borderRadius: BorderRadius.only(
+                        topLeft: Radius.circular(8),
+                        topRight: Radius.circular(8),
+                      ),
+                    ),
+                    child: Row(
+                      children: [
+                        Icon(Icons.code, size: 16, color: Colors.blue[700]!),
+                        SizedBox(width: 8),
+                        Text(
+                          'JavaScript 代码编辑器',
+                          style: TextStyle(
+                            fontSize: 14,
+                            fontWeight: FontWeight.bold,
+                            color: Colors.blue[700]!,
+                          ),
+                        ),
+                        Spacer(),
+                        // 显示当前主题
+                        Container(
+                          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+                          decoration: BoxDecoration(
+                            color: Colors.white,
+                            borderRadius: BorderRadius.circular(12),
+                            border: Border.all(color: Colors.blue[300]!),
+                          ),
+                          child: Text(
+                            _getCurrentThemeName(),
+                            style: TextStyle(fontSize: 12, color: Colors.blue[700]!),
+                          ),
+                        ),
+                        SizedBox(width: 8),
+                        IconButton(
+                          icon: Icon(Icons.info_outline, size: 16),
+                          onPressed: () {
+                            showDialog(
+                              context: context,
+                              builder: (context) => AlertDialog(
+                                title: Text('使用说明'),
+                                content: Text(
+                                  '• 使用本功能需要了解js基本语法\n'
+                                  '• math.js的版本是12.4.3\n'
+                                  '• 每次运行代码都会创建全新的JavaScript环境\n'
+                                  '• 之前的变量和状态不会保留\n'
+                                  '• math.js库会在每次运行时重新加载\n'
+                                  '• 执行速度可能稍慢(因为要重新初始化)\n'
+                                  '• 使用console.log()输出结果\n'
+                                  '• 点击调色板图标可以切换语法高亮主题'
+                                ),
+                                actions: [
+                                  TextButton(
+                                    onPressed: () => Navigator.pop(context),
+                                    child: Text('确定'),
+                                  ),
+                                ],
+                              ),
+                            );
+                          },
+                          tooltip: '使用说明',
+                        ),
+                      ],
+                    ),
+                  ),
+                  Expanded(
+                    child: CodeTheme(  // 添加 CodeTheme 包装
+                      data: CodeThemeData(styles: _currentTheme),
+                      child: SingleChildScrollView(
+                        child: CodeField(
+                          controller: _codeController,
+                          textStyle: TextStyle(fontFamily: 'Monospace', fontSize: 14),
+                          gutterStyle: GutterStyle(
+                            showLineNumbers: true,
+                            showErrors: true,
+                            showFoldingHandles: true,
+                          ),
+                        ),
+                      ),
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ),
+
+          SizedBox(height: 8),
+
+          // 输出区域
+          // 输出区域
+Expanded(
+  flex: 2,
+  child: Container(
+    margin: EdgeInsets.all(8),
+    decoration: BoxDecoration(
+      border: Border.all(color: Colors.grey[300]!),
+      borderRadius: BorderRadius.circular(8),
+    ),
+    child: Column(
+      children: [
+        Container(
+          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+          decoration: BoxDecoration(
+            color: Colors.grey[100],
+            borderRadius: BorderRadius.only(
+              topLeft: Radius.circular(8),
+              topRight: Radius.circular(8),
+            ),
+          ),
+          child: Row(
+            children: [
+              Icon(Icons.output, size: 16, color: Colors.grey[600]!),
+              SizedBox(width: 8),
+              Text(
+                '执行输出',
+                style: TextStyle(
+                  fontSize: 14,
+                  fontWeight: FontWeight.bold,
+                  color: Colors.grey[600]!,
+                ),
+              ),
+              Spacer(),
+              if (_isExecuting)
+                Row(
+                  children: [
+                    SizedBox(
+                      width: 16,
+                      height: 16,
+                      child: CircularProgressIndicator(strokeWidth: 2),
+                    ),
+                    SizedBox(width: 8),
+                    Text(
+                      '执行中...',
+                      style: TextStyle(
+                        fontSize: 12,
+                        color: Colors.orange,
+                      ),
+                    ),
+                  ],
+                ),
+            ],
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            padding: EdgeInsets.all(16),
+            child: SelectableText(  // 修改这里:Text -> SelectableText
+              _output,
+              style: TextStyle(
+                fontFamily: 'Monospace',
+                fontSize: 12,
+              ),
+            ),
+          ),
+        ),
+      ],
+    ),
+  ),
+),
+        ],
+      ),
+    );
+  }
+
+  // 获取当前主题名称
+  String _getCurrentThemeName() {
+    if (_currentTheme == githubTheme) return 'GitHub';
+    if (_currentTheme == vsTheme) return 'VS';
+    if (_currentTheme == darculaTheme) return 'Darcula';
+    return '默认';
+  }
+
+  // 其他方法保持不变...
+  Future<void> _initializeJsRuntime() async {
+    try {
+      if (_jsRuntime != null) {
+        _jsRuntime!.dispose();
+        _jsRuntime = null;
+      }
+
+      _jsRuntime = getJavascriptRuntime();
+
+      // 设置非浏览器环境的console重写
+      _setupConsoleOverride();
+
+      // 加载math.js库
+      final String mathJsCode = await rootBundle.loadString('assets/javascripts/math.js');
+      final JsEvalResult mathResult = _jsRuntime!.evaluate(mathJsCode);
+
+      if (mathResult.isError) {
+        print('加载math.js失败: ${mathResult.stringResult}');
+        throw Exception('加载math.js失败: ${mathResult.stringResult}');
+      } else {
+        print('math.js加载成功');
+      }
+    } catch (e) {
+      print('初始化JavaScript运行时失败: $e');
+      if (_jsRuntime != null) {
+        _jsRuntime!.dispose();
+        _jsRuntime = null;
+      }
+      throw e;
+    }
+  }
+
+  void _setupConsoleOverride() {
+  if (_jsRuntime == null) return;
+  
+  const String consoleOverride = '''
+    // 创建全局输出数组
+    var __consoleOutput = [];
+    
+    // 保存原始console方法(如果存在)
+    var originalConsole = {
+      log: typeof console !== 'undefined' ? console.log : function() {},
+      warn: typeof console !== 'undefined' ? console.warn : function() {},
+      error: typeof console !== 'undefined' ? console.error : function() {},
+      info: typeof console !== 'undefined' ? console.info : function() {}
+    };
+    
+    // 重写console方法
+    if (typeof console !== 'undefined') {
+      console.log = function(...args) {
+        originalConsole.log.apply(console, args);
+        const message = args.map(arg => 
+          typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+        ).join(' ');
+        __consoleOutput.push('[LOG] ' + message);
+      };
+      
+      console.warn = function(...args) {
+        originalConsole.warn.apply(console, args);
+        const message = args.map(arg => 
+          typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+        ).join(' ');
+        __consoleOutput.push('[WARN] ' + message);
+      };
+      
+      console.error = function(...args) {
+        originalConsole.error.apply(console, args);
+        const message = args.map(arg => 
+          typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+        ).join(' ');
+        __consoleOutput.push('[ERROR] ' + message);
+      };
+      
+      console.info = function(...args) {
+        originalConsole.info.apply(console, args);
+        const message = args.map(arg => 
+          typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+        ).join(' ');
+        __consoleOutput.push('[INFO] ' + message);
+      };
+    } else {
+      // 如果console不存在,创建它
+      var console = {
+        log: function(...args) {
+          const message = args.map(arg => 
+            typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+          ).join(' ');
+          __consoleOutput.push('[LOG] ' + message);
+        },
+        warn: function(...args) {
+          const message = args.map(arg => 
+            typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+          ).join(' ');
+          __consoleOutput.push('[WARN] ' + message);
+        },
+        error: function(...args) {
+          const message = args.map(arg => 
+            typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+          ).join(' ');
+          __consoleOutput.push('[ERROR] ' + message);
+        },
+        info: function(...args) {
+          const message = args.map(arg => 
+            typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
+          ).join(' ');
+          __consoleOutput.push('[INFO] ' + message);
+        }
+      };
+    }
+    
+    // 清空输出数组的方法
+    var __clearConsoleOutput = function() {
+      __consoleOutput = [];
+    };
+    
+    // 获取输出内容的方法(修复换行问题)
+    var __getConsoleOutput = function() {
+      return __consoleOutput.join('\\n'); // 关键修复:使用单个换行符
+    };
+  ''';
+  
+  _jsRuntime!.evaluate(consoleOverride);
+}
+
+  @override
+  void dispose() {
+    _codeController.dispose();
+    if (_jsRuntime != null) {
+      _jsRuntime!.dispose();
+      _jsRuntime = null;
+    }
+    super.dispose();
+  }
+
+  String _getExampleCode() {
+    return '''// JavaScript IDE
+// math.js 库已自动加载,可以使用所有数学函数
+
+// 创建共享的作用域对象
+const scope = {};
+
+// 1. 基本算术运算
+console.log('1. 基本算术运算:');
+console.log('2 + 3 =', math.evaluate('2 + 3'));
+console.log('10 - 4 =', math.evaluate('10 - 4'));
+console.log('6 * 7 =', math.evaluate('6 * 7'));
+console.log('15 / 3 =', math.evaluate('15 / 3'));
+console.log('2^8 =', math.evaluate('2^8'));
+console.log('10 % 3 =', math.evaluate('10 % 3'));
+console.log('');
+
+// 2. 复杂表达式和运算符优先级
+console.log('2. 复杂表达式和运算符优先级:');
+console.log('2 + 3 * 4 =', math.evaluate('2 + 3 * 4'));
+console.log('(2 + 3) * 4 =', math.evaluate('(2 + 3) * 4'));
+console.log('3 + 4 * 2 / (1 - 5)^2 =', math.evaluate('3 + 4 * 2 / (1 - 5)^2'));
+console.log('');
+
+// 3. 数学常数
+console.log('3. 数学常数:');
+console.log('pi =', math.evaluate('pi'));
+console.log('e =', math.evaluate('e'));
+console.log('Infinity =', math.evaluate('Infinity'));
+console.log('');
+
+// 4. 内置函数
+console.log('4. 内置函数:');
+console.log('sqrt(25) =', math.evaluate('sqrt(25)'));
+console.log('sin(pi/2) =', math.evaluate('sin(pi/2)'));
+console.log('cos(pi) =', math.evaluate('cos(pi)'));
+console.log('tan(0) =', math.evaluate('tan(0)'));
+console.log('log(100) =', math.evaluate('log(100)'));
+console.log('log(100, 10) =', math.evaluate('log(100, 10)'));
+console.log('exp(2) =', math.evaluate('exp(2)'));
+console.log('abs(-5.5) =', math.evaluate('abs(-5.5)'));
+console.log('round(3.7) =', math.evaluate('round(3.7)'));
+console.log('floor(4.9) =', math.evaluate('floor(4.9)'));
+console.log('ceil(4.1) =', math.evaluate('ceil(4.1)'));
+console.log('max(1, 5, 3) =', math.evaluate('max(1, 5, 3)'));
+console.log('min(1, 5, 3) =', math.evaluate('min(1, 5, 3)'));
+console.log('');
+
+// 5. 变量和赋值
+console.log('5. 变量和赋值:');
+console.log('x = 5:', math.evaluate('x = 5', scope));
+console.log('y = x + 3:', math.evaluate('y = x + 3', scope));
+console.log('x * y =', math.evaluate('x * y', scope));
+console.log('');
+
+// 6. 矩阵和数组运算
+console.log('6. 矩阵和数组运算:');
+console.log('[1, 2, 3] =', math.evaluate('[1, 2, 3]'));
+console.log('[[1,2],[3,4]] =', math.evaluate('[[1,2],[3,4]]'));
+console.log('size([1,2,3,4]) =', math.evaluate('size([1,2,3,4])'));
+console.log('[1,2,3] + [4,5,6] =', math.evaluate('[1,2,3] + [4,5,6]'));
+console.log('2 * [1,2,3] =', math.evaluate('2 * [1,2,3]'));
+console.log('');
+
+// 7. 复数运算
+console.log('7. 复数运算:');
+console.log('2 + 3i =', math.evaluate('2 + 3i'));
+console.log('re(2+3i) =', math.evaluate('re(2+3i)'));
+console.log('im(2+3i) =', math.evaluate('im(2+3i)'));
+console.log('');
+
+// 8. 单位换算
+console.log('8. 单位换算:');
+console.log('5 inch to cm =', math.evaluate('5 inch to cm'));
+console.log('100 km/hour to m/s =', math.evaluate('100 km/hour to m/s'));
+console.log('');
+
+// 9. 布尔运算和比较
+console.log('9. 布尔运算和比较:');
+console.log('2 > 3 =', math.evaluate('2 > 3'));
+console.log('5 == 5 =', math.evaluate('5 == 5'));
+console.log('3 != 4 =', math.evaluate('3 != 4'));
+console.log('true and false =', math.evaluate('true and false'));
+console.log('true or false =', math.evaluate('true or false'));
+console.log('not true =', math.evaluate('not true'));
+console.log('');
+
+// 10. 条件表达式
+console.log('10. 条件表达式:');
+console.log('2 > 3 ? "yes" : "no" =', math.evaluate('2 > 3 ? "yes" : "no"'));
+console.log('x > 0 ? "positive" : "non-positive" =', math.evaluate('x > 0 ? "positive" : "non-positive"', scope));
+console.log('');
+
+// 11. 字符串操作
+console.log('11. 字符串操作:');
+console.log('concat("hello", " ", "world") =', math.evaluate('concat("hello", " ", "world")'));
+console.log('');
+
+// 12. 自定义函数
+console.log('12. 自定义函数:');
+math.evaluate('f(x) = x^2 + 2*x + 1', scope);
+console.log('f(3) =', math.evaluate('f(3)', scope));
+console.log('f(5) =', math.evaluate('f(5)', scope));
+console.log('');
+
+// 13. 统计函数
+console.log('13. 统计函数:');
+console.log('mean([1, 2, 3, 4, 5]) =', math.evaluate('mean([1, 2, 3, 4, 5])'));
+console.log('median([1, 2, 3, 4, 5]) =', math.evaluate('median([1, 2, 3, 4, 5])'));
+console.log('std([1, 2, 3, 4, 5]) =', math.evaluate('std([1, 2, 3, 4, 5])'));
+console.log('sum([1, 2, 3, 4, 5]) =', math.evaluate('sum([1, 2, 3, 4, 5])'));
+console.log('');
+
+// 14. 大数运算
+console.log('14. 大数运算:');
+console.log('123456789012345678901234567890 + 1 =', math.evaluate('123456789012345678901234567890 + 1'));
+console.log('2^100 =', math.evaluate('2^100'));
+console.log('');
+
+// 15. 链式操作示例
+console.log('15. 链式操作示例:');
+try {
+    const result = math.chain(5)
+        .add(3)
+        .multiply(2)
+        .divide(4)
+        .done();
+    console.log('(5 + 3) * 2 / 4 =', result);
+} catch (error) {
+    console.log('链式操作出错:', error.message);
+}
+console.log('');
+
+// 16. 多表达式执行
+console.log('16. 多表达式执行:');
+const multiResult = math.evaluate(`
+    a = 10;
+    b = 20;
+    c = a + b;
+    c * 2
+`);
+console.log('多表达式结果:', multiResult);
+console.log('');
+
+// 17. 数值格式和精度
+console.log('17. 数值格式和精度:');
+console.log('format(1/3, 14) =', math.evaluate('format(1/3, 14)'));
+console.log('');
+
+// 18. 角度和弧度转换
+console.log('18. 角度和弧度转换:');
+console.log('sin(90 deg) =', math.evaluate('sin(90 deg)'));
+console.log('cos(180 deg) =', math.evaluate('cos(180 deg)'));
+console.log('');
+
+// 19. 随机数生成
+console.log('19. 随机数生成:');
+console.log('random() =', math.evaluate('random()'));
+console.log('random(10) =', math.evaluate('random(10)'));
+console.log('randomInt(10) =', math.evaluate('randomInt(10)'));
+console.log('random(5, 10) =', math.evaluate('random(5, 10)'));
+console.log('');
+
+// 20. 位运算
+console.log('20. 位运算:');
+console.log('5 & 3 =', math.evaluate('5 & 3'));
+console.log('5 | 3 =', math.evaluate('5 | 3'));
+console.log('');
+
+// 21. 阶乘和组合
+console.log('21. 阶乘和组合:');
+console.log('5! =', math.evaluate('5!'));
+console.log('combinations(5, 2) =', math.evaluate('combinations(5, 2)'));
+console.log('');
+
+// 22. 进制转换
+console.log('22. 进制转换:');
+console.log('0b1010 =', math.evaluate('0b1010'));
+console.log('0o777 =', math.evaluate('0o777'));
+console.log('0xFF =', math.evaluate('0xFF'));
+console.log('');
+
+// 23. 关系运算
+console.log('23. 关系运算:');
+console.log('2 < 3 =', math.evaluate('2 < 3'));
+console.log('2 <= 2 =', math.evaluate('2 <= 2'));
+console.log('3 >= 4 =', math.evaluate('3 >= 4'));
+console.log('');
+
+// 24. 分数运算
+console.log('24. 分数运算:');
+console.log("4/8 + 1 -1/3 = ", math.simplify('4/8 + 1 -1/3').toString());
+''';
+  }
+
+  Future<void> _saveAs() async {
+  try {
+    final String codeText = _codeController.text;
+    final Uint8List codeBytes = Uint8List.fromList(utf8.encode(codeText));
+
+    String? outputPath = await FilePicker.platform.saveFile(
+      dialogTitle: '另存为JavaScript文件',
+      fileName: 'javascript_code.js',
+      allowedExtensions: ['js'],
+      bytes: codeBytes,
+    );
+    
+    if (outputPath != null) {
+      if (mounted) {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(
+            content: Text('文件已保存'),
+            backgroundColor: Colors.green,
+          ),
+        );
+      }
+    }
+  } catch (e) {
+    print('保存文件错误: $e');
+    if (mounted) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text('保存文件失败: $e'),
+          backgroundColor: Colors.red,
+        ),
+      );
+    }
+  }
+}
+
+  Future<void> _loadFromFile() async {
+  try {
+    FilePickerResult? result = await FilePicker.platform.pickFiles(
+      type: FileType.custom,
+      allowedExtensions: ['js'],
+      dialogTitle: '选择JavaScript文件',
+    );
+    
+    if (result != null && result.files.isNotEmpty) {
+      final PlatformFile platformFile = result.files.single;
+      
+      if (platformFile.path == null && platformFile.bytes == null) {
+        if (mounted) {
+          ScaffoldMessenger.of(context).showSnackBar(
+            SnackBar(
+              content: Text('无法获取文件内容'),
+              backgroundColor: Colors.red,
+            ),
+          );
+        }
+        return;
+      }
+      
+      // 读取文件内容
+      String contents;
+      if (platformFile.bytes != null) {
+        // 直接从字节数据读取
+        contents = utf8.decode(platformFile.bytes!);
+      } else {
+        // 从文件路径读取
+        final file = File(platformFile.path!);
+        contents = await file.readAsString();
+      }
+      
+      setState(() {
+        _codeController.text = contents;
+        _output = '文件已加载: ${platformFile.name}';
+      });
+
+      if (mounted) {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(
+            content: Text('文件加载成功: ${platformFile.name}'),
+            backgroundColor: Colors.green,
+          ),
+        );
+      }
+    }
+  } catch (e) {
+    print('加载文件错误: $e');
+    if (mounted) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text('加载文件失败: $e'),
+          backgroundColor: Colors.red,
+        ),
+      );
+    }
+  }
+}
+
+  Future<void> _executeCode() async {
+    if (_isExecuting || !_isInitialized) return;
+
+    setState(() {
+      _isExecuting = true;
+      _output = '正在初始化JavaScript环境...\\n';
+    });
+
+    try {
+      await _initializeJsRuntime();
+
+      final String code = _codeController.text;
+
+      setState(() {
+        _output += '执行代码中...\\n';
+      });
+
+      _jsRuntime!.evaluate('__clearConsoleOutput();');
+
+      final JsEvalResult result = _jsRuntime!.evaluate(code);
+
+      final JsEvalResult consoleOutputResult = _jsRuntime!.evaluate('__getConsoleOutput();');
+      final String consoleOutput = consoleOutputResult.stringResult;
+
+      setState(() {
+        _output = '执行完成!\n';
+        
+        if (result.isError) {
+          _output += '❌ 执行错误: ${result.stringResult}\n';
+        } else {
+          _output += '✅ 执行成功!\n';
+          if (result.stringResult != 'null' && result.stringResult.isNotEmpty) {
+            _output += '📊 返回值: ${result.stringResult}\n';
+          } else {
+            _output += '\n';
+          }
+        }
+        
+        if (consoleOutput.isNotEmpty) {
+          _output += '--- 控制台输出 ---\n$consoleOutput';
+        } else {
+          _output += 'ℹ️ 没有控制台输出';
+        }
+      });
+    } catch (e) {
+      setState(() {
+        _output = '执行出错: $e';
+      });
+    } finally {
+      setState(() {
+        _isExecuting = false;
+      });
+    }
+  }
+
+  void _clearOutput() {
+    setState(() {
+      _output = '';
+    });
+  }
+
+  void _resetCode() {
+    setState(() {
+      _codeController.text = _getExampleCode();
+      _output = '';
+    });
+  }
+
+  void _injectMathJs() async {
+    try {
+      await _initializeJsRuntime();
+      
+      setState(() {
+        _output = 'math.js重新加载成功! JavaScript环境已重置。';
+      });
+    } catch (e) {
+      setState(() {
+        _output = '重新加载math.js失败: $e';
+      });
+    }
+  }
+}

+ 260 - 0
lib/tools/binomial_distribution.dart

@@ -0,0 +1,260 @@
+// tools/binomial_distribution.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import 'package:fl_chart/fl_chart.dart';
+import '../utils/math_utils.dart';
+
+class BinomialDistributionPage extends StatefulWidget {
+  @override
+  _BinomialDistributionPageState createState() => _BinomialDistributionPageState();
+}
+
+class _BinomialDistributionPageState extends State<BinomialDistributionPage> {
+  final TextEditingController nController = TextEditingController();
+  final TextEditingController pController = TextEditingController();
+
+  String expectation = '';
+  String variance = '';
+  List<String> probabilities = [];
+  List<BarChartGroupData> barGroups = [];
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('二项分布')),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: SingleChildScrollView( // 添加滚动容器
+          child: Column(
+            children: [
+              _buildInputField('n (试验次数)', nController),
+              _buildInputField('p (成功概率)', pController),
+              SizedBox(height: 20),
+              ElevatedButton(
+                onPressed: _calculate,
+                child: Text('计算'),
+              ),
+              SizedBox(height: 20),
+              if (expectation.isNotEmpty) ...[
+                _buildResultCard('期望', expectation),
+                _buildResultCard('方差', variance),
+                SizedBox(height: 10),
+                Text('概率分布:', style: TextStyle(fontWeight: FontWeight.bold)),
+                if (barGroups.isNotEmpty) ...[
+                  SizedBox(height: 10),
+                  Container(
+                    height: 300,
+                    child: BarChart(
+                      BarChartData(
+                        barTouchData: BarTouchData(
+                          enabled: false, // 禁用触摸交互
+                        ),
+                        titlesData: FlTitlesData(
+                          show: true,
+                          bottomTitles: AxisTitles(
+                            sideTitles: SideTitles(
+                              showTitles: true,
+                              getTitlesWidget: (value, meta) {
+                                return Padding(
+                                  padding: const EdgeInsets.only(top: 8.0),
+                                  child: Text(
+                                    value.toInt().toString(),
+                                    style: TextStyle(fontSize: 10),
+                                  ),
+                                );
+                              },
+                              reservedSize: 30,
+                            ),
+                          ),
+                          leftTitles: AxisTitles(
+                            sideTitles: SideTitles(
+                              showTitles: true,
+                              getTitlesWidget: (value, meta) {
+                                if (value % 0.1 == 0) {
+                                  return Text(
+                                    value.toStringAsFixed(1),
+                                    style: TextStyle(fontSize: 10),
+                                  );
+                                }
+                                return Text('');
+                              },
+                              reservedSize: 40,
+                            ),
+                          ),
+                          topTitles: AxisTitles(
+                            sideTitles: SideTitles(showTitles: false),
+                          ),
+                          rightTitles: AxisTitles(
+                            sideTitles: SideTitles(showTitles: false),
+                          ),
+                        ),
+                        borderData: FlBorderData(show: true),
+                        barGroups: barGroups,
+                        gridData: FlGridData(show: true),
+                        alignment: BarChartAlignment.spaceAround,
+                        maxY: _getMaxY(),
+                      ),
+                    ),
+                  ),
+                  SizedBox(height: 10),
+                ],
+                // 移除Expanded并替换为固定高度的ListView
+                Container(
+                  height: 200, // 设置固定高度
+                  child: ListView.builder(
+                    itemCount: probabilities.length,
+                    itemBuilder: (context, index) {
+                      return ListTile(
+                        title: Text(probabilities[index]),
+                      );
+                    },
+                  ),
+                ),
+              ],
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  double _getMaxY() {
+    if (barGroups.isEmpty) return 1.0;
+    double maxY = 0.0;
+    for (var group in barGroups) {
+      for (var rod in group.barRods) {
+        if (rod.toY > maxY) maxY = rod.toY;
+      }
+    }
+    return maxY * 1.1;
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: TextField(
+        controller: controller,
+        keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+        decoration: InputDecoration(
+          labelText: label,
+          border: OutlineInputBorder(),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildResultCard(String title, String value) {
+    return Card(
+      child: ListTile(
+        title: Text(title),
+        subtitle: Text(value),
+      ),
+    );
+  }
+
+  Rational _combination(int n, int k) {
+    if (k < 0 || k > n) return Rational.zero;
+    if (k == 0 || k == n) return Rational.one;
+
+    BigInt result = BigInt.one;
+    for (int i = 1; i <= k; i++) {
+      result = result * BigInt.from(n - i + 1) ~/ BigInt.from(i);
+    }
+    return Rational(result);
+  }
+
+  void _calculate() {
+    try {
+      final n = int.parse(nController.text);
+      final p = MathUtils.parseRational(pController.text);
+
+      final EX = MathUtils.fromInt(n) * p;
+      final DX = MathUtils.fromInt(n) * p * (Rational.one - p);
+
+      final probs = <String>[];
+      final barGroupsData = <BarChartGroupData>[];
+
+      // 首先计算所有概率并找到最大值
+      List<double> probValues = [];
+      double maxProb = 0.0;
+      
+      for (int i = 0; i <= n; i++) {
+        final comb = _combination(n, i);
+        final prob = comb * p.pow(i) * (Rational.one - p).pow(n - i);
+        final probDouble = prob.toDouble();
+        
+        probValues.add(probDouble);
+        if (probDouble > maxProb) {
+          maxProb = probDouble;
+        }
+        
+        probs.add('P(X = $i) = ${MathUtils.rationalToString(prob)}');
+      }
+
+      // 找到所有具有最大概率的i值
+      List<int> maxProbIndices = [];
+      for (int i = 0; i < probValues.length; i++) {
+        if (probValues[i] == maxProb) {
+          maxProbIndices.add(i);
+        }
+      }
+
+      // 确定最高组的范围
+      int minMaxIndex = maxProbIndices.reduce((a, b) => a < b ? a : b);
+      int maxMaxIndex = maxProbIndices.reduce((a, b) => a > b ? a : b);
+
+      // 构建柱状图数据
+      for (int i = 0; i <= n; i++) {
+        double probDouble = probValues[i];
+        
+        barGroupsData.add(
+          BarChartGroupData(
+            x: i,
+            barRods: [
+              BarChartRodData(
+                toY: probDouble,
+                color: _getBarColor(i, minMaxIndex, maxMaxIndex),
+                width: 16,
+                borderRadius: BorderRadius.circular(4),
+              ),
+            ],
+          ),
+        );
+      }
+
+      setState(() {
+        expectation = 'EX = ${MathUtils.rationalToString(EX)}';
+        variance = 'DX = ${MathUtils.rationalToString(DX)}';
+        probabilities = probs;
+        barGroups = barGroupsData;
+      });
+    } catch (e) {
+      setState(() {
+        expectation = '错误: $e';
+        variance = '';
+        probabilities = [];
+        barGroups = [];
+      });
+    }
+  }
+
+  Color _getBarColor(int x, int minMaxIndex, int maxMaxIndex) {
+    if (x >= minMaxIndex && x <= maxMaxIndex) {
+      // 最高组(可能有多组)
+      return Colors.red;
+    } else if (x < minMaxIndex) {
+      // 最高组左边的组
+      return Colors.blue.shade300;
+    } else {
+      // 最高组右边的组
+      return Colors.green.shade300;
+    }
+  }
+
+  @override
+  void dispose() {
+    nController.dispose();
+    pController.dispose();
+    super.dispose();
+  }
+}

+ 291 - 0
lib/tools/bisection_method.dart

@@ -0,0 +1,291 @@
+// tools/bisection_method.dart
+import 'package:flutter/material.dart';
+import 'package:math_expressions/math_expressions.dart';
+
+class BisectionMethodPage extends StatefulWidget {
+  @override
+  _BisectionMethodPageState createState() => _BisectionMethodPageState();
+}
+
+class _BisectionMethodPageState extends State<BisectionMethodPage> {
+  final TextEditingController _functionController = TextEditingController(text: 'x^3 - x - 2');
+  final TextEditingController _aController = TextEditingController(text: '1');
+  final TextEditingController _bController = TextEditingController(text: '2');
+  final TextEditingController _toleranceController = TextEditingController(text: '0.0001');
+  final TextEditingController _maxIterationsController = TextEditingController(text: '100');
+
+  List<String> _iterations = [];
+  String _result = '';
+  bool _isCalculating = false;
+
+  // 使用 math_expressions 库计算数学表达式
+  double _evaluateFunction(String function, double x) {
+    try {
+      // 预处理函数表达式
+      String exprString = function
+          .replaceAll(' ', '')
+          .replaceAll('×', '*')
+          .replaceAll('÷', '/');
+      
+      // 创建解析器
+      Parser p = Parser();
+      
+      // 解析表达式
+      Expression expression = p.parse(exprString);
+      
+      // 创建上下文并绑定变量 x
+      ContextModel contextModel = ContextModel()
+        ..bindVariable(Variable('x'), Number(x));
+      
+      // 计算表达式
+      double result = expression.evaluate(EvaluationType.REAL, contextModel);
+      
+      return result;
+    } catch (e) {
+      throw Exception('函数表达式解析错误: $e\n请检查表达式格式是否正确');
+    }
+  }
+
+  void _calculate() {
+    setState(() {
+      _isCalculating = true;
+      _iterations.clear();
+      _result = '';
+    });
+
+    try {
+      double a = double.parse(_aController.text);
+      double b = double.parse(_bController.text);
+      double tolerance = double.parse(_toleranceController.text);
+      int maxIterations = int.parse(_maxIterationsController.text);
+      String function = _functionController.text;
+
+      if (a >= b) {
+        throw Exception('区间左端点必须小于右端点');
+      }
+
+      double fa = _evaluateFunction(function, a);
+      double fb = _evaluateFunction(function, b);
+
+      if (fa.isNaN || fb.isNaN) {
+        throw Exception('函数在端点处计算失败,请检查表达式');
+      }
+
+      if (fa * fb > 0) {
+        throw Exception('函数在区间两端点必须异号\nf(a) = $fa, f(b) = $fb');
+      }
+
+      int iteration = 0;
+      double c = a;
+
+      while ((b - a).abs() > tolerance && iteration < maxIterations) {
+        c = (a + b) / 2;
+        double fc = _evaluateFunction(function, c);
+
+        if (fc.isNaN) {
+          throw Exception('迭代过程中函数计算失败');
+        }
+
+        _iterations.add('迭代 ${iteration + 1}: a=${a.toStringAsFixed(6)}, b=${b.toStringAsFixed(6)}, c=${c.toStringAsFixed(6)}, f(c)=${fc.toStringAsFixed(6)}');
+
+        if (fc.abs() < tolerance) {
+          break;
+        } else if (fa * fc < 0) {
+          b = c;
+          fb = fc;
+        } else {
+          a = c;
+          fa = fc;
+        }
+
+        iteration++;
+      }
+
+      setState(() {
+        _result = '近似解: ${c.toStringAsFixed(8)}\n'
+                 '迭代次数: $iteration\n'
+                 '最终误差: ${(b - a).abs().toStringAsFixed(8)}';
+      });
+
+    } catch (e) {
+      setState(() {
+        _result = '错误: $e';
+      });
+    } finally {
+      setState(() {
+        _isCalculating = false;
+      });
+    }
+  }
+
+  void _clear() {
+    setState(() {
+      _functionController.text = 'x^3 - x - 2';
+      _aController.text = '1';
+      _bController.text = '2';
+      _toleranceController.text = '0.0001';
+      _maxIterationsController.text = '100';
+      _iterations.clear();
+      _result = '';
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('二分法求根', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+        iconTheme: IconThemeData(color: Colors.black),
+      ),
+      body: SingleChildScrollView(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '二分法求方程近似解',
+              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.deepPurple),
+            ),
+            SizedBox(height: 16),
+            Text(
+              '使用方法:\n'
+              '1. 输入函数表达式(支持: +, -, *, /, ^, sin, cos, tan, ln, e, sqrt 等)\n'
+              '2. 输入有根区间 [a, b]\n'
+              '3. 设置精度和最大迭代次数\n'
+              '4. 点击计算获取近似解\n\n'
+              '示例表达式:\n'
+              '• x^3 - x - 2\n'
+              '• sin(x) + cos(x)\n'
+              '• e^x - 2\n'
+              '• ln(x) + sqrt(x)',
+              style: TextStyle(fontSize: 14, color: Colors.grey[600]),
+            ),
+            SizedBox(height: 20),
+            
+            // 函数输入
+            _buildInputField('函数 f(x)', '例如: x^3 - x - 2', _functionController),
+            SizedBox(height: 16),
+            
+            Row(
+              children: [
+                Expanded(child: _buildInputField('区间左端点 a', '1', _aController)),
+                SizedBox(width: 16),
+                Expanded(child: _buildInputField('区间右端点 b', '2', _bController)),
+              ],
+            ),
+            SizedBox(height: 16),
+            
+            Row(
+              children: [
+                Expanded(child: _buildInputField('精度 tolerance', '0.0001', _toleranceController)),
+                SizedBox(width: 16),
+                Expanded(child: _buildInputField('最大迭代次数', '100', _maxIterationsController)),
+              ],
+            ),
+            SizedBox(height: 24),
+            
+            // 按钮
+            Row(
+              children: [
+                Expanded(
+                  child: ElevatedButton(
+                    onPressed: _isCalculating ? null : _calculate,
+                    style: ElevatedButton.styleFrom(
+                      backgroundColor: Colors.deepPurple,
+                      padding: EdgeInsets.symmetric(vertical: 16),
+                    ),
+                    child: _isCalculating 
+                        ? SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white))
+                        : Text('计算', style: TextStyle(fontSize: 16, color: Colors.white)),
+                  ),
+                ),
+                SizedBox(width: 16),
+                Expanded(
+                  child: OutlinedButton(
+                    onPressed: _clear,
+                    style: OutlinedButton.styleFrom(
+                      padding: EdgeInsets.symmetric(vertical: 16),
+                    ),
+                    child: Text('清空', style: TextStyle(fontSize: 16)),
+                  ),
+                ),
+              ],
+            ),
+            SizedBox(height: 24),
+            
+            // 结果展示
+            if (_result.isNotEmpty) ...[
+              Container(
+                width: double.infinity,
+                padding: EdgeInsets.all(16),
+                decoration: BoxDecoration(
+                  color: _result.contains('错误') ? Colors.red[50] : Colors.green[50],
+                  borderRadius: BorderRadius.circular(8),
+                  border: Border.all(color: _result.contains('错误') ? Colors.red : Colors.green),
+                ),
+                child: Text(
+                  _result,
+                  style: TextStyle(
+                    fontSize: 16, 
+                    fontWeight: FontWeight.bold, 
+                    color: _result.contains('错误') ? Colors.red[800] : Colors.green[800]
+                  ),
+                ),
+              ),
+              SizedBox(height: 16),
+            ],
+            
+            // 迭代过程
+            if (_iterations.isNotEmpty) ...[
+              Text('迭代过程:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
+              SizedBox(height: 8),
+              Container(
+                height: 200,
+                decoration: BoxDecoration(
+                  border: Border.all(color: Colors.grey[300]!),
+                  borderRadius: BorderRadius.circular(8),
+                ),
+                child: ListView.builder(
+                  itemCount: _iterations.length,
+                  itemBuilder: (context, index) {
+                    return Container(
+                      padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+                      decoration: BoxDecoration(
+                        border: index > 0 ? Border(top: BorderSide(color: Colors.grey[200]!)) : null,
+                        color: index % 2 == 0 ? Colors.grey[50] : Colors.white,
+                      ),
+                      child: Text(
+                        _iterations[index],
+                        style: TextStyle(fontSize: 12, fontFamily: 'Monospace'),
+                      ),
+                    );
+                  },
+                ),
+              ),
+            ],
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, String hint, TextEditingController controller) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Text(label, style: TextStyle(fontWeight: FontWeight.bold)),
+        SizedBox(height: 4),
+        TextField(
+          controller: controller,
+          decoration: InputDecoration(
+            hintText: hint,
+            border: OutlineInputBorder(),
+            contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 268 - 0
lib/tools/chi_square.dart

@@ -0,0 +1,268 @@
+// tools/chi_square.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import 'package:fl_chart/fl_chart.dart';
+import '../utils/math_utils.dart';
+
+class ChiSquarePage extends StatefulWidget {
+  @override
+  _ChiSquarePageState createState() => _ChiSquarePageState();
+}
+
+class _ChiSquarePageState extends State<ChiSquarePage> {
+  final TextEditingController aController = TextEditingController();
+  final TextEditingController bController = TextEditingController();
+  final TextEditingController cController = TextEditingController();
+  final TextEditingController dController = TextEditingController();
+
+  String result = '';
+  List<BarChartGroupData> barGroups = [];
+  bool showChart = false;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('卡方检验')),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text('请输入2×2列联表数据:'),
+            SizedBox(height: 20),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('a', aController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('b', bController)),
+              ],
+            ),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('c', cController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('d', dController)),
+              ],
+            ),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算卡方值'),
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 16),
+                  ),
+                ),
+              ),
+            SizedBox(height: 20),
+            if (showChart && barGroups.isNotEmpty)
+              Expanded(
+                child: Card(
+                  elevation: 4,
+                  child: Padding(
+                    padding: EdgeInsets.all(16),
+                    child: Column(
+                      children: [
+                        Text(
+                          '等高堆积条形图',
+                          style: TextStyle(
+                            fontSize: 18,
+                            fontWeight: FontWeight.bold,
+                          ),
+                        ),
+                        SizedBox(height: 10),
+                        Expanded(
+                          child: BarChart(
+                            BarChartData(
+                              alignment: BarChartAlignment.spaceAround,
+                              maxY: 1.0,
+                              barTouchData: BarTouchData(
+                                enabled: false,
+                              ),
+                              titlesData: FlTitlesData(
+                                show: true,
+                                bottomTitles: AxisTitles(
+                                  sideTitles: SideTitles(
+                                    showTitles: true,
+                                    getTitlesWidget: (value, meta) {
+                                      switch (value.toInt()) {
+                                        case 0:
+                                          return Text('组1');
+                                        case 1:
+                                          return Text('组2');
+                                        default:
+                                          return Text('');
+                                      }
+                                    },
+                                  ),
+                                ),
+                                leftTitles: AxisTitles(
+                                  sideTitles: SideTitles(
+                                    showTitles: true,
+                                    getTitlesWidget: (value, meta) {
+                                      return Text('${(value * 100).toInt()}%');
+                                    },
+                                    reservedSize: 40,
+                                  ),
+                                ),
+                                topTitles: AxisTitles(
+                                  sideTitles: SideTitles(showTitles: false),
+                                ),
+                                rightTitles: AxisTitles(
+                                  sideTitles: SideTitles(showTitles: false),
+                                ),
+                              ),
+                              gridData: FlGridData(
+                                show: true,
+                                drawVerticalLine: false,
+                                getDrawingHorizontalLine: (value) {
+                                  return FlLine(
+                                    color: Colors.grey.withOpacity(0.3),
+                                    strokeWidth: 1,
+                                  );
+                                },
+                              ),
+                              borderData: FlBorderData(
+                                show: false,
+                              ),
+                              barGroups: barGroups,
+                            ),
+                          ),
+                        ),
+                        SizedBox(height: 10),
+                        Row(
+                          mainAxisAlignment: MainAxisAlignment.center,
+                          children: [
+                            _buildLegend(Colors.blue, '类别A'),
+                            SizedBox(width: 20),
+                            _buildLegend(Colors.red, '类别B'),
+                          ],
+                        ),
+                      ],
+                    ),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return TextField(
+      controller: controller,
+      keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+      decoration: InputDecoration(
+        labelText: label,
+        border: OutlineInputBorder(),
+      ),
+    );
+  }
+
+  Widget _buildLegend(Color color, String text) {
+    return Row(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        Container(
+          width: 16,
+          height: 16,
+          color: color,
+        ),
+        SizedBox(width: 8),
+        Text(text),
+      ],
+    );
+  }
+
+  void _calculate() {
+  try {
+    final a = int.parse(aController.text);
+    final b = int.parse(bController.text);
+    final c = int.parse(cController.text);
+    final d = int.parse(dController.text);
+
+    final n = a + b + c + d;
+    final numerator = Rational.fromInt(n) *
+    Rational.fromInt((a * d - b * c) * (a * d - b * c));
+    final denominator = Rational.fromInt((a + b) * (c + d) * (a + c) * (b + d));
+    final K2 = numerator / denominator;
+
+    // 计算比例用于图表
+    final totalGroup1 = a + b;
+    final totalGroup2 = c + d;
+    
+    final proportionA1 = totalGroup1 > 0 ? a / totalGroup1 : 0.0;
+    final proportionB1 = totalGroup1 > 0 ? b / totalGroup1 : 0.0;
+    final proportionA2 = totalGroup2 > 0 ? c / totalGroup2 : 0.0;
+    final proportionB2 = totalGroup2 > 0 ? d / totalGroup2 : 0.0;
+
+    // 创建条形图数据 - 修复堆积问题
+    barGroups = [
+      BarChartGroupData(
+        x: 0,
+        groupVertically: true, // 启用垂直堆积
+        barsSpace: 4, // 条形之间的间距
+        barRods: [
+          BarChartRodData(
+            fromY: 0,
+            toY: proportionA1,
+            color: Colors.blue,
+            width: 30,
+          ),
+          BarChartRodData(
+            fromY: proportionA1, // 从第一个条形的顶部开始
+            toY: proportionA1 + proportionB1,
+            color: Colors.red,
+            width: 30,
+          ),
+        ],
+      ),
+      BarChartGroupData(
+        x: 1,
+        groupVertically: true, // 启用垂直堆积
+        barsSpace: 4, // 条形之间的间距
+        barRods: [
+          BarChartRodData(
+            fromY: 0,
+            toY: proportionA2,
+            color: Colors.blue,
+            width: 30,
+          ),
+          BarChartRodData(
+            fromY: proportionA2, // 从第一个条形的顶部开始
+            toY: proportionA2 + proportionB2,
+            color: Colors.red,
+            width: 30,
+          ),
+        ],
+      ),
+    ];
+
+    setState(() {
+      result = 'K² = ${MathUtils.rationalToString(K2)}\n'
+      '近似值: ${K2.toDouble().toStringAsFixed(4)}';
+      showChart = true;
+    });
+  } catch (e) {
+    setState(() {
+      result = '错误: $e';
+      showChart = false;
+    });
+  }
+}
+
+  @override
+  void dispose() {
+    aController.dispose();
+    bController.dispose();
+    cController.dispose();
+    dController.dispose();
+    super.dispose();
+  }
+}

+ 137 - 0
lib/tools/circle_equation.dart

@@ -0,0 +1,137 @@
+// tools/circle_equation.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/math_utils.dart';
+
+class CircleEquationPage extends StatefulWidget {
+  @override
+  _CircleEquationPageState createState() => _CircleEquationPageState();
+}
+
+class _CircleEquationPageState extends State<CircleEquationPage> {
+  final TextEditingController x1Controller = TextEditingController();
+  final TextEditingController y1Controller = TextEditingController();
+  final TextEditingController x2Controller = TextEditingController();
+  final TextEditingController y2Controller = TextEditingController();
+  final TextEditingController x3Controller = TextEditingController();
+  final TextEditingController y3Controller = TextEditingController();
+
+  String result = '';
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('圆的方程')),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text('请输入三个点的坐标:'),
+            SizedBox(height: 10),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('点1 x', x1Controller)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('点1 y', y1Controller)),
+              ],
+            ),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('点2 x', x2Controller)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('点2 y', y2Controller)),
+              ],
+            ),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('点3 x', x3Controller)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('点3 y', y3Controller)),
+              ],
+            ),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算圆的方程'),
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 14, fontFamily: 'Monospace'),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return TextField(
+      controller: controller,
+      keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+      decoration: InputDecoration(
+        labelText: label,
+        border: OutlineInputBorder(),
+        hintText: '输入分数如: 1/2 或小数',
+      ),
+    );
+  }
+
+  void _calculate() {
+    try {
+      final x1 = MathUtils.parseRational(x1Controller.text);
+      final y1 = MathUtils.parseRational(y1Controller.text);
+      final x2 = MathUtils.parseRational(x2Controller.text);
+      final y2 = MathUtils.parseRational(y2Controller.text);
+      final x3 = MathUtils.parseRational(x3Controller.text);
+      final y3 = MathUtils.parseRational(y3Controller.text);
+
+      final z1 = x1 * x1 + y1 * y1;
+      final z2 = x2 * x2 + y2 * y2;
+      final z3 = x3 * x3 + y3 * y3;
+
+      final solutions = MathUtils.solveLinear2(
+        x1 - x2, y1 - y2, z2 - z1,
+        x1 - x3, y1 - y3, z3 - z1,
+      );
+
+      final D = solutions[0];
+      final E = solutions[1];
+      final F = -z1 - D * x1 - E * y1;
+      final r2 = (D * D + E * E - Rational.fromInt(4) * F)/Rational.fromInt(4);
+
+      setState(() {
+        result = '''
+      圆的方程: x² + y² + Dx + Ey + F = 0
+      D = ${MathUtils.rationalToString(D)}
+      E = ${MathUtils.rationalToString(E)}
+      F = ${MathUtils.rationalToString(F)}
+      圆心: (${MathUtils.rationalToString(-D / Rational.fromInt(2))},
+      ${MathUtils.rationalToString(-E / Rational.fromInt(2))})
+      半径平方: ${MathUtils.rationalToString(r2)}
+      ''';
+      });
+    } catch (e) {
+      setState(() {
+        result = '错误: $e\n注意: 三个点不能共线';
+      });
+    }
+  }
+
+  @override
+  void dispose() {
+    x1Controller.dispose();
+    y1Controller.dispose();
+    x2Controller.dispose();
+    y2Controller.dispose();
+    x3Controller.dispose();
+    y3Controller.dispose();
+    super.dispose();
+  }
+}

+ 453 - 0
lib/tools/convolution_calculator.dart

@@ -0,0 +1,453 @@
+// tools/convolution_calculator.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+
+class ConvolutionCalculatorPage extends StatefulWidget {
+  @override
+  _ConvolutionCalculatorPageState createState() => _ConvolutionCalculatorPageState();
+}
+
+class _ConvolutionCalculatorPageState extends State<ConvolutionCalculatorPage> {
+  final List<TextEditingController> _xValueControllers = [];
+  final List<TextEditingController> _xProbControllers = [];
+  final List<TextEditingController> _yValueControllers = [];
+  final List<TextEditingController> _yProbControllers = [];
+  
+  List<Map<String, Rational>> _convolutionResult = [];
+  bool _showResult = false;
+
+  @override
+  void initState() {
+    super.initState();
+    // 初始化默认数据
+    _initializeDefaultData();
+  }
+
+  void _initializeDefaultData() {
+    // 添加X的默认数据
+    _addXRow(value: '0', prob: '1/2');
+    _addXRow(value: '1', prob: '1/2');
+    
+    // 添加Y的默认数据
+    _addYRow(value: '0', prob: '1/3');
+    _addYRow(value: '1', prob: '1/3');
+    _addYRow(value: '2', prob: '1/3');
+  }
+
+  void _addXRow({String value = '', String prob = ''}) {
+    setState(() {
+      _xValueControllers.add(TextEditingController(text: value));
+      _xProbControllers.add(TextEditingController(text: prob));
+    });
+  }
+
+  void _removeXRow(int index) {
+    setState(() {
+      _xValueControllers.removeAt(index);
+      _xProbControllers.removeAt(index);
+    });
+  }
+
+  void _addYRow({String value = '', String prob = ''}) {
+    setState(() {
+      _yValueControllers.add(TextEditingController(text: value));
+      _yProbControllers.add(TextEditingController(text: prob));
+    });
+  }
+
+  void _removeYRow(int index) {
+    setState(() {
+      _yValueControllers.removeAt(index);
+      _yProbControllers.removeAt(index);
+    });
+  }
+
+  Rational _parseRational(String input) {
+    try {
+      if (input.contains('/')) {
+        List<String> parts = input.split('/');
+        if (parts.length == 2) {
+          return Rational.parse(parts[0].trim()) / Rational.parse(parts[1].trim());
+        }
+      }
+      return Rational.parse(input.trim());
+    } catch (e) {
+      throw FormatException('无效的概率格式: $input');
+    }
+  }
+
+  void _calculateConvolution() {
+    try {
+      // 解析X分布
+      Map<Rational, Rational> xDistribution = {};
+      Rational xSum = Rational.zero;
+      for (int i = 0; i < _xValueControllers.length; i++) {
+        Rational value = _parseRational(_xValueControllers[i].text);
+        Rational prob = _parseRational(_xProbControllers[i].text);
+        xDistribution[value] = prob;
+        xSum += prob;
+      }
+
+      // 解析Y分布
+      Map<Rational, Rational> yDistribution = {};
+      Rational ySum = Rational.zero;
+      for (int i = 0; i < _yValueControllers.length; i++) {
+        Rational value = _parseRational(_yValueControllers[i].text);
+        Rational prob = _parseRational(_yProbControllers[i].text);
+        yDistribution[value] = prob;
+        ySum += prob;
+      }
+
+      // 验证概率和是否为1
+      if (xSum != Rational.one) {
+        throw FormatException('X的概率和必须为1,当前为: $xSum');
+      }
+      if (ySum != Rational.one) {
+        throw FormatException('Y的概率和必须为1,当前为: $ySum');
+      }
+
+      // 计算卷积
+      Map<Rational, Rational> convolution = {};
+      for (var xEntry in xDistribution.entries) {
+        for (var yEntry in yDistribution.entries) {
+          Rational sumValue = xEntry.key + yEntry.key;
+          Rational prob = xEntry.value * yEntry.value;
+          convolution[sumValue] = (convolution[sumValue] ?? Rational.zero) + prob;
+        }
+      }
+
+      // 转换为列表并排序
+      _convolutionResult = convolution.entries
+          .map((entry) => {'value': entry.key, 'probability': entry.value})
+          .toList();
+      
+      _convolutionResult.sort((a, b) => a['value']!.compareTo(b['value']!));
+
+      setState(() {
+        _showResult = true;
+      });
+
+    } catch (e) {
+      _showErrorDialog(e.toString());
+    }
+  }
+
+  void _showErrorDialog(String message) {
+    showDialog(
+      context: context,
+      builder: (context) => AlertDialog(
+        title: Text('计算错误'),
+        content: Text(message),
+        actions: [
+          TextButton(
+            onPressed: () => Navigator.of(context).pop(),
+            child: Text('确定'),
+          ),
+        ],
+      ),
+    );
+  }
+
+  void _clearAll() {
+    setState(() {
+      _xValueControllers.clear();
+      _xProbControllers.clear();
+      _yValueControllers.clear();
+      _yProbControllers.clear();
+      _convolutionResult.clear();
+      _showResult = false;
+      _initializeDefaultData();
+    });
+  }
+
+  @override
+  void dispose() {
+    for (var controller in _xValueControllers) {
+      controller.dispose();
+    }
+    for (var controller in _xProbControllers) {
+      controller.dispose();
+    }
+    for (var controller in _yValueControllers) {
+      controller.dispose();
+    }
+    for (var controller in _yProbControllers) {
+      controller.dispose();
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('卷积分布列计算'),
+        backgroundColor: Colors.white,
+        elevation: 1,
+        actions: [
+          IconButton(
+            icon: Icon(Icons.clear_all),
+            onPressed: _clearAll,
+            tooltip: '清空所有',
+          ),
+        ],
+      ),
+      body: SingleChildScrollView(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            _buildDistributionInput('随机变量 X 的分布列', _xValueControllers, _xProbControllers, _addXRow, _removeXRow),
+            SizedBox(height: 24),
+            _buildDistributionInput('随机变量 Y 的分布列', _yValueControllers, _yProbControllers, _addYRow, _removeYRow),
+            SizedBox(height: 24),
+            Center(
+              child: ElevatedButton.icon(
+                onPressed: _calculateConvolution,
+                icon: Icon(Icons.calculate),
+                label: Text('计算卷积分布列'),
+                style: ElevatedButton.styleFrom(
+                  padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
+                ),
+              ),
+            ),
+            SizedBox(height: 24),
+            if (_showResult) _buildResultTable(),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildDistributionInput(
+    String title,
+    List<TextEditingController> valueControllers,
+    List<TextEditingController> probControllers,
+    VoidCallback onAdd,
+    Function(int) onRemove,
+  ) {
+    return Card(
+      elevation: 2,
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              title,
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 16),
+            _buildTableHeader(),
+            SizedBox(height: 8),
+            ...List.generate(valueControllers.length, (index) {
+              return _buildInputRow(
+                valueControllers[index],
+                probControllers[index],
+                index,
+                onRemove,
+                valueControllers.length,
+              );
+            }),
+            SizedBox(height: 16),
+            Center(
+              child: OutlinedButton.icon(
+                onPressed: onAdd,
+                icon: Icon(Icons.add, size: 18),
+                label: Text('添加一行'),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildTableHeader() {
+    return Row(
+      children: [
+        Expanded(
+          flex: 2,
+          child: Text('取值', style: TextStyle(fontWeight: FontWeight.bold)),
+        ),
+        SizedBox(width: 16),
+        Expanded(
+          flex: 3,
+          child: Text('概率 (支持分数如 1/2)', style: TextStyle(fontWeight: FontWeight.bold)),
+        ),
+        SizedBox(width: 16),
+        Expanded(
+          flex: 1,
+          child: Text('操作', style: TextStyle(fontWeight: FontWeight.bold)),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildInputRow(
+    TextEditingController valueController,
+    TextEditingController probController,
+    int index,
+    Function(int) onRemove,
+    int totalRows,
+  ) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: Row(
+        children: [
+          Expanded(
+            flex: 2,
+            child: TextField(
+              controller: valueController,
+              decoration: InputDecoration(
+                hintText: '如: 0, 1, 2...',
+                border: OutlineInputBorder(),
+                contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+              ),
+              keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+            ),
+          ),
+          SizedBox(width: 16),
+          Expanded(
+            flex: 3,
+            child: TextField(
+              controller: probController,
+              decoration: InputDecoration(
+                hintText: '如: 1/2, 0.25, 0.3',
+                border: OutlineInputBorder(),
+                contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+              ),
+            ),
+          ),
+          SizedBox(width: 16),
+          Expanded(
+            flex: 1,
+            child: IconButton(
+              icon: Icon(Icons.remove_circle, color: Colors.red),
+              onPressed: totalRows > 1 ? () => onRemove(index) : null,
+              tooltip: '删除此行',
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildResultTable() {
+    return Card(
+      elevation: 2,
+      color: Colors.green[50],
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '卷积分布列 X + Y',
+              style: TextStyle(
+                fontSize: 18,
+                fontWeight: FontWeight.bold,
+                color: Colors.green[800],
+              ),
+            ),
+            SizedBox(height: 16),
+            _buildResultTableHeader(),
+            ..._convolutionResult.map(_buildResultRow).toList(),
+            SizedBox(height: 16),
+            _buildProbabilitySum(),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildResultTableHeader() {
+    return Container(
+      padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
+      decoration: BoxDecoration(
+        color: Colors.green[100],
+        borderRadius: BorderRadius.vertical(top: Radius.circular(8)),
+      ),
+      child: Row(
+        children: [
+          Expanded(
+            child: Text('X + Y 取值', style: TextStyle(fontWeight: FontWeight.bold)),
+          ),
+          Expanded(
+            child: Text('概率 (分数)', style: TextStyle(fontWeight: FontWeight.bold)),
+          ),
+          Expanded(
+            child: Text('概率 (小数)', style: TextStyle(fontWeight: FontWeight.bold)),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildResultRow(Map<String, Rational> entry) {
+    Rational value = entry['value']!;
+    Rational probability = entry['probability']!;
+    double decimalProb = probability.toDouble();
+
+    return Container(
+      padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
+      decoration: BoxDecoration(
+        border: Border(bottom: BorderSide(color: Colors.green[100]!)),
+      ),
+      child: Row(
+        children: [
+          Expanded(
+            child: Text(
+              value.toString(),
+              style: TextStyle(fontSize: 16),
+            ),
+          ),
+          Expanded(
+            child: Text(
+              _toFractionString(probability),
+              style: TextStyle(fontSize: 16, fontFamily: 'Monospace'),
+            ),
+          ),
+          Expanded(
+            child: Text(
+              decimalProb.toStringAsFixed(6),
+              style: TextStyle(fontSize: 16, fontFamily: 'Monospace'),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  String _toFractionString(Rational rational) {
+    if (rational.denominator == BigInt.one) {
+      return rational.numerator.toString();
+    }
+    return '${rational.numerator}/${rational.denominator}';
+  }
+
+  Widget _buildProbabilitySum() {
+    Rational total = _convolutionResult.fold(
+      Rational.zero,
+      (sum, entry) => sum + entry['probability']!,
+    );
+
+    return Container(
+      padding: EdgeInsets.all(12),
+      decoration: BoxDecoration(
+        color: Colors.green[100],
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: [
+          Text('概率总和:', style: TextStyle(fontWeight: FontWeight.bold)),
+          Text(
+            '${_toFractionString(total)} = ${total.toDouble().toStringAsFixed(6)}',
+            style: TextStyle(fontWeight: FontWeight.bold, fontFamily: 'Monospace'),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 108 - 0
lib/tools/cuo_wei_xiang_jian.dart

@@ -0,0 +1,108 @@
+// tools/cuo_wei_xiang_jian.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/math_utils.dart';
+
+class CuoWeiXiangJianPage extends StatefulWidget {
+  @override
+  _CuoWeiXiangJianPageState createState() => _CuoWeiXiangJianPageState();
+}
+
+class _CuoWeiXiangJianPageState extends State<CuoWeiXiangJianPage> {
+  final TextEditingController aController = TextEditingController();
+  final TextEditingController bController = TextEditingController();
+  final TextEditingController qController = TextEditingController();
+  final TextEditingController mController = TextEditingController();
+
+  String result = '';
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('错位相减法')),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text(
+              '通项为: aₙ = (a·n + b) · qⁿ⁺ᵐ',
+              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 20),
+            _buildInputField('a (系数)', aController),
+            _buildInputField('b (常数)', bController),
+            _buildInputField('q (公比)', qController),
+            _buildInputField('m (指数偏移)', mController),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算'),
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 16),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: TextField(
+        controller: controller,
+        keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+        decoration: InputDecoration(
+          labelText: label,
+          border: OutlineInputBorder(),
+          hintText: '输入分数如: 1/2 或小数',
+        ),
+      ),
+    );
+  }
+
+  void _calculate() {
+    try {
+      final a = MathUtils.parseRational(aController.text);
+      final b = MathUtils.parseRational(bController.text);
+      final q = MathUtils.parseRational(qController.text);
+      final m = int.parse(mController.text);
+
+      // 将指数统一为n-1
+      final aAdj = a * q.pow(m + 1);
+      final bAdj = b * q.pow(m + 1);
+
+      // 计算A,B
+      final A = aAdj / (q - Rational.one);
+      final B = (bAdj - A) / (q - Rational.one);
+
+      setState(() {
+        result = '结果: Sₙ = (A·n + B) · qⁿ - B\n'
+      'A = ${MathUtils.rationalToString(A)}\n'
+      'B = ${MathUtils.rationalToString(B)}';
+      });
+    } catch (e) {
+      setState(() {
+        result = '错误: $e';
+      });
+    }
+  }
+
+  @override
+  void dispose() {
+    aController.dispose();
+    bController.dispose();
+    qController.dispose();
+    mController.dispose();
+    super.dispose();
+  }
+}

+ 354 - 0
lib/tools/derivative_calculator.dart

@@ -0,0 +1,354 @@
+// tools/derivative_calculator.dart
+import 'package:flutter/material.dart';
+import 'package:math_expressions/math_expressions.dart';
+
+class DerivativeCalculatorPage extends StatefulWidget {
+  @override
+  _DerivativeCalculatorPageState createState() => _DerivativeCalculatorPageState();
+}
+
+class _DerivativeCalculatorPageState extends State<DerivativeCalculatorPage> {
+  final TextEditingController _functionController = TextEditingController();
+  final TextEditingController _pointController = TextEditingController();
+  String _result = '';
+  String _derivative = '';
+  bool _showPointInput = false;
+
+  @override
+  void dispose() {
+    _functionController.dispose();
+    _pointController.dispose();
+    super.dispose();
+  }
+
+  void _calculateDerivative() {
+    final String functionText = _functionController.text.trim();
+
+    if (functionText.isEmpty) {
+      setState(() {
+        _result = '请输入函数表达式';
+      _derivative = '';
+      });
+      return;
+    }
+
+    try {
+      // 解析函数表达式
+      Parser p = Parser();
+      Expression exp = p.parse(functionText);
+
+      // 对x求导
+      Expression deriv = exp.derive('x');
+
+      // 简化导数表达式
+      Expression simplifiedDeriv = deriv.simplify();
+
+      setState(() {
+        _derivative = '导函数: ${simplifiedDeriv.toString()}';
+
+      // 如果用户要求计算某点的导数值
+      if (_showPointInput && _pointController.text.isNotEmpty) {
+        try {
+          double point = double.parse(_pointController.text);
+          ContextModel cm = ContextModel();
+          cm.bindVariable(Variable('x'), Number(point));
+          double value = simplifiedDeriv.evaluate(EvaluationType.REAL, cm);
+          _result = '在 x = $point 处的导数值: ${value.toStringAsFixed(4)}';
+        } catch (e) {
+          _result = '点的格式错误,请输入有效的数字';
+        }
+      } else {
+        _result = _derivative;
+      }
+      });
+    } catch (e) {
+      setState(() {
+        _result = '表达式解析错误,请检查格式\n支持的格式示例: x^2 + 3*x + sin(x)';
+      _derivative = '';
+      });
+    }
+  }
+
+  void _clearAll() {
+    setState(() {
+      _functionController.clear();
+      _pointController.clear();
+      _result = '';
+    _derivative = '';
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('导数计算器', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+        actions: [
+          IconButton(
+            icon: Icon(Icons.info_outline),
+            onPressed: () {
+              _showInstructions();
+            },
+          ),
+        ],
+      ),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '函数表达式 (以x为变量):',
+              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 8),
+            TextField(
+              controller: _functionController,
+              decoration: InputDecoration(
+                hintText: '例如: x^2 + 3*x + sin(x)',
+                border: OutlineInputBorder(),
+                suffixIcon: IconButton(
+                  icon: Icon(Icons.info),
+                  onPressed: _showExpressionHelp,
+                ),
+              ),
+            ),
+            SizedBox(height: 16),
+
+            // 计算某点导数值的选项
+            Row(
+              children: [
+                Checkbox(
+                  value: _showPointInput,
+                  onChanged: (value) {
+                    setState(() {
+                      _showPointInput = value!;
+                      if (!value) {
+                        _pointController.clear();
+                      }
+                    });
+                  },
+                ),
+                Text('计算某点处的导数值'),
+              ],
+            ),
+
+            if (_showPointInput) ...[
+              SizedBox(height: 8),
+              Text(
+                '输入点的x坐标:',
+                style: TextStyle(fontSize: 14),
+              ),
+              SizedBox(height: 8),
+              TextField(
+                controller: _pointController,
+                decoration: InputDecoration(
+                  hintText: '例如: 2.5',
+                  border: OutlineInputBorder(),
+                ),
+                keyboardType: TextInputType.numberWithOptions(decimal: true),
+              ),
+            ],
+
+            SizedBox(height: 20),
+            Row(
+              children: [
+                Expanded(
+                  child: ElevatedButton(
+                    onPressed: _calculateDerivative,
+                    style: ElevatedButton.styleFrom(
+                      backgroundColor: Colors.blue,
+                      padding: EdgeInsets.symmetric(vertical: 12),
+                    ),
+                    child: Text('计算导数', style: TextStyle(color: Colors.white)),
+                  ),
+                ),
+                SizedBox(width: 10),
+                Expanded(
+                  child: ElevatedButton(
+                    onPressed: _clearAll,
+                    style: ElevatedButton.styleFrom(
+                      backgroundColor: Colors.grey,
+                      padding: EdgeInsets.symmetric(vertical: 12),
+                    ),
+                    child: Text('清空', style: TextStyle(color: Colors.white)),
+                  ),
+                ),
+              ],
+            ),
+
+            SizedBox(height: 20),
+            if (_result.isNotEmpty) ...[
+              Container(
+                width: double.infinity,
+                padding: EdgeInsets.all(16),
+                decoration: BoxDecoration(
+                  color: Colors.grey[50],
+                  borderRadius: BorderRadius.circular(8),
+                  border: Border.all(color: Colors.grey[300]!),
+                ),
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: [
+                    if (_derivative.isNotEmpty && _showPointInput) ...[
+                      Text(
+                        _derivative,
+                        style: TextStyle(fontSize: 14, color: Colors.grey[700]),
+                      ),
+                      SizedBox(height: 8),
+                    ],
+                    Text(
+                      _result,
+                      style: TextStyle(
+                        fontSize: 16,
+                        fontWeight: FontWeight.bold,
+                        color: Colors.blue[800],
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+            ],
+
+            Spacer(),
+            _buildExamples(),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildExamples() {
+    return Container(
+      width: double.infinity,
+      padding: EdgeInsets.all(16),
+      decoration: BoxDecoration(
+        color: Colors.blue[50],
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Text(
+            '表达式示例:',
+            style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue[800]),
+          ),
+          SizedBox(height: 8),
+          _buildExampleItem('x^2 + 3*x + 2'),
+          _buildExampleItem('sin(x)'),
+          _buildExampleItem('cos(x)'),
+          _buildExampleItem('ln(x)'),
+          _buildExampleItem('sqrt(x)'),
+          _buildExampleItem('(x+1)/(x-1)'),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildExampleItem(String example) {
+    return GestureDetector(
+      onTap: () {
+        setState(() {
+          _functionController.text = example;
+        });
+      },
+      child: Padding(
+        padding: EdgeInsets.symmetric(vertical: 4),
+        child: Text(
+          '• $example',
+          style: TextStyle(color: Colors.blue[700], fontSize: 14),
+        ),
+      ),
+    );
+  }
+
+  void _showInstructions() {
+    showDialog(
+      context: context,
+      builder: (context) => AlertDialog(
+        title: Text('使用说明'),
+        content: SingleChildScrollView(
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            mainAxisSize: MainAxisSize.min,
+            children: [
+              Text('1. 输入以x为自变量的函数表达式'),
+              SizedBox(height: 8),
+              Text('2. 支持的基本函数:'),
+              Text('   • sin(x), cos(x), tan(x)'),
+              Text('   • ln(x) - 自然对数'),
+              Text('   • sqrt(x) - 平方根'),
+              SizedBox(height: 8),
+              Text('3. 支持的操作符:'),
+              Text('   • +, -, *, /'),
+              Text('   • ^ - 幂运算'),
+              Text('   • () - 括号'),
+              SizedBox(height: 8),
+              Text('4. 可选计算某点处的导数值'),
+            ],
+          ),
+        ),
+        actions: [
+          TextButton(
+            onPressed: () => Navigator.pop(context),
+            child: Text('确定'),
+          ),
+        ],
+      ),
+    );
+  }
+
+  void _showExpressionHelp() {
+    showDialog(
+      context: context,
+      builder: (context) => AlertDialog(
+        title: Text('表达式格式帮助'),
+        content: SingleChildScrollView(
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            mainAxisSize: MainAxisSize.min,
+            children: [
+              Text('支持的数学表达式:'),
+              SizedBox(height: 8),
+              _buildHelpItem('x^2', 'x的平方'),
+              _buildHelpItem('3*x', '3乘以x'),
+              _buildHelpItem('sin(x)', '正弦函数'),
+              _buildHelpItem('cos(x)', '余弦函数'),
+              _buildHelpItem('ln(x)', '自然对数'),
+              _buildHelpItem('sqrt(x)', '平方根'),
+              _buildHelpItem('(x+1)/(x-1)', '分式'),
+            ],
+          ),
+        ),
+        actions: [
+          TextButton(
+            onPressed: () => Navigator.pop(context),
+            child: Text('确定'),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildHelpItem(String expression, String description) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 4),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Container(
+            width: 80,
+            child: Text(
+              expression,
+              style: TextStyle(fontFamily: 'Monospace', fontWeight: FontWeight.bold),
+            ),
+          ),
+          SizedBox(width: 10),
+          Expanded(child: Text(description)),
+        ],
+      ),
+    );
+  }
+}

+ 449 - 0
lib/tools/function_plotter.dart

@@ -0,0 +1,449 @@
+// tools/function_plotter.dart
+import 'package:flutter/material.dart';
+import 'package:math_expressions/math_expressions.dart';
+import 'package:fl_chart/fl_chart.dart';
+
+class FunctionPlotterPage extends StatefulWidget {
+  @override
+  _FunctionPlotterPageState createState() => _FunctionPlotterPageState();
+}
+
+class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
+  final List<TextEditingController> _functionControllers = [
+    TextEditingController(text: 'x^2'),
+  ];
+  final TextEditingController _xMinController = TextEditingController(text: '-5');
+  final TextEditingController _xMaxController = TextEditingController(text: '5');
+  final TextEditingController _yMinController = TextEditingController(text: '-10');
+  final TextEditingController _yMaxController = TextEditingController(text: '10');
+  
+  List<Color> _functionColors = [
+    Colors.blue,
+    Colors.red,
+    Colors.green,
+    Colors.orange,
+    Colors.purple,
+  ];
+  
+  List<String> _functionErrors = [];
+
+  @override
+  void dispose() {
+    for (var controller in _functionControllers) {
+      controller.dispose();
+    }
+    _xMinController.dispose();
+    _xMaxController.dispose();
+    _yMinController.dispose();
+    _yMaxController.dispose();
+    super.dispose();
+  }
+
+  void _addFunction() {
+    if (_functionControllers.length < 5) {
+      setState(() {
+        _functionControllers.add(TextEditingController());
+      });
+    }
+  }
+
+  void _removeFunction(int index) {
+    if (_functionControllers.length > 1) {
+      setState(() {
+        _functionControllers.removeAt(index);
+        if (_functionErrors.length > index) {
+          _functionErrors.removeAt(index);
+        }
+      });
+    }
+  }
+
+  List<FlSpot> _evaluateFunction(String function, double xMin, double xMax, int functionIndex) {
+    List<FlSpot> points = [];
+    
+    try {
+      Parser p = Parser();
+      Expression exp = p.parse(function);
+      ContextModel cm = ContextModel();
+      
+      int numberOfPoints = 200;
+      double step = (xMax - xMin) / numberOfPoints;
+      
+      for (int i = 0; i <= numberOfPoints; i++) {
+        double xValue = xMin + i * step;
+        
+        try {
+          cm.bindVariable(Variable('x'), Number(xValue));
+          double yValue = exp.evaluate(EvaluationType.REAL, cm);
+          
+          if (yValue.isFinite && yValue.abs() < 1e10) {
+            points.add(FlSpot(xValue, yValue));
+          } else {
+            points.add(FlSpot.nullSpot);
+          }
+        } catch (e) {
+          points.add(FlSpot.nullSpot);
+        }
+      }
+    } catch (e) {
+      if (_functionErrors.length <= functionIndex) {
+        _functionErrors.addAll(List.filled(functionIndex - _functionErrors.length + 1, ''));
+      }
+      _functionErrors[functionIndex] = '函数解析错误: ${e.toString()}';
+    }
+    
+    return points;
+  }
+
+  void _plotFunctions() {
+    setState(() {
+      _functionErrors.clear();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('函数图像绘制', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+        actions: [
+          IconButton(
+            icon: Icon(Icons.refresh),
+            onPressed: _plotFunctions,
+            tooltip: '重新绘制',
+          ),
+        ],
+      ),
+      body: SingleChildScrollView( // 添加外层滚动
+        child: Padding(
+          padding: EdgeInsets.all(16),
+          child: Column(
+            children: [
+              // 函数输入区域 - 移除固定高度约束
+              _buildFunctionInputs(),
+              
+              SizedBox(height: 16), // 添加间距
+              
+              // 绘图区域 - 设置固定高度
+              Container(
+                height: MediaQuery.of(context).size.height * 0.5, // 使用屏幕高度的一半
+                child: _buildPlotArea(),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildFunctionInputs() {
+    return Card(
+      elevation: 4,
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '函数表达式 (使用 x 作为变量)',
+              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 10),
+            Text(
+              '支持: +, -, *, /, ^, sin, cos, tan, ln, sqrt, e',
+              style: TextStyle(fontSize: 12, color: Colors.grey),
+            ),
+            SizedBox(height: 10),
+            
+            // 坐标范围设置 - 在小屏幕上改为垂直布局
+            LayoutBuilder(
+              builder: (context, constraints) {
+                if (constraints.maxWidth < 600) {
+                  // 小屏幕垂直布局
+                  return Column(
+                    children: [
+                      _buildRangeTextField(_xMinController, 'x最小值'),
+                      SizedBox(height: 10),
+                      _buildRangeTextField(_xMaxController, 'x最大值'),
+                      SizedBox(height: 10),
+                      _buildRangeTextField(_yMinController, 'y最小值'),
+                      SizedBox(height: 10),
+                      _buildRangeTextField(_yMaxController, 'y最大值'),
+                    ],
+                  );
+                } else {
+                  // 大屏幕水平布局
+                  return Row(
+                    children: [
+                      Expanded(child: _buildRangeTextField(_xMinController, 'x最小值')),
+                      SizedBox(width: 10),
+                      Expanded(child: _buildRangeTextField(_xMaxController, 'x最大值')),
+                      SizedBox(width: 10),
+                      Expanded(child: _buildRangeTextField(_yMinController, 'y最小值')),
+                      SizedBox(width: 10),
+                      Expanded(child: _buildRangeTextField(_yMaxController, 'y最大值')),
+                    ],
+                  );
+                }
+              },
+            ),
+            SizedBox(height: 10),
+            
+            // 函数输入列表 - 设置最大高度并允许滚动
+            Container(
+              constraints: BoxConstraints(
+                maxHeight: 200, // 限制最大高度
+              ),
+              child: ListView.builder(
+                shrinkWrap: true,
+                itemCount: _functionControllers.length,
+                itemBuilder: (context, index) {
+                  return Padding(
+                    padding: EdgeInsets.symmetric(vertical: 5),
+                    child: Row(
+                      children: [
+                        Container(
+                          width: 20,
+                          height: 20,
+                          decoration: BoxDecoration(
+                            color: _functionColors[index],
+                            shape: BoxShape.circle,
+                          ),
+                        ),
+                        SizedBox(width: 10),
+                        Expanded(
+                          child: TextField(
+                            controller: _functionControllers[index],
+                            decoration: InputDecoration(
+                              hintText: '例如: x^2, sin(x), ln(x)',
+                              border: OutlineInputBorder(),
+                              errorText: _functionErrors.length > index ? _functionErrors[index] : null,
+                            ),
+                            onChanged: (value) => _plotFunctions(),
+                          ),
+                        ),
+                        SizedBox(width: 10),
+                        if (_functionControllers.length > 1)
+                          IconButton(
+                            icon: Icon(Icons.remove, color: Colors.red),
+                            onPressed: () => _removeFunction(index),
+                          ),
+                      ],
+                    ),
+                  );
+                },
+              ),
+            ),
+            
+            SizedBox(height: 10),
+            
+            // 添加函数按钮
+            if (_functionControllers.length < 5)
+              Center(
+                child: ElevatedButton.icon(
+                  onPressed: () {
+                    _addFunction();
+                    _plotFunctions();
+                  },
+                  icon: Icon(Icons.add),
+                  label: Text('添加函数'),
+                  style: ElevatedButton.styleFrom(
+                    backgroundColor: Colors.blue,
+                    foregroundColor: Colors.white,
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  // 辅助方法:构建范围输入框
+  Widget _buildRangeTextField(TextEditingController controller, String label) {
+    return TextField(
+      controller: controller,
+      decoration: InputDecoration(
+        labelText: label,
+        border: OutlineInputBorder(),
+        contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+      ),
+      keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+      onChanged: (value) => _plotFunctions(),
+    );
+  }
+
+  Widget _buildPlotArea() {
+    return Card(
+      elevation: 4,
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text(
+              '函数图像',
+              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 10),
+            Expanded(
+              child: _buildPlot(),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildPlot() {
+    double xMin, xMax, yMin, yMax;
+    
+    try {
+      xMin = double.parse(_xMinController.text);
+      xMax = double.parse(_xMaxController.text);
+      yMin = double.parse(_yMinController.text);
+      yMax = double.parse(_yMaxController.text);
+    } catch (e) {
+      return Center(
+        child: Text('请输入有效的坐标范围', style: TextStyle(color: Colors.red)),
+      );
+    }
+    
+    if (xMin >= xMax || yMin >= yMax) {
+      return Center(
+        child: Text('最小值必须小于最大值', style: TextStyle(color: Colors.red)),
+      );
+    }
+    
+    List<LineChartBarData> lineBars = [];
+    
+    for (int i = 0; i < _functionControllers.length; i++) {
+      String function = _functionControllers[i].text.trim();
+      if (function.isEmpty) continue;
+      
+      List<FlSpot> spots = _evaluateFunction(function, xMin, xMax, i);
+      
+      if (spots.isNotEmpty) {
+        lineBars.add(
+          LineChartBarData(
+            spots: spots,
+            isCurved: true,
+            color: _functionColors[i],
+            barWidth: 2,
+            isStrokeCapRound: true,
+            dotData: FlDotData(show: false),
+            belowBarData: BarAreaData(show: false),
+          ),
+        );
+      }
+    }
+    
+    if (lineBars.isEmpty) {
+      return Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Icon(Icons.functions, size: 50, color: Colors.grey),
+            SizedBox(height: 10),
+            Text('暂无函数图像可显示', style: TextStyle(color: Colors.grey)),
+            Text('请输入有效的函数表达式', style: TextStyle(color: Colors.grey, fontSize: 12)),
+          ],
+        ),
+      );
+    }
+    
+    return LineChart(
+      LineChartData(
+        clipData: FlClipData.all(),
+        gridData: FlGridData(
+          show: true,
+          drawVerticalLine: true,
+          drawHorizontalLine: true,
+          getDrawingHorizontalLine: (value) {
+            return FlLine(
+              color: Colors.grey.withOpacity(0.3),
+              strokeWidth: 1,
+            );
+          },
+          getDrawingVerticalLine: (value) {
+            return FlLine(
+              color: Colors.grey.withOpacity(0.3),
+              strokeWidth: 1,
+            );
+          },
+        ),
+        titlesData: FlTitlesData(
+          show: true,
+          bottomTitles: AxisTitles(
+            sideTitles: SideTitles(
+              showTitles: true,
+              reservedSize: 30,
+              interval: _calculateInterval(xMin, xMax),
+              getTitlesWidget: (value, meta) {
+                return Text(
+                  value.toStringAsFixed(1),
+                  style: TextStyle(fontSize: 10, color: Colors.black),
+                );
+              },
+            ),
+          ),
+          leftTitles: AxisTitles(
+            sideTitles: SideTitles(
+              showTitles: true,
+              reservedSize: 40,
+              interval: _calculateInterval(yMin, yMax),
+              getTitlesWidget: (value, meta) {
+                return Text(
+                  value.toStringAsFixed(1),
+                  style: TextStyle(fontSize: 10, color: Colors.black),
+                );
+              },
+            ),
+          ),
+          rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
+          topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
+        ),
+        borderData: FlBorderData(
+          show: true,
+          border: Border.all(color: Colors.black, width: 2),
+        ),
+        minX: xMin,
+        maxX: xMax,
+        minY: yMin,
+        maxY: yMax,
+        lineBarsData: lineBars,
+        lineTouchData: LineTouchData(enabled: false),
+        extraLinesData: ExtraLinesData(
+          horizontalLines: [
+            HorizontalLine(
+              y: 0,
+              color: Colors.black,
+              strokeWidth: 1,
+            ),
+          ],
+          verticalLines: [
+            VerticalLine(
+              x: 0,
+              color: Colors.black,
+              strokeWidth: 1,
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  double _calculateInterval(double min, double max) {
+    double range = max - min;
+    if (range <= 0) return 1.0;
+    
+    if (range <= 2) return 0.5;
+    if (range <= 5) return 1.0;
+    if (range <= 10) return 2.0;
+    if (range <= 20) return 5.0;
+    if (range <= 50) return 10.0;
+    if (range <= 100) return 20.0;
+    return 50.0;
+  }
+}

+ 276 - 0
lib/tools/histogram.dart

@@ -0,0 +1,276 @@
+// tools/histogram.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/math_utils.dart';
+import 'package:fl_chart/fl_chart.dart';
+
+class HistogramPage extends StatefulWidget {
+  @override
+  _HistogramPageState createState() => _HistogramPageState();
+}
+
+class _HistogramPageState extends State<HistogramPage> {
+  final TextEditingController leftEndpointController = TextEditingController();
+  final TextEditingController groupDistanceController = TextEditingController();
+  final TextEditingController frequenciesController = TextEditingController();
+  final TextEditingController percentileController = TextEditingController();
+
+  String result = '';
+  List<BarChartGroupData>? barGroups;
+  List<String>? groupLabels;
+  double? maxFrequencyDensity;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('直方图统计')),
+      body: SingleChildScrollView( // 添加滚动视图
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            _buildInputField('左端点', leftEndpointController),
+            _buildInputField('组距', groupDistanceController),
+            _buildInputField('各组频率(用空格分隔)', frequenciesController),
+            _buildInputField('百分位数(可选)', percentileController),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算'),
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 14, fontFamily: 'Monospace'),
+                  ),
+                ),
+              ),
+            if (barGroups != null && barGroups!.isNotEmpty)
+              SizedBox( // 将 Expanded 改为 SizedBox 并指定高度
+                height: 400, // 设置固定高度
+                child: Card(
+                  child: Padding(
+                    padding: EdgeInsets.all(16),
+                    child: Column(
+                      children: [
+                        Text(
+                          '直方图',
+                          style: TextStyle(
+                            fontSize: 18,
+                            fontWeight: FontWeight.bold,
+                          ),
+                        ),
+                        SizedBox(height: 10),
+                        Expanded( // 这个 Expanded 可以保留,因为它是在 Column 内部
+                          child: BarChart(
+                            BarChartData(
+                              barGroups: barGroups,
+                              alignment: BarChartAlignment.spaceBetween,
+                              groupsSpace: 0,
+                              barTouchData: BarTouchData(
+                                enabled: false,
+                              ),
+                              borderData: FlBorderData(show: true),
+                              gridData: FlGridData(show: false),
+                              titlesData: FlTitlesData(
+                                bottomTitles: AxisTitles(
+                                  sideTitles: SideTitles(
+                                    showTitles: true,
+                                    getTitlesWidget: (value, meta) {
+                                      final index = value.toInt();
+                                      if (index >= 0 && index < (groupLabels?.length ?? 0)) {
+                                        return Padding(
+                                          padding: const EdgeInsets.only(top: 8.0),
+                                          child: Text(
+                                            groupLabels![index],
+                                            style: TextStyle(
+                                              fontSize: 10,
+                                              fontWeight: FontWeight.bold,
+                                            ),
+                                            textAlign: TextAlign.center,
+                                          ),
+                                        );
+                                      }
+                                      return Text('');
+                                    },
+                                    reservedSize: 40,
+                                  ),
+                                ),
+                                leftTitles: AxisTitles(
+                                  sideTitles: SideTitles(
+                                    showTitles: true,
+                                    reservedSize: 40,
+                                    getTitlesWidget: (value, meta) {
+                                      return Text(value.toStringAsFixed(2));
+                                    },
+                                  ),
+                                ),
+                                rightTitles: AxisTitles(
+                                  sideTitles: SideTitles(showTitles: false),
+                                ),
+                                topTitles: AxisTitles(
+                                  sideTitles: SideTitles(showTitles: false),
+                                ),
+                              ),
+                              maxY: maxFrequencyDensity != null 
+                                  ? (maxFrequencyDensity! * 1.1)
+                                  : 1.0,
+                            ),
+                          ),
+                        ),
+                      ],
+                    ),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: TextField(
+        controller: controller,
+        keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+        decoration: InputDecoration(
+          labelText: label,
+          border: OutlineInputBorder(),
+          hintText: label.contains('频率') ? '用空格分隔多个值' : '输入分数如: 1/2 或小数',
+        ),
+      ),
+    );
+  }
+
+  void _calculate() {
+    try {
+      final a = MathUtils.parseRational(leftEndpointController.text);
+      final d = MathUtils.parseRational(groupDistanceController.text);
+      final frequencies = frequenciesController.text.split(' ').map((s) => MathUtils.parseRational(s)).toList();
+
+      Rational sumFreq = MathUtils.zero;
+      for (final f in frequencies) {
+        sumFreq += f;
+      }
+
+      if (sumFreq != MathUtils.one) {
+        setState(() {
+          result = '错误: 频率之和必须为1,当前为 ${MathUtils.rationalToString(sumFreq)}';
+          barGroups = null;
+          groupLabels = null;
+        });
+        return;
+      }
+
+      // 计算均值
+      Rational mean = MathUtils.zero;
+      for (int i = 0; i < frequencies.length; i++) {
+        mean += (a + MathUtils.fromInt(i) * d + d / MathUtils.two) * frequencies[i];
+      }
+
+      // 计算方差
+      Rational variance = MathUtils.zero;
+      for (int i = 0; i < frequencies.length; i++) {
+        final midPoint = a + MathUtils.fromInt(i) * d + d / MathUtils.two;
+        variance += (midPoint - mean).pow(2) * frequencies[i];
+      }
+
+      String output = '均值: ${MathUtils.rationalToString(mean)} (近似值: ${mean.toDouble()})\n'
+      '方差: ${MathUtils.rationalToString(variance)} (近似值: ${variance.toDouble()})\n';
+
+      // 计算中位数
+      Rational cumulative = MathUtils.zero;
+      for (int i = 0; i < frequencies.length; i++) {
+        if (cumulative + frequencies[i] > MathUtils.half) {
+          final median = a + MathUtils.fromInt(i) * d - (cumulative - MathUtils.half) / frequencies[i] * d;
+          output += '中位数: ${MathUtils.rationalToString(median)} (近似值: ${median.toDouble()})\n';
+          break;
+        }
+        cumulative += frequencies[i];
+      }
+
+      // 计算百分位数
+      if (percentileController.text.isNotEmpty) {
+        final p = MathUtils.parseRational(percentileController.text);
+        cumulative = MathUtils.zero;
+        for (int i = 0; i < frequencies.length; i++) {
+          if (cumulative + frequencies[i] > p) {
+            final percentile = a + MathUtils.fromInt(i) * d - (cumulative - p) / frequencies[i] * d;
+            output += '${MathUtils.rationalToString(p)} 百分位数: ${MathUtils.rationalToString(percentile)} (近似值: ${percentile.toDouble()})';
+            break;
+          }
+          cumulative += frequencies[i];
+        }
+      }
+
+      // 生成直方图数据
+      _generateHistogramData(a, d, frequencies);
+
+      setState(() {
+        result = output;
+      });
+    } catch (e) {
+      setState(() {
+        result = '错误: $e';
+        barGroups = null;
+        groupLabels = null;
+      });
+    }
+  }
+
+  void _generateHistogramData(Rational a, Rational d, List<Rational> frequencies) {
+    barGroups = [];
+    groupLabels = [];
+    maxFrequencyDensity = 0.0;
+
+    for (int i = 0; i < frequencies.length; i++) {
+      final left = a + MathUtils.fromInt(i) * d;
+      final right = left + d;
+      
+      // 计算频率/组距作为纵坐标
+      final frequencyDensity = (frequencies[i] / d).toDouble();
+      
+      // 更新最大频率密度
+      if (frequencyDensity > maxFrequencyDensity!) {
+        maxFrequencyDensity = frequencyDensity;
+      }
+
+      barGroups!.add(
+        BarChartGroupData(
+          x: i,
+          barRods: [
+            BarChartRodData(
+              toY: frequencyDensity,
+              width: 16,
+              color: Colors.blue,
+              borderRadius: BorderRadius.zero,
+            ),
+          ],
+        ),
+      );
+
+      groupLabels!.add(
+        '[${MathUtils.rationalToString(left)}, ${MathUtils.rationalToString(right)})',
+      );
+    }
+
+    // 确保maxFrequencyDensity不为0
+    if (maxFrequencyDensity == 0.0) {
+      maxFrequencyDensity = 1.0;
+    }
+  }
+
+  @override
+  void dispose() {
+    leftEndpointController.dispose();
+    groupDistanceController.dispose();
+    frequenciesController.dispose();
+    percentileController.dispose();
+    super.dispose();
+  }
+}

+ 269 - 0
lib/tools/hypergeometric_distribution.dart

@@ -0,0 +1,269 @@
+// tools/hypergeometric_distribution.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import 'package:fl_chart/fl_chart.dart';
+import '../utils/math_utils.dart';
+
+class HypergeometricDistributionPage extends StatefulWidget {
+  @override
+  _HypergeometricDistributionPageState createState() => _HypergeometricDistributionPageState();
+}
+
+class _HypergeometricDistributionPageState extends State<HypergeometricDistributionPage> {
+  final TextEditingController nController = TextEditingController(); // 抽取数
+  final TextEditingController NController = TextEditingController(); // 总数
+  final TextEditingController MController = TextEditingController(); // 满足条件数
+
+  String expectation = '';
+  String variance = '';
+  List<String> probabilities = [];
+  List<BarChartGroupData> barGroups = [];
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('超几何分布')),
+      body: SingleChildScrollView( // 添加滚动支持
+        child: Padding(
+          padding: EdgeInsets.all(16),
+          child: Column(
+            children: [
+              _buildInputField('总数 N', NController),
+              _buildInputField('满足条件数 M', MController),
+              _buildInputField('抽取数 n', nController),
+              SizedBox(height: 20),
+              ElevatedButton(
+                onPressed: _calculate,
+                child: Text('计算'),
+              ),
+              SizedBox(height: 20),
+              if (expectation.isNotEmpty) ...[
+                _buildResultCard('期望', expectation),
+                _buildResultCard('方差', variance),
+                SizedBox(height: 10),
+                Text('概率分布:', style: TextStyle(fontWeight: FontWeight.bold)),
+                if (barGroups.isNotEmpty) ...[
+                  SizedBox(height: 10),
+                  Container(
+                    height: 300,
+                    child: BarChart(
+                      BarChartData(
+                        barTouchData: BarTouchData(
+                          enabled: false,
+                        ),
+                        titlesData: FlTitlesData(
+                          show: true,
+                          bottomTitles: AxisTitles(
+                            sideTitles: SideTitles(
+                              showTitles: true,
+                              getTitlesWidget: (value, meta) {
+                                return Padding(
+                                  padding: const EdgeInsets.only(top: 8.0),
+                                  child: Text(
+                                    value.toInt().toString(),
+                                    style: TextStyle(fontSize: 10),
+                                  ),
+                                );
+                              },
+                              reservedSize: 30,
+                            ),
+                          ),
+                          leftTitles: AxisTitles(
+                            sideTitles: SideTitles(
+                              showTitles: true,
+                              getTitlesWidget: (value, meta) {
+                                if (value % 0.1 == 0) {
+                                  return Text(
+                                    value.toStringAsFixed(1),
+                                    style: TextStyle(fontSize: 10),
+                                  );
+                                }
+                                return Text('');
+                              },
+                              reservedSize: 40,
+                            ),
+                          ),
+                          topTitles: AxisTitles(
+                            sideTitles: SideTitles(showTitles: false),
+                          ),
+                          rightTitles: AxisTitles(
+                            sideTitles: SideTitles(showTitles: false),
+                          ),
+                        ),
+                        borderData: FlBorderData(show: true),
+                        barGroups: barGroups,
+                        gridData: FlGridData(show: true),
+                        alignment: BarChartAlignment.spaceAround,
+                        maxY: _getMaxY(),
+                      ),
+                    ),
+                  ),
+                  SizedBox(height: 10),
+                ],
+                // 移除 Expanded 和 ListView.builder,改用简单的 Column
+                Column(
+                  children: probabilities.map((prob) => 
+                    Card(
+                      child: ListTile(
+                        title: Text(prob),
+                      ),
+                    )
+                  ).toList(),
+                ),
+              ],
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  double _getMaxY() {
+    if (barGroups.isEmpty) return 1.0;
+    double maxY = 0.0;
+    for (var group in barGroups) {
+      for (var rod in group.barRods) {
+        if (rod.toY > maxY) maxY = rod.toY;
+      }
+    }
+    return maxY * 1.1;
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: TextField(
+        controller: controller,
+        keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+        decoration: InputDecoration(
+          labelText: label,
+          border: OutlineInputBorder(),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildResultCard(String title, String value) {
+    return Card(
+      child: ListTile(
+        title: Text(title),
+        subtitle: Text(value),
+      ),
+    );
+  }
+
+  Rational _combination(int n, int k) {
+    if (k < 0 || k > n) return Rational.zero;
+    if (k == 0 || k == n) return Rational.one;
+
+    BigInt result = BigInt.one;
+    for (int i = 1; i <= k; i++) {
+      result = result * BigInt.from(n - i + 1) ~/ BigInt.from(i);
+    }
+    return Rational(result);
+  }
+
+  void _calculate() {
+    try {
+      final N = int.parse(NController.text);
+      final M = int.parse(MController.text);
+      final n = int.parse(nController.text);
+
+      final m = n - (N - M) > 0 ? n - (N - M) : 0;
+      final r = n < M ? n : M;
+
+      final EX = MathUtils.fromInt(n * M) / MathUtils.fromInt(N);
+      final DX = MathUtils.fromInt(n * M * (N - M) * (N - n)) /
+      (MathUtils.fromInt(N) * MathUtils.fromInt(N) * MathUtils.fromInt(N - 1));
+
+      final probs = <String>[];
+      final barGroupsData = <BarChartGroupData>[];
+
+      // 首先计算所有概率并找到最大值
+      List<double> probValues = [];
+      double maxProb = 0.0;
+      
+      for (int k = m; k <= r; k++) {
+        final combMk = _combination(M, k);
+        final combNM_nk = _combination(N - M, n - k);
+        final combNn = _combination(N, n);
+        final prob = combMk * combNM_nk / combNn;
+        final probDouble = prob.toDouble();
+        
+        probValues.add(probDouble);
+        if (probDouble > maxProb) {
+          maxProb = probDouble;
+        }
+        
+        probs.add('P(X = $k) = ${MathUtils.rationalToString(prob)}');
+      }
+
+      // 找到所有具有最大概率的k值
+      List<int> maxProbIndices = [];
+      for (int i = 0; i < probValues.length; i++) {
+        if (probValues[i] == maxProb) {
+          maxProbIndices.add(m + i);
+        }
+      }
+
+      // 确定最高组的范围
+      int minMaxIndex = maxProbIndices.reduce((a, b) => a < b ? a : b);
+      int maxMaxIndex = maxProbIndices.reduce((a, b) => a > b ? a : b);
+
+      // 构建柱状图数据
+      for (int i = 0; i <= r - m; i++) {
+        int k = m + i;
+        double probDouble = probValues[i];
+        
+        barGroupsData.add(
+          BarChartGroupData(
+            x: k,
+            barRods: [
+              BarChartRodData(
+                toY: probDouble,
+                color: _getBarColor(k, minMaxIndex, maxMaxIndex),
+                width: 16,
+                borderRadius: BorderRadius.circular(4),
+              ),
+            ],
+          ),
+        );
+      }
+
+      setState(() {
+        expectation = 'EX = ${MathUtils.rationalToString(EX)}';
+        variance = 'DX = ${MathUtils.rationalToString(DX)}';
+        probabilities = probs;
+        barGroups = barGroupsData;
+      });
+    } catch (e) {
+      setState(() {
+        expectation = '错误: $e';
+        variance = '';
+        probabilities = [];
+        barGroups = [];
+      });
+    }
+  }
+
+  Color _getBarColor(int x, int minMaxIndex, int maxMaxIndex) {
+    if (x >= minMaxIndex && x <= maxMaxIndex) {
+      // 最高组(可能有多组)
+      return Colors.red;
+    } else if (x < minMaxIndex) {
+      // 最高组左边的组
+      return Colors.blue.shade300;
+    } else {
+      // 最高组右边的组
+      return Colors.green.shade300;
+    }
+  }
+
+  @override
+  void dispose() {
+    nController.dispose();
+    NController.dispose();
+    MController.dispose();
+    super.dispose();
+  }
+}

+ 116 - 0
lib/tools/mixed_variance.dart

@@ -0,0 +1,116 @@
+// tools/mixed_variance.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/math_utils.dart';
+
+class MixedVariancePage extends StatefulWidget {
+  @override
+  _MixedVariancePageState createState() => _MixedVariancePageState();
+}
+
+class _MixedVariancePageState extends State<MixedVariancePage> {
+  final TextEditingController sampleSizesController = TextEditingController();
+  final TextEditingController meansController = TextEditingController();
+  final TextEditingController variancesController = TextEditingController();
+
+  String result = '';
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('混合方差')),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            _buildInputField('各层样本量(用空格分隔)', sampleSizesController),
+            _buildInputField('各层均值(用空格分隔)', meansController),
+            _buildInputField('各层方差(用空格分隔)', variancesController),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算'),
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 16),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: TextField(
+        controller: controller,
+        keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+        decoration: InputDecoration(
+          labelText: label,
+          border: OutlineInputBorder(),
+          hintText: label.contains('分隔') ? '用空格分隔多个值' : '输入分数如: 1/2 或小数',
+        ),
+      ),
+    );
+  }
+
+  void _calculate() {
+    try {
+      final sizes = sampleSizesController.text.split(' ').map((s) => MathUtils.parseRational(s)).toList();
+      final means = meansController.text.split(' ').map((s) => MathUtils.parseRational(s)).toList();
+      final variances = variancesController.text.split(' ').map((s) => MathUtils.parseRational(s)).toList();
+
+      if (sizes.length != means.length || sizes.length != variances.length) {
+        setState(() {
+          result = '错误: 输入的数据长度不一致';
+        });
+        return;
+      }
+
+      Rational totalSize = Rational.zero;
+      Rational weightedSum = Rational.zero;
+
+      for (int i = 0; i < sizes.length; i++) {
+        totalSize += sizes[i];
+        weightedSum += sizes[i] * means[i];
+      }
+
+      final overallMean = weightedSum / totalSize;
+
+      Rational mixedVariance = Rational.zero;
+      for (int i = 0; i < sizes.length; i++) {
+        mixedVariance += sizes[i] * (variances[i] + (means[i] - overallMean).pow(2)) / totalSize;
+      }
+
+      // 获取近似值
+      double overallMeanApprox = overallMean.toDouble();
+      double mixedVarianceApprox = mixedVariance.toDouble();
+
+      setState(() {
+        result = '总均值 = ${MathUtils.rationalToString(overallMean)} (≈${overallMeanApprox.toStringAsFixed(4)})\n'
+      '混合方差 = ${MathUtils.rationalToString(mixedVariance)} (≈${mixedVarianceApprox.toStringAsFixed(4)})';
+      });
+    } catch (e) {
+      setState(() {
+        result = '错误: $e';
+      });
+    }
+  }
+
+  @override
+  void dispose() {
+    sampleSizesController.dispose();
+    meansController.dispose();
+    variancesController.dispose();
+    super.dispose();
+  }
+}

+ 408 - 0
lib/tools/parametric_plotter.dart

@@ -0,0 +1,408 @@
+// tools/parametric_plotter.dart
+import 'package:flutter/material.dart';
+import 'package:math_expressions/math_expressions.dart';
+import 'package:fl_chart/fl_chart.dart';
+
+class ParametricPlotterPage extends StatefulWidget {
+  @override
+  _ParametricPlotterPageState createState() => _ParametricPlotterPageState();
+}
+
+class _ParametricPlotterPageState extends State<ParametricPlotterPage> {
+  final TextEditingController xController = TextEditingController(text: 'cos(t)');
+  final TextEditingController yController = TextEditingController(text: 'sin(t)');
+  final TextEditingController tMinController = TextEditingController(text: '0');
+  final TextEditingController tMaxController = TextEditingController(text: '6.3');
+  final TextEditingController stepController = TextEditingController(text: '0.1');
+
+  List<FlSpot> points = [];
+  String errorMessage = '';
+  bool isLoading = false;
+
+  double minX = 0;
+  double maxX = 0;
+  double minY = 0;
+  double maxY = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      _plotGraph();
+    });
+  }
+
+  void _plotGraph() {
+    setState(() {
+      isLoading = true;
+      errorMessage = '';
+    });
+
+    try {
+      final tMin = double.parse(tMinController.text);
+      final tMax = double.parse(tMaxController.text);
+      final step = double.parse(stepController.text);
+
+      if (tMin >= tMax) {
+        throw Exception('t最小值必须小于t最大值');
+      }
+      if (step <= 0) {
+        throw Exception('步长必须大于0');
+      }
+
+      final xExpr = _parseExpression(xController.text);
+      final yExpr = _parseExpression(yController.text);
+
+      List<FlSpot> newPoints = [];
+      double currentMinX = double.infinity;
+      double currentMaxX = -double.infinity;
+      double currentMinY = double.infinity;
+      double currentMaxY = -double.infinity;
+
+      for (double t = tMin; t <= tMax; t += step) {
+        try {
+          final x = _evaluateExpression(xExpr, t);
+          final y = _evaluateExpression(yExpr, t);
+
+          if (x.isFinite && y.isFinite) {
+            newPoints.add(FlSpot(x, y));
+            currentMinX = x < currentMinX ? x : currentMinX;
+            currentMaxX = x > currentMaxX ? x : currentMaxX;
+            currentMinY = y < currentMinY ? y : currentMinY;
+            currentMaxY = y > currentMaxY ? y : currentMaxY;
+          }
+        } catch (e) {
+          // 跳过无效点
+        }
+      }
+
+      if (newPoints.isEmpty) {
+        throw Exception('未生成有效数据点,请检查参数方程');
+      }
+
+      setState(() {
+        points = newPoints;
+        minX = currentMinX;
+        maxX = currentMaxX;
+        minY = currentMinY;
+        maxY = currentMaxY;
+      });
+    } catch (e) {
+      setState(() {
+        errorMessage = e.toString();
+        points = [];
+      });
+    } finally {
+      setState(() {
+        isLoading = false;
+      });
+    }
+  }
+
+  Expression _parseExpression(String expression) {
+  // 清理表达式中的空格
+  String cleaned = expression.replaceAll(' ', '');
+  
+  // 将常见函数名转换为math_expressions识别的格式
+  cleaned = cleaned.replaceAllMapped(RegExp(r'(\d)([a-zA-Z])'), (match) {
+    return '${match.group(1)}*${match.group(2)}';
+  });
+  
+  // 移除这行,因为math_expressions使用^作为幂运算符
+  // cleaned = cleaned.replaceAll('^', '**');
+  
+  try {
+    Parser p = Parser();
+    return p.parse(cleaned);
+  } catch (e) {
+    throw Exception('表达式解析错误: $e');
+  }
+}
+
+  double _evaluateExpression(Expression expr, double t) {
+    ContextModel cm = ContextModel()
+      ..bindVariable(Variable('t'), Number(t))
+      ..bindVariable(Variable('pi'), Number(3.141592653589793))
+      ..bindVariable(Variable('e'), Number(2.718281828459045));
+
+    try {
+      return expr.evaluate(EvaluationType.REAL, cm);
+    } catch (e) {
+      throw Exception('在 t=$t 时计算错误: $e');
+    }
+  }
+
+  Widget _buildInputPanel() {
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.all(16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '参数方程设置',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 16),
+            TextField(
+              controller: xController,
+              decoration: InputDecoration(
+                labelText: 'x = f(t)',
+                hintText: '例如: cos(t), t^2, 2*t+1',
+                border: OutlineInputBorder(),
+              ),
+            ),
+            SizedBox(height: 12),
+            TextField(
+              controller: yController,
+              decoration: InputDecoration(
+                labelText: 'y = g(t)',
+                hintText: '例如: sin(t), t^3, 3*t-2',
+                border: OutlineInputBorder(),
+              ),
+            ),
+            SizedBox(height: 16),
+            Row(
+              children: [
+                Expanded(
+                  child: TextField(
+                    controller: tMinController,
+                    decoration: InputDecoration(
+                      labelText: 't最小值',
+                      border: OutlineInputBorder(),
+                    ),
+                    keyboardType: TextInputType.numberWithOptions(decimal: true),
+                  ),
+                ),
+                SizedBox(width: 12),
+                Expanded(
+                  child: TextField(
+                    controller: tMaxController,
+                    decoration: InputDecoration(
+                      labelText: 't最大值',
+                      border: OutlineInputBorder(),
+                    ),
+                    keyboardType: TextInputType.numberWithOptions(decimal: true),
+                  ),
+                ),
+                SizedBox(width: 12),
+                Expanded(
+                  child: TextField(
+                    controller: stepController,
+                    decoration: InputDecoration(
+                      labelText: '步长',
+                      border: OutlineInputBorder(),
+                    ),
+                    keyboardType: TextInputType.numberWithOptions(decimal: true),
+                  ),
+                ),
+              ],
+            ),
+            SizedBox(height: 16),
+            if (errorMessage.isNotEmpty)
+              Container(
+                width: double.infinity,
+                padding: EdgeInsets.all(12),
+                decoration: BoxDecoration(
+                  color: Colors.red[50],
+                  borderRadius: BorderRadius.circular(8),
+                  border: Border.all(color: Colors.red),
+                ),
+                child: Text(
+                  errorMessage,
+                  style: TextStyle(color: Colors.red),
+                ),
+              ),
+            SizedBox(height: 16),
+            ElevatedButton.icon(
+              onPressed: _plotGraph,
+              icon: Icon(Icons.show_chart),
+              label: Text('绘制图像'),
+              style: ElevatedButton.styleFrom(
+                minimumSize: Size(double.infinity, 50),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+ Widget _buildGraph() {
+  if (points.isEmpty) {
+    return Container(
+      height: 300,
+      child: Center(
+        child: Text(
+          '暂无数据',
+          style: TextStyle(fontSize: 16, color: Colors.grey),
+        ),
+      ),
+    );
+  }
+
+  // 计算坐标轴范围,确保x和y轴有相同的缩放比例
+  double rangeX = maxX - minX;
+  double rangeY = maxY - minY;
+  double maxRange = rangeX > rangeY ? rangeX : rangeY;
+  
+  double centerX = (minX + maxX) / 2;
+  double centerY = (minY + maxY) / 2;
+  
+  double adjustedMinX = centerX - maxRange / 2;
+  double adjustedMaxX = centerX + maxRange / 2;
+  double adjustedMinY = centerY - maxRange / 2;
+  double adjustedMaxY = centerY + maxRange / 2;
+
+  // 添加一些边距
+  double margin = maxRange * 0.1;
+  adjustedMinX -= margin;
+  adjustedMaxX += margin;
+  adjustedMinY -= margin;
+  adjustedMaxY += margin;
+
+  return Card(
+    elevation: 4,
+    margin: EdgeInsets.all(16),
+    child: Padding(
+      padding: EdgeInsets.all(16),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Text(
+            '参数方程图像',
+            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+          ),
+          SizedBox(height: 16),
+          // 修复:使用 AspectRatio 确保图表始终保持正方形
+          AspectRatio(
+            aspectRatio: 1, // 强制宽高比为1:1
+            child: Container(
+              decoration: BoxDecoration(
+                border: Border.all(color: Colors.grey[300]!),
+                borderRadius: BorderRadius.circular(8),
+              ),
+              child: LineChart(
+                LineChartData(
+                  gridData: FlGridData(show: true),
+                  titlesData: FlTitlesData(
+                    show: true,
+                    bottomTitles: AxisTitles(
+                      sideTitles: SideTitles(showTitles: true),
+                    ),
+                    leftTitles: AxisTitles(
+                      sideTitles: SideTitles(showTitles: true),
+                    ),
+                    rightTitles: AxisTitles(
+                      sideTitles: SideTitles(showTitles: false),
+                    ),
+                    topTitles: AxisTitles(
+                      sideTitles: SideTitles(showTitles: false),
+                    ),
+                  ),
+                  borderData: FlBorderData(show: true),
+                  minX: adjustedMinX,
+                  maxX: adjustedMaxX,
+                  minY: adjustedMinY,
+                  maxY: adjustedMaxY,
+                  lineTouchData: LineTouchData(enabled: false),
+                  lineBarsData: [
+                    LineChartBarData(
+                      spots: points,
+                      isCurved: true,
+                      color: Colors.blue,
+                      barWidth: 3,
+                      dotData: FlDotData(show: false),
+                      belowBarData: BarAreaData(show: false),
+                    ),
+                  ],
+                ),
+              ),
+            ),
+          ),
+          SizedBox(height: 16),
+          Text(
+            '方程: x = ${xController.text}, y = ${yController.text}',
+            style: TextStyle(fontSize: 14, color: Colors.grey[600]),
+          ),
+          Text(
+            '参数范围: t ∈ [${tMinController.text}, ${tMaxController.text}], 步长: ${stepController.text}',
+            style: TextStyle(fontSize: 14, color: Colors.grey[600]),
+          ),
+        ],
+      ),
+    ),
+  );
+}
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('参数方程绘图', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+      ),
+      body: isLoading
+          ? Center(child: CircularProgressIndicator())
+          : SingleChildScrollView(
+              child: Column(
+                children: [
+                  _buildInputPanel(),
+                  _buildGraph(),
+                  _buildExamples(),
+                ],
+              ),
+            ),
+    );
+  }
+
+  Widget _buildExamples() {
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.all(16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '示例方程',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 12),
+            Wrap(
+              spacing: 8,
+              runSpacing: 8,
+              children: [
+                // 删除了圆的示例
+                _buildExampleChip('椭圆', '2*cos(t)', 'sin(t)', '0', '6.3'),
+                _buildExampleChip('螺旋线', 't*cos(t)', 't*sin(t)', '0', '12.6'),
+                _buildExampleChip('抛物线', 't', 't^2', '-2', '2'),
+                _buildExampleChip('心形线', '2*cos(t)-cos(2*t)', '2*sin(t)-sin(2*t)', '0', '6.3'),
+              ],
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildExampleChip(String title, String xExpr, String yExpr, String tMin, String tMax) {
+    return ActionChip(
+      label: Text(title),
+      onPressed: () {
+        setState(() {
+          xController.text = xExpr;
+          yController.text = yExpr;
+          tMinController.text = tMin;
+          tMaxController.text = tMax;
+          stepController.text = '0.1';
+        });
+        _plotGraph();
+      },
+      backgroundColor: Colors.blue[50],
+    );
+  }
+}

+ 240 - 0
lib/tools/regression.dart

@@ -0,0 +1,240 @@
+// tools/regression.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/math_utils.dart';
+import 'dart:math';
+import 'package:fl_chart/fl_chart.dart';
+
+class RegressionPage extends StatefulWidget {
+  @override
+  _RegressionPageState createState() => _RegressionPageState();
+}
+
+class _RegressionPageState extends State<RegressionPage> {
+  final TextEditingController xController = TextEditingController();
+  final TextEditingController yController = TextEditingController();
+
+  String result = '';
+  List<FlSpot> scatterSpots = [];
+  List<FlSpot> regressionLineSpots = [];
+  bool showRegressionLine = false;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('线性回归')),
+      body: SingleChildScrollView( // 添加可滚动容器
+        padding: EdgeInsets.all(16), // 将padding移到这里
+        child: Column(
+          children: [
+            _buildInputField('x值(用空格分隔)', xController),
+            _buildInputField('y值(用空格分隔)', yController),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算'),
+            ),
+            SizedBox(height: 20),
+            // 回归直线显示选项
+            Row(
+              children: [
+                Checkbox(
+                  value: showRegressionLine,
+                  onChanged: (value) {
+                    setState(() {
+                      showRegressionLine = value!;
+                    });
+                  },
+                ),
+                Text('显示回归直线'),
+              ],
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 14, fontFamily: 'Monospace'),
+                  ),
+                ),
+              ),
+            SizedBox(height: 20),
+            // 散点图
+            if (scatterSpots.isNotEmpty) _buildScatterChart(),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return Padding(
+      padding: EdgeInsets.symmetric(vertical: 8),
+      child: TextField(
+        controller: controller,
+        keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+        decoration: InputDecoration(
+          labelText: label,
+          border: OutlineInputBorder(),
+          hintText: '用空格分隔多个值',
+        ),
+      ),
+    );
+  }
+
+  Widget _buildScatterChart() {
+    return Container(
+      height: 300,
+      padding: EdgeInsets.all(16),
+      child: Card(
+        elevation: 4,
+        child: Padding(
+          padding: EdgeInsets.all(16),
+          child: LineChart(
+            LineChartData(
+              gridData: FlGridData(show: true),
+              titlesData: FlTitlesData(
+                bottomTitles: AxisTitles(
+                  sideTitles: SideTitles(showTitles: true),
+                ),
+                leftTitles: AxisTitles(
+                  sideTitles: SideTitles(showTitles: true),
+                ),
+                topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
+                rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
+              ),
+              borderData: FlBorderData(show: true),
+              minX: scatterSpots.map((spot) => spot.x).reduce(min) - 1,
+              maxX: scatterSpots.map((spot) => spot.x).reduce(max) + 1,
+              minY: scatterSpots.map((spot) => spot.y).reduce(min) - 1,
+              maxY: scatterSpots.map((spot) => spot.y).reduce(max) + 1,
+              lineBarsData: [
+                // 散点图
+                LineChartBarData(
+                  spots: scatterSpots,
+                  isCurved: false,
+                  dotData: FlDotData(
+                    show: true,
+                    getDotPainter: (spot, percent, barData, index) {
+                      return FlDotCirclePainter(
+                        radius: 4,
+                        color: Colors.blue,
+                        strokeWidth: 2,
+                        strokeColor: Colors.blue,
+                      );
+                    },
+                  ),
+                  color: Colors.transparent, // 隐藏连接线
+                  belowBarData: BarAreaData(show: false),
+                ),
+                // 回归直线(可选)
+                if (showRegressionLine && regressionLineSpots.isNotEmpty)
+                  LineChartBarData(
+                    spots: regressionLineSpots,
+                    isCurved: false,
+                    dotData: FlDotData(show: false),
+                    color: Colors.red,
+                    barWidth: 2,
+                    belowBarData: BarAreaData(show: false),
+                  ),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  void _calculate() {
+    try {
+      final xList = xController.text.split(' ').where((s) => s.isNotEmpty).map((s) => double.parse(s)).toList();
+      final yList = yController.text.split(' ').where((s) => s.isNotEmpty).map((s) => double.parse(s)).toList();
+
+      if (xList.isEmpty || yList.isEmpty) {
+        setState(() {
+          result = '错误: 请输入数据';
+        });
+        return;
+      }
+
+      if (xList.length != yList.length) {
+        setState(() {
+          result = '错误: x和y的数据长度不一致';
+        });
+        return;
+      }
+
+      final n = xList.length;
+      double sx = 0, sy = 0, sx2 = 0, sy2 = 0, sxy = 0;
+
+      for (int i = 0; i < n; i++) {
+        sx += xList[i];
+        sy += yList[i];
+        sx2 += xList[i] * xList[i];
+        sy2 += yList[i] * yList[i];
+        sxy += xList[i] * yList[i];
+      }
+
+      final xBar = sx / n;
+      final yBar = sy / n;
+      final sigmaX = sx2 - n * xBar * xBar;
+      final sigmaY = sy2 - n * yBar * yBar;
+      final sigmaXY = sxy - n * xBar * yBar;
+
+      final r = sigmaXY / sqrt(sigmaX * sigmaY);
+      final b = sigmaXY / sigmaX;
+      final a = yBar - b * xBar;
+
+      double se2 = 0;
+      for (int i = 0; i < n; i++) {
+        se2 += pow(yList[i] - b * xList[i] - a, 2);
+      }
+      final R2 = 1 - se2 / sigmaY;
+
+      // 准备散点数据
+      scatterSpots = [];
+      for (int i = 0; i < n; i++) {
+        scatterSpots.add(FlSpot(xList[i], yList[i]));
+      }
+
+      // 准备回归直线数据(两个端点)
+      regressionLineSpots = [];
+      if (xList.isNotEmpty) {
+        double minX = xList.reduce(min);
+        double maxX = xList.reduce(max);
+        regressionLineSpots.add(FlSpot(minX, a + b * minX));
+        regressionLineSpots.add(FlSpot(maxX, a + b * maxX));
+      }
+
+      setState(() {
+        result = '''
+      x_bar = ${xBar.toStringAsFixed(4)}
+      y_bar = ${yBar.toStringAsFixed(4)}
+      Σ(x-x̄)² = ${sigmaX.toStringAsFixed(4)}
+      Σ(y-ȳ)² = ${sigmaY.toStringAsFixed(4)}
+      Σ(x-x̄)(y-ȳ) = ${sigmaXY.toStringAsFixed(4)}
+      相关系数 r = ${r.toStringAsFixed(4)}
+      斜率 b = ${b.toStringAsFixed(4)}
+      截距 a = ${a.toStringAsFixed(4)}
+      残差平方和 = ${se2.toStringAsFixed(4)}
+      决定系数 R² = ${R2.toStringAsFixed(4)}
+      ''';
+      });
+    } catch (e) {
+      setState(() {
+        result = '错误: $e';
+        scatterSpots = [];
+        regressionLineSpots = [];
+      });
+    }
+  }
+
+  @override
+  void dispose() {
+    xController.dispose();
+    yController.dispose();
+    super.dispose();
+  }
+}

+ 692 - 0
lib/tools/statistical_charts.dart

@@ -0,0 +1,692 @@
+// tools/statistical_charts.dart
+import 'package:flutter/material.dart';
+import 'package:fl_chart/fl_chart.dart';
+
+class StatisticalChartsPage extends StatefulWidget {
+  @override
+  _StatisticalChartsPageState createState() => _StatisticalChartsPageState();
+}
+
+class _StatisticalChartsPageState extends State<StatisticalChartsPage> {
+  int _currentChartIndex = 0;
+  final List<String> _chartTypes = ['条形图', '折线图', '饼图', '雷达图'];
+  
+  // 数据控制器
+  final List<TextEditingController> _dataControllers = [];
+  final List<TextEditingController> _labelControllers = [];
+  
+  // 图表数据
+  List<double> _dataValues = [];
+  List<String> _dataLabels = [];
+  
+  @override
+  void initState() {
+    super.initState();
+    // 初始化默认数据
+    _initializeDefaultData();
+  }
+  
+  void _initializeDefaultData() {
+    // 清空现有控制器
+    _dataControllers.clear();
+    _labelControllers.clear();
+    
+    // 添加默认数据
+    for (int i = 0; i < 5; i++) {
+      _dataControllers.add(TextEditingController(text: '${(i + 1) * 10}'));
+      _labelControllers.add(TextEditingController(text: '数据${i + 1}'));
+    }
+    
+    _updateChartData();
+  }
+  
+  void _updateChartData() {
+    setState(() {
+      _dataValues = [];
+      _dataLabels = [];
+      
+      for (int i = 0; i < _dataControllers.length; i++) {
+        if (_dataControllers[i].text.isNotEmpty && 
+            _labelControllers[i].text.isNotEmpty) {
+          try {
+            double value = double.parse(_dataControllers[i].text);
+            _dataValues.add(value);
+            _dataLabels.add(_labelControllers[i].text);
+          } catch (e) {
+            // 忽略无效数据
+          }
+        }
+      }
+    });
+  }
+  
+  void _addDataField() {
+    setState(() {
+      _dataControllers.add(TextEditingController(text: '0'));
+      _labelControllers.add(TextEditingController(text: '新数据'));
+    });
+  }
+  
+  void _removeDataField(int index) {
+    if (_dataControllers.length > 1) {
+      setState(() {
+        _dataControllers.removeAt(index);
+        _labelControllers.removeAt(index);
+        _updateChartData();
+      });
+    }
+  }
+  
+  Widget _buildDataInputPanel() {
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.all(16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '数据输入',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 16),
+            Row(
+              children: [
+                Expanded(
+                  flex: 2,
+                  child: Text(
+                    '标签',
+                    style: TextStyle(fontWeight: FontWeight.bold),
+                    textAlign: TextAlign.center,
+                  ),
+                ),
+                SizedBox(width: 16),
+                Expanded(
+                  flex: 2,
+                  child: Text(
+                    '数值',
+                    style: TextStyle(fontWeight: FontWeight.bold),
+                    textAlign: TextAlign.center,
+                  ),
+                ),
+                SizedBox(width: 16),
+                Expanded(
+                  flex: 1,
+                  child: Text(
+                    '操作',
+                    style: TextStyle(fontWeight: FontWeight.bold),
+                    textAlign: TextAlign.center,
+                  ),
+                ),
+              ],
+            ),
+            SizedBox(height: 8),
+            ...List.generate(_dataControllers.length, (index) {
+              return Padding(
+                padding: EdgeInsets.symmetric(vertical: 8),
+                child: Row(
+                  children: [
+                    Expanded(
+                      flex: 2,
+                      child: TextField(
+                        controller: _labelControllers[index],
+                        decoration: InputDecoration(
+                          hintText: '输入标签',
+                          border: OutlineInputBorder(),
+                          contentPadding: EdgeInsets.symmetric(horizontal: 12),
+                        ),
+                        onChanged: (value) => _updateChartData(),
+                      ),
+                    ),
+                    SizedBox(width: 16),
+                    Expanded(
+                      flex: 2,
+                      child: TextField(
+                        controller: _dataControllers[index],
+                        decoration: InputDecoration(
+                          hintText: '输入数值',
+                          border: OutlineInputBorder(),
+                          contentPadding: EdgeInsets.symmetric(horizontal: 12),
+                        ),
+                        keyboardType: TextInputType.number,
+                        onChanged: (value) => _updateChartData(),
+                      ),
+                    ),
+                    SizedBox(width: 16),
+                    Expanded(
+                      flex: 1,
+                      child: IconButton(
+                        icon: Icon(Icons.delete, color: Colors.red),
+                        onPressed: () => _removeDataField(index),
+                      ),
+                    ),
+                  ],
+                ),
+              );
+            }),
+            SizedBox(height: 16),
+            Row(
+              children: [
+                ElevatedButton(
+                  onPressed: _addDataField,
+                  child: Text('添加数据'),
+                  style: ElevatedButton.styleFrom(
+                    backgroundColor: Colors.blue,
+                    foregroundColor: Colors.white,
+                  ),
+                ),
+                SizedBox(width: 16),
+                ElevatedButton(
+                  onPressed: _initializeDefaultData,
+                  child: Text('重置数据'),
+                  style: ElevatedButton.styleFrom(
+                    backgroundColor: Colors.grey,
+                    foregroundColor: Colors.white,
+                  ),
+                ),
+              ],
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+  
+  Widget _buildChartTypeSelector() {
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.symmetric(horizontal: 16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '图表类型',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 12),
+            Wrap(
+              spacing: 8,
+              runSpacing: 8,
+              children: List.generate(_chartTypes.length, (index) {
+                return ChoiceChip(
+                  label: Text(_chartTypes[index]),
+                  selected: _currentChartIndex == index,
+                  onSelected: (selected) {
+                    setState(() {
+                      _currentChartIndex = index;
+                    });
+                  },
+                  selectedColor: Colors.blue,
+                  labelStyle: TextStyle(
+                    color: _currentChartIndex == index ? Colors.white : Colors.black,
+                  ),
+                );
+              }),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+  
+  Widget _buildChart() {
+    if (_dataValues.isEmpty) {
+      return Container(
+        height: 300,
+        child: Center(
+          child: Text(
+            '请输入有效数据',
+            style: TextStyle(fontSize: 18, color: Colors.grey),
+          ),
+        ),
+      );
+    }
+    
+    switch (_currentChartIndex) {
+      case 0: // 条形图
+        return _buildBarChart();
+      case 1: // 折线图
+        return _buildLineChart();
+      case 2: // 饼图
+        return _buildPieChart();
+      case 3: // 雷达图
+        return _buildRadarChart();
+      default:
+        return _buildBarChart();
+    }
+  }
+  
+  Widget _buildBarChart() {
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.all(16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text(
+              '条形图',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 16),
+            Container(
+              height: 300,
+              child: BarChart(
+                BarChartData(
+                  alignment: BarChartAlignment.spaceAround,
+                  maxY: _dataValues.isEmpty ? 100 : _dataValues.reduce((a, b) => a > b ? a : b) * 1.2,
+                  barTouchData: BarTouchData(
+                    enabled: true,
+                    // 简化 tooltip 配置
+                    touchTooltipData: BarTouchTooltipData(),
+                  ),
+                  titlesData: FlTitlesData(
+                    show: true,
+                    bottomTitles: AxisTitles(
+                      sideTitles: SideTitles(
+                        showTitles: true,
+                        getTitlesWidget: (value, meta) {
+                          int index = value.toInt();
+                          if (index >= 0 && index < _dataLabels.length) {
+                            return Padding(
+                              padding: const EdgeInsets.only(top: 8.0),
+                              child: Text(
+                                _dataLabels[index],
+                                style: TextStyle(fontSize: 10),
+                                maxLines: 2,
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            );
+                          }
+                          return Text('');
+                        },
+                      ),
+                    ),
+                    leftTitles: AxisTitles(
+                      sideTitles: SideTitles(
+                        showTitles: true,
+                        reservedSize: 40,
+                      ),
+                    ),
+                  ),
+                  gridData: FlGridData(show: true),
+                  borderData: FlBorderData(show: true),
+                  barGroups: List.generate(_dataValues.length, (index) {
+                    return BarChartGroupData(
+                      x: index,
+                      barRods: [
+                        BarChartRodData(
+                          toY: _dataValues[index],
+                          color: Colors.primaries[index % Colors.primaries.length],
+                          width: 16,
+                        ),
+                      ],
+                    );
+                  }),
+                ),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+  
+ // 在类顶部添加曲线类型状态变量
+bool _isLineCurved = true;
+
+Widget _buildLineChart() {
+  return Card(
+    elevation: 4,
+    margin: EdgeInsets.all(16),
+    child: Padding(
+      padding: EdgeInsets.all(16),
+      child: Column(
+        children: [
+          Text(
+            '折线图',
+            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+          ),
+          SizedBox(height: 16),
+          
+          // 添加曲线类型选择器
+          Container(
+            padding: EdgeInsets.symmetric(horizontal: 16),
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                Text(
+                  '曲线类型:',
+                  style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
+                ),
+                Row(
+                  children: [
+                    ChoiceChip(
+                      label: Text('直线'),
+                      selected: !_isLineCurved,
+                      onSelected: (selected) {
+                        setState(() {
+                          _isLineCurved = false;
+                        });
+                      },
+                      selectedColor: Colors.blue,
+                      labelStyle: TextStyle(
+                        color: !_isLineCurved ? Colors.white : Colors.black,
+                      ),
+                    ),
+                    SizedBox(width: 8),
+                    ChoiceChip(
+                      label: Text('光滑曲线'),
+                      selected: _isLineCurved,
+                      onSelected: (selected) {
+                        setState(() {
+                          _isLineCurved = true;
+                        });
+                      },
+                      selectedColor: Colors.blue,
+                      labelStyle: TextStyle(
+                        color: _isLineCurved ? Colors.white : Colors.black,
+                      ),
+                    ),
+                  ],
+                ),
+              ],
+            ),
+          ),
+          SizedBox(height: 16),
+          
+          Container(
+            height: 300,
+            child: LineChart(
+              LineChartData(
+                lineTouchData: LineTouchData(enabled: true),
+                gridData: FlGridData(show: true),
+                titlesData: FlTitlesData(
+                  show: true,
+                  bottomTitles: AxisTitles(
+                    sideTitles: SideTitles(
+                      showTitles: true,
+                      getTitlesWidget: (value, meta) {
+                        int index = value.toInt();
+                        if (index >= 0 && index < _dataLabels.length) {
+                          return Padding(
+                            padding: const EdgeInsets.only(top: 8.0),
+                            child: Text(
+                              _dataLabels[index],
+                              style: TextStyle(fontSize: 10),
+                            ),
+                          );
+                        }
+                        return Text('');
+                      },
+                    ),
+                  ),
+                  leftTitles: AxisTitles(
+                    sideTitles: SideTitles(
+                      showTitles: true,
+                      reservedSize: 40,
+                    ),
+                  ),
+                ),
+                borderData: FlBorderData(show: true),
+                minX: 0,
+                maxX: _dataValues.length > 0 ? (_dataValues.length - 1).toDouble() : 1,
+                minY: 0,
+                maxY: _dataValues.isEmpty ? 100 : _dataValues.reduce((a, b) => a > b ? a : b) * 1.2,
+                lineBarsData: [
+                  LineChartBarData(
+                    spots: List.generate(_dataValues.length, (index) {
+                      return FlSpot(index.toDouble(), _dataValues[index]);
+                    }),
+                    isCurved: _isLineCurved,
+                    color: Colors.blue,
+                    barWidth: 4,
+                    belowBarData: BarAreaData(show: false),
+                    dotData: FlDotData(
+                      show: true,
+                      getDotPainter: (spot, percent, barData, index) {
+                        return FlDotCirclePainter(
+                          radius: 4,
+                          color: Colors.blue,
+                          strokeWidth: 2,
+                          strokeColor: Colors.white,
+                        );
+                      },
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ),
+        ],
+      ),
+    ),
+  );
+}
+  
+  Widget _buildPieChart() {
+    double total = _dataValues.fold(0, (a, b) => a + b);
+    
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.all(16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text(
+              '饼图',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 16),
+            Container(
+              height: 300,
+              child: Row(
+                children: [
+                  Expanded(
+                    flex: 2,
+                    child: PieChart(
+                      PieChartData(
+                        sectionsSpace: 4,
+                        centerSpaceRadius: 40,
+                        sections: List.generate(_dataValues.length, (index) {
+                          return PieChartSectionData(
+                            color: Colors.primaries[index % Colors.primaries.length],
+                            value: _dataValues[index],
+                            title: '${(_dataValues[index] / total * 100).toStringAsFixed(1)}%',
+                            radius: 50,
+                            titleStyle: TextStyle(
+                              fontSize: 12,
+                              fontWeight: FontWeight.bold,
+                              color: Colors.white,
+                            ),
+                          );
+                        }),
+                      ),
+                    ),
+                  ),
+                  Expanded(
+                    flex: 1,
+                    child: Column(
+                      mainAxisAlignment: MainAxisAlignment.center,
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: List.generate(_dataValues.length, (index) {
+                        return Padding(
+                          padding: EdgeInsets.symmetric(vertical: 4),
+                          child: Row(
+                            children: [
+                              Container(
+                                width: 12,
+                                height: 12,
+                                color: Colors.primaries[index % Colors.primaries.length],
+                              ),
+                              SizedBox(width: 8),
+                              Flexible(
+                                child: Text(
+                                  _dataLabels[index],
+                                  style: TextStyle(fontSize: 12),
+                                  overflow: TextOverflow.ellipsis,
+                                ),
+                              ),
+                            ],
+                          ),
+                        );
+                      }),
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+  
+  Widget _buildRadarChart() {
+  if (_dataValues.length < 3) {
+    return Card(
+      elevation: 4,
+      margin: EdgeInsets.all(16),
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            Text(
+              '雷达图',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 16),
+            Container(
+              height: 300,
+              child: Center(
+                child: Text(
+                  '雷达图需要至少3个数据点\n请添加更多数据',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(fontSize: 16, color: Colors.grey),
+                ),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  return Card(
+    elevation: 4,
+    margin: EdgeInsets.all(16),
+    child: Padding(
+      padding: EdgeInsets.all(16),
+      child: Column(
+        children: [
+          Text(
+            '雷达图',
+            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+          ),
+          SizedBox(height: 16),
+          Container(
+            height: 300,
+            child: RadarChart(
+              RadarChartData(
+                dataSets: [
+                  RadarDataSet(
+                    dataEntries: List.generate(_dataValues.length, (index) {
+                      return RadarEntry(
+                        value: _dataValues[index],
+                      );
+                    }),
+                    fillColor: Colors.blue.withOpacity(0.3),
+                    borderColor: Colors.blue,
+                    borderWidth: 2,
+                  ),
+                ],
+                radarBackgroundColor: Colors.transparent,
+                radarBorderData: BorderSide(color: Colors.grey, width: 1),
+                tickBorderData: BorderSide(color: Colors.grey, width: 1),
+                gridBorderData: BorderSide(color: Colors.grey, width: 1),
+                ticksTextStyle: TextStyle(color: Colors.black, fontSize: 10),
+                tickCount: 5,
+                getTitle: (index, angle) {
+                  if (index >= 0 && index < _dataLabels.length) {
+                    return RadarChartTitle(
+                      text: _dataLabels[index],
+                      angle: angle,
+                    );
+                  }
+                  return RadarChartTitle(text: '');
+                },
+                titleTextStyle: TextStyle(
+                  color: Colors.black,
+                  fontSize: 12,
+                  fontWeight: FontWeight.bold,
+                ),
+                titlePositionPercentageOffset: 0.15,
+              ),
+              swapAnimationDuration: Duration(milliseconds: 500),
+              swapAnimationCurve: Curves.easeInOut,
+            ),
+          ),
+          SizedBox(height: 16),
+          Container(
+            height: 20,
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: [
+                Container(
+                  width: 12,
+                  height: 12,
+                  decoration: BoxDecoration(
+                    color: Colors.blue.withOpacity(0.3),
+                    border: Border.all(color: Colors.blue, width: 2),
+                  ),
+                ),
+                SizedBox(width: 8),
+                Text(
+                  '数据分布',
+                  style: TextStyle(fontSize: 12, color: Colors.grey),
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    ),
+  );
+}
+  
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('统计图表绘制', style: TextStyle(color: Colors.black)),
+        backgroundColor: Colors.white,
+        elevation: 1,
+      ),
+      body: SingleChildScrollView(
+        child: Column(
+          children: [
+            _buildChartTypeSelector(),
+            _buildDataInputPanel(),
+            _buildChart(),
+          ],
+        ),
+      ),
+    );
+  }
+  
+  @override
+  void dispose() {
+    // 清理控制器
+    for (var controller in _dataControllers) {
+      controller.dispose();
+    }
+    for (var controller in _labelControllers) {
+      controller.dispose();
+    }
+    super.dispose();
+  }
+}

+ 269 - 0
lib/tools/symmetry.dart

@@ -0,0 +1,269 @@
+// tools/symmetry.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/math_utils.dart';
+
+class SymmetryPage extends StatefulWidget {
+  @override
+  _SymmetryPageState createState() => _SymmetryPageState();
+}
+
+class _SymmetryPageState extends State<SymmetryPage> {
+  int _selectedMode = 0; // 0: 点关于直线对称, 1: 直线关于点对称, 2: 直线关于直线对称
+
+  // 点关于直线对称
+  final TextEditingController pointXController = TextEditingController();
+  final TextEditingController pointYController = TextEditingController();
+  final TextEditingController lineAController = TextEditingController();
+  final TextEditingController lineBController = TextEditingController();
+  final TextEditingController lineCController = TextEditingController();
+
+  // 直线关于点对称
+  final TextEditingController line2AController = TextEditingController();
+  final TextEditingController line2BController = TextEditingController();
+  final TextEditingController line2CController = TextEditingController();
+  final TextEditingController point2XController = TextEditingController();
+  final TextEditingController point2YController = TextEditingController();
+
+  // 直线关于直线对称
+  final TextEditingController line3AController = TextEditingController();
+  final TextEditingController line3BController = TextEditingController();
+  final TextEditingController line3CController = TextEditingController();
+  final TextEditingController axisAController = TextEditingController();
+  final TextEditingController axisBController = TextEditingController();
+  final TextEditingController axisCController = TextEditingController();
+
+  String result = '';
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: Text('对称变换')),
+      body: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          children: [
+            DropdownButton<int>(
+              value: _selectedMode,
+              items: [
+                DropdownMenuItem(value: 0, child: Text('点关于直线对称')),
+                DropdownMenuItem(value: 1, child: Text('直线关于点对称')),
+                DropdownMenuItem(value: 2, child: Text('直线关于直线对称')),
+              ],
+              onChanged: (value) {
+                setState(() {
+                  _selectedMode = value!;
+                  result = '';
+                });
+              },
+            ),
+            SizedBox(height: 20),
+            _buildInputs(),
+            SizedBox(height: 20),
+            ElevatedButton(
+              onPressed: _calculate,
+              child: Text('计算'),
+            ),
+            SizedBox(height: 20),
+            if (result.isNotEmpty)
+              Card(
+                child: Padding(
+                  padding: EdgeInsets.all(16),
+                  child: Text(
+                    result,
+                    style: TextStyle(fontSize: 16),
+                  ),
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputs() {
+    switch (_selectedMode) {
+      case 0:
+        return Column(
+          children: [
+            Text('点坐标:'),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('x', pointXController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('y', pointYController)),
+              ],
+            ),
+            Text('直线方程: Ax + By + C = 0'),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('A', lineAController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('B', lineBController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('C', lineCController)),
+              ],
+            ),
+          ],
+        );
+      case 1:
+        return Column(
+          children: [
+            Text('直线方程: Ax + By + C = 0'),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('A', line2AController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('B', line2BController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('C', line2CController)),
+              ],
+            ),
+            Text('点坐标:'),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('x', point2XController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('y', point2YController)),
+              ],
+            ),
+          ],
+        );
+      case 2:
+        return Column(
+          children: [
+            Text('原直线: Ax + By + C = 0'),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('A', line3AController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('B', line3BController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('C', line3CController)),
+              ],
+            ),
+            Text('对称轴: Ax + By + C = 0'),
+            Row(
+              children: [
+                Expanded(child: _buildInputField('A', axisAController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('B', axisBController)),
+                SizedBox(width: 10),
+                Expanded(child: _buildInputField('C', axisCController)),
+              ],
+            ),
+          ],
+        );
+      default:
+        return Container();
+    }
+  }
+
+  Widget _buildInputField(String label, TextEditingController controller) {
+    return TextField(
+      controller: controller,
+      keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
+      decoration: InputDecoration(
+        labelText: label,
+        border: OutlineInputBorder(),
+      ),
+    );
+  }
+
+  void _calculate() {
+    try {
+      switch (_selectedMode) {
+        case 0:
+          _calculatePointLineSymmetry();
+          break;
+        case 1:
+          _calculateLinePointSymmetry();
+          break;
+        case 2:
+          _calculateLineLineSymmetry();
+          break;
+      }
+    } catch (e) {
+      setState(() {
+        result = '错误: $e';
+      });
+    }
+  }
+
+// 在 symmetry.dart 中修复相关代码
+void _calculatePointLineSymmetry() {
+  final x = MathUtils.parseRational(pointXController.text);
+  final y = MathUtils.parseRational(pointYController.text);
+  final A = MathUtils.parseRational(lineAController.text);
+  final B = MathUtils.parseRational(lineBController.text);
+  final C = MathUtils.parseRational(lineCController.text);
+
+  final solutions = MathUtils.solveLinear2(
+    A, B, - (A * x + B * y + MathUtils.two * C),
+    B, -A, B * x - A * y,
+  );
+
+  setState(() {
+    result = '对称点: (${MathUtils.rationalToString(solutions[0])}, '
+                     '${MathUtils.rationalToString(solutions[1])})';
+  });
+}
+
+void _calculateLinePointSymmetry() {
+  final A = MathUtils.parseRational(line2AController.text);
+  final B = MathUtils.parseRational(line2BController.text);
+  final C = MathUtils.parseRational(line2CController.text);
+  final x = MathUtils.parseRational(point2XController.text);
+  final y = MathUtils.parseRational(point2YController.text);
+
+  final newC = -MathUtils.two * A * x - MathUtils.two * B * y - C;
+
+  setState(() {
+    result = '对称直线方程: ${MathUtils.rationalToString(A)}x + '
+                     '${MathUtils.rationalToString(B)}y + '
+                     '${MathUtils.rationalToString(newC)} = 0';
+  });
+}
+
+void _calculateLineLineSymmetry() {
+  final P = MathUtils.parseRational(line3AController.text);
+  final Q = MathUtils.parseRational(line3BController.text);
+  final R = MathUtils.parseRational(line3CController.text);
+  final A = MathUtils.parseRational(axisAController.text);
+  final B = MathUtils.parseRational(axisBController.text);
+  final C = MathUtils.parseRational(axisCController.text);
+
+  final k = MathUtils.two * (P * A + Q * B) / (A * A + B * B);
+
+  final newA = P - A * k;
+  final newB = Q - B * k;
+  final newC = R - C * k;
+
+  setState(() {
+    result = '对称直线方程: ${MathUtils.rationalToString(newA)}x + '
+                     '${MathUtils.rationalToString(newB)}y + '
+                     '${MathUtils.rationalToString(newC)} = 0';
+  });
+}
+// 在 symmetry.dart 中修复 dispose 方法
+@override
+void dispose() {
+  pointXController.dispose();
+  pointYController.dispose();
+  lineAController.dispose();
+  lineBController.dispose();
+  lineCController.dispose();
+  line2AController.dispose();
+  line2BController.dispose();
+  line2CController.dispose();
+  point2XController.dispose();
+  point2YController.dispose();
+  line3AController.dispose();
+  line3BController.dispose();
+  line3CController.dispose();
+  axisAController.dispose();
+  axisBController.dispose();
+  axisCController.dispose();
+  super.dispose();
+}
+}

+ 350 - 0
lib/tools/vector_normal.dart

@@ -0,0 +1,350 @@
+// tools/vector_normal.dart
+import 'package:flutter/material.dart';
+import 'package:rational/rational.dart';
+import '../utils/rationalsqrt2.dart';
+
+class VectorNormalPage extends StatefulWidget {
+  @override
+  _VectorNormalPageState createState() => _VectorNormalPageState();
+}
+
+class _VectorNormalPageState extends State<VectorNormalPage> {
+  final List<TextEditingController> _controllers = List.generate(9, (_) => TextEditingController());
+  final List<FocusNode> _focusNodes = List.generate(9, (_) => FocusNode());
+  TextEditingController? _currentFocusedController;
+  FocusNode? _currentFocusedNode;
+  String _result = '';
+  bool _isCalculating = false;
+
+  @override
+  void initState() {
+    super.initState();
+    // 为每个焦点节点添加监听器
+    for (int i = 0; i < _focusNodes.length; i++) {
+      _focusNodes[i].addListener(() {
+        if (_focusNodes[i].hasFocus) {
+          _setCurrentFocusedController(_controllers[i], _focusNodes[i]);
+        }
+      });
+    }
+  }
+
+  @override
+  void dispose() {
+    for (var controller in _controllers) {
+      controller.dispose();
+    }
+    for (var focusNode in _focusNodes) {
+      focusNode.dispose();
+    }
+    super.dispose();
+  }
+
+  // 设置当前聚焦的控制器和焦点节点
+  void _setCurrentFocusedController(TextEditingController controller, FocusNode focusNode) {
+    setState(() {
+      _currentFocusedController = controller;
+      _currentFocusedNode = focusNode;
+    });
+  }
+
+  // 插入根号到当前光标位置
+  void _insertSqrt() {
+    if (_currentFocusedController == null || _currentFocusedNode == null) return;
+    
+    final controller = _currentFocusedController!;
+    final focusNode = _currentFocusedNode!;
+    final text = controller.text;
+    final selection = controller.selection;
+    final newText = text.substring(0, selection.start) + '√' + text.substring(selection.end);
+    final newCursorPosition = selection.start + 1;
+    
+    controller.value = TextEditingValue(
+      text: newText,
+      selection: TextSelection.collapsed(offset: newCursorPosition),
+    );
+    
+    // 确保焦点保持在当前输入框
+    if (!focusNode.hasFocus) {
+      focusNode.requestFocus();
+    }
+    
+    // 延迟设置光标位置,避免全选
+    Future.delayed(Duration(milliseconds: 10), () {
+      controller.selection = TextSelection.collapsed(offset: newCursorPosition);
+    });
+  }
+
+  // 计算法向量
+  void _calculateNormal() {
+    setState(() {
+      _isCalculating = true;
+      _result = '';
+    });
+
+    try {
+      // 解析三个点的坐标
+      List<Vector3> points = [];
+      for (int i = 0; i < 3; i++) {
+        final x = RationalExtend.parse(_controllers[i * 3].text.isEmpty ? '0' : _controllers[i * 3].text);
+        final y = RationalExtend.parse(_controllers[i * 3 + 1].text.isEmpty ? '0' : _controllers[i * 3 + 1].text);
+        final z = RationalExtend.parse(_controllers[i * 3 + 2].text.isEmpty ? '0' : _controllers[i * 3 + 2].text);
+        points.add(Vector3(x, y, z));
+      }
+
+      // 计算两个向量
+      final vector1 = points[1] - points[0];
+      final vector2 = points[2] - points[0];
+
+      // 计算叉积(法向量)
+      final normal = vector1.cross(vector2);
+
+      setState(() {
+        _result = '法向量:(${normal.x}, ${normal.y}, ${normal.z})';
+      });
+    } catch (e) {
+      setState(() {
+        _result = '计算错误:$e';
+      });
+    } finally {
+      setState(() {
+        _isCalculating = false;
+      });
+    }
+  }
+
+  // 清除所有输入
+  void _clearAll() {
+    for (var controller in _controllers) {
+      controller.clear();
+    }
+    setState(() {
+      _result = '';
+      _currentFocusedController = null;
+      _currentFocusedNode = null;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('三点法向量计算'),
+        backgroundColor: Colors.white,
+        elevation: 1,
+      ),
+      body: SingleChildScrollView(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            _buildInputSection('点A', 0),
+            SizedBox(height: 16),
+            _buildInputSection('点B', 3),
+            SizedBox(height: 16),
+            _buildInputSection('点C', 6),
+            SizedBox(height: 24),
+            _buildControlButtons(),
+            SizedBox(height: 24),
+            _buildResultSection(),
+            SizedBox(height: 24),
+            _buildInstructions(),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInputSection(String title, int startIndex) {
+    return Card(
+      elevation: 2,
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              title,
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 12),
+            Row(
+              children: [
+                _buildCoordinateInput('x', _controllers[startIndex], _focusNodes[startIndex]),
+                SizedBox(width: 12),
+                _buildCoordinateInput('y', _controllers[startIndex + 1], _focusNodes[startIndex + 1]),
+                SizedBox(width: 12),
+                _buildCoordinateInput('z', _controllers[startIndex + 2], _focusNodes[startIndex + 2]),
+              ],
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildCoordinateInput(String label, TextEditingController controller, FocusNode focusNode) {
+    return Expanded(
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Text(
+            label,
+            style: TextStyle(fontSize: 14, color: Colors.grey[600]),
+          ),
+          SizedBox(height: 4),
+          TextField(
+            controller: controller,
+            focusNode: focusNode,
+            decoration: InputDecoration(
+              hintText: '0',
+              border: OutlineInputBorder(),
+              contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+            ),
+            keyboardType: TextInputType.number,
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildControlButtons() {
+    return Row(
+      children: [
+        Expanded(
+          flex: 3,
+          child: ElevatedButton(
+            onPressed: _isCalculating ? null : _calculateNormal,
+            style: ElevatedButton.styleFrom(
+              backgroundColor: Colors.blue,
+              padding: EdgeInsets.symmetric(vertical: 12),
+            ),
+            child: _isCalculating
+            ? SizedBox(
+              width: 20,
+              height: 20,
+              child: CircularProgressIndicator(
+                strokeWidth: 2,
+                valueColor: AlwaysStoppedAnimation(Colors.white),
+              ),
+            )
+            : Text(
+              '计算法向量',
+              style: TextStyle(fontSize: 16, color: Colors.white),
+            ),
+          ),
+        ),
+        SizedBox(width: 12),
+        Expanded(
+          flex: 2,
+          child: OutlinedButton(
+            onPressed: _clearAll,
+            style: OutlinedButton.styleFrom(
+              padding: EdgeInsets.symmetric(vertical: 12),
+            ),
+            child: Text('清除', style: TextStyle(fontSize: 16)),
+          ),
+        ),
+        SizedBox(width: 12),
+        // 使用 GestureDetector 而不是 ElevatedButton 来避免焦点转移
+        GestureDetector(
+          onTap: _insertSqrt,
+          child: Container(
+            width: 60,
+            padding: EdgeInsets.symmetric(vertical: 12),
+            decoration: BoxDecoration(
+              color: Colors.blue,
+              borderRadius: BorderRadius.circular(4),
+            ),
+            child: Text(
+              '√',
+              style: TextStyle(fontSize: 18, color: Colors.white),
+              textAlign: TextAlign.center,
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  Widget _buildResultSection() {
+    if (_result.isEmpty) return SizedBox();
+
+    return Card(
+      color: Colors.grey[50],
+      elevation: 2,
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '计算结果',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 12),
+            Text(
+              _result,
+              style: TextStyle(fontSize: 16, color: Colors.blue[700]),
+            ),
+            SizedBox(height: 8),
+            Text(
+              '说明:法向量是通过向量AB和向量AC的叉积计算得到',
+              style: TextStyle(fontSize: 12, color: Colors.grey[600]),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildInstructions() {
+    return Card(
+      color: Colors.grey[50],
+      elevation: 2,
+      child: Padding(
+        padding: EdgeInsets.all(16),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              '使用说明',
+              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+            ),
+            SizedBox(height: 12),
+            Text(
+              '√2/2请输入为1/2√2\n'
+              '如果已知两向量坐标,将一个点设为原点即可',
+              style: TextStyle(fontSize: 14, color: Colors.grey[700]),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+// 三维向量类,使用RationalExtend进行计算
+class Vector3 {
+  final RationalExtend x, y, z;
+
+  Vector3(this.x, this.y, this.z);
+
+  // 向量减法
+  Vector3 operator -(Vector3 other) {
+    return Vector3(x - other.x, y - other.y, z - other.z);
+  }
+
+  // 向量叉积
+  Vector3 cross(Vector3 other) {
+    final newX = y * other.z - z * other.y;
+    final newY = z * other.x - x * other.z;
+    final newZ = x * other.y - y * other.x;
+    return Vector3(newX, newY, newZ);
+  }
+
+  @override
+  String toString() {
+    return '($x, $y, $z)';
+  }
+}

+ 128 - 0
lib/utils/math_utils.dart

@@ -0,0 +1,128 @@
+// utils/math_utils.dart
+import 'package:rational/rational.dart';
+
+class MathUtils {
+  // 添加常量
+  static final Rational zero = Rational.zero;
+  static final Rational one = Rational.one;
+  static final Rational two = Rational.fromInt(2);
+  static final Rational half = Rational.fromInt(1) / Rational.fromInt(2);
+
+  static Rational parseRational(String input) {
+    try {
+      // 去除空格
+      input = input.trim();
+      
+      if (input.contains('/')) {
+        List<String> parts = input.split('/');
+        if (parts.length == 2) {
+          String numerator = parts[0].trim();
+          String denominator = parts[1].trim();
+          
+          // 修复:直接使用 Rational 构造函数而不是 parse
+          BigInt num = BigInt.parse(numerator);
+          BigInt den = BigInt.parse(denominator);
+          
+          if (den == BigInt.zero) {
+            throw FormatException('分母不能为零');
+          }
+          
+          return Rational(num, den);
+        }
+      }
+      
+      // 处理小数
+      if (input.contains('.')) {
+        return _decimalToRational(input);
+      }
+      
+      // 处理整数
+      return Rational.parse(input);
+    } catch (e) {
+      throw FormatException('无效的分数格式: $input');
+    }
+  }
+
+  static Rational _decimalToRational(String decimalStr) {
+    // 更精确的小数转分数实现
+    if (!decimalStr.contains('.')) {
+      return Rational.parse(decimalStr);
+    }
+    
+    List<String> parts = decimalStr.split('.');
+    String integerPart = parts[0];
+    String decimalPart = parts[1];
+    
+    // 处理负号
+    bool isNegative = integerPart.startsWith('-');
+    if (isNegative) {
+      integerPart = integerPart.substring(1);
+    }
+    
+    // 如果整数部分是空的,设为0
+    if (integerPart.isEmpty) {
+      integerPart = '0';
+    }
+    
+    // 计算分母:10的decimalPart长度次方
+    BigInt denominator = BigInt.from(10).pow(decimalPart.length);
+    // 计算分子:整数部分 * 分母 + 小数部分
+    BigInt numerator = 
+        BigInt.parse(integerPart) * denominator + BigInt.parse(decimalPart);
+    
+    if (isNegative) {
+      numerator = -numerator;
+    }
+    
+    return Rational(numerator, denominator);
+  }
+
+  static String rationalToString(Rational rational) {
+    // 简化分数显示
+    if (rational.denominator == BigInt.one) {
+      return rational.numerator.toString();
+    }
+    
+    // 尝试约分
+    Rational simplified = rational;
+    
+    return '${simplified.numerator}/${simplified.denominator}';
+  }
+
+  static Rational det2(Rational a, Rational b, Rational c, Rational d) {
+    return a * d - b * c;
+  }
+
+  static List<Rational> solveLinear2(
+    Rational a1, Rational b1, Rational c1,
+    Rational a2, Rational b2, Rational c2,
+  ) {
+    final det = det2(a1, b1, a2, b2);
+    if (det == zero) {
+      throw Exception('方程组无解或有无穷多解');
+    }
+    final x = det2(c1, b1, c2, b2) / det;
+    final y = det2(a1, c1, a2, c2) / det;
+    return [x, y];
+  }
+
+  // 创建 Rational 从 BigInt
+  static Rational fromBigInt(BigInt value) {
+    return Rational(value);
+  }
+
+  // 创建 Rational 从 int
+  static Rational fromInt(int value) {
+    return Rational.fromInt(value);
+  }
+  
+  // 计算最大公约数
+  static BigInt _gcd(BigInt a, BigInt b) {
+    while (b != BigInt.zero) {
+      final temp = b;
+      b = a % b;
+      a = temp;
+    }
+    return a;
+  }
+}

+ 769 - 0
lib/utils/rationalsqrt2.dart

@@ -0,0 +1,769 @@
+// utils/rationalsqrt2.dart
+
+/* 此文件实现了有理数与二次根式形成的数域
+ * 需要在pubspec.yaml中添加依赖rational: ^2.2.3
+ * 使用方法请看最后的测试用例
+ * Ksqrt类表示有理数乘以根号整数
+ * RationalExtend类表示多个Ksqrt类根式相加
+ * Author:nonvioce
+ * Time: 2025.10.26
+*/
+import 'package:rational/rational.dart';
+import 'dart:math';
+
+class Ksqrt {
+  Rational k; // 有理数系数
+  int q;      // 底数(整数)
+
+  Ksqrt(this.k, this.q) {
+    if (q < 0) throw ArgumentError("底数q不能为负数");
+  }
+
+  // 从整数创建(系数为整数,底数为1)
+  factory Ksqrt.fromInt(int value) {
+    return Ksqrt(Rational.fromInt(value), 1);
+  }
+
+  // 从Rational创建(系数为有理数,底数为1)
+  factory Ksqrt.fromRational(Rational rational) {
+    return Ksqrt(rational, 1);
+  }
+
+  // 增强的字符串解析 - 增加小数支持
+  factory Ksqrt.parse(String str) {
+    str = str.trim();
+
+    // 处理纯数字(整数或小数)
+    if (RegExp(r'^-?\d+(\.\d+)?$').hasMatch(str)) {
+      return Ksqrt.fromRational(_parseRational(str));
+    }
+
+    // 处理分数(不包含根号)
+    if (str.contains('/') && !str.contains('√')) {
+      try {
+        var parts = str.split('/');
+        if (parts.length != 2) throw FormatException("无效的分数格式: $str");
+
+        var numerator = _parseRational(parts[0]);
+        var denominator = _parseRational(parts[1]);
+
+        if (denominator == Rational.zero) throw FormatException("分母不能为零: $str");
+
+        return Ksqrt.fromRational(numerator / denominator);
+      } catch (e) {
+        throw FormatException("无效的分数格式: $str, 错误: $e");
+      }
+    }
+
+    // 处理根式
+    if (str.contains('√')) {
+      var sqrtIndex = str.indexOf('√');
+      var coefficientStr = str.substring(0, sqrtIndex);
+      var radicandStr = str.substring(sqrtIndex + 1);
+
+      // 处理括号内的底数
+      if (radicandStr.startsWith('(') && radicandStr.endsWith(')')) {
+        radicandStr = radicandStr.substring(1, radicandStr.length - 1);
+      }
+
+      Rational k;
+
+      // 解析系数
+      if (coefficientStr.isEmpty || coefficientStr == "+") {
+        k = Rational.one;
+      } else if (coefficientStr == "-") {
+        k = -Rational.one;
+      } else {
+        coefficientStr = coefficientStr.replaceAll('*', '');
+        try {
+          if (coefficientStr.contains('/')) {
+            var coeffParts = coefficientStr.split('/');
+            if (coeffParts.length != 2) throw FormatException("无效的系数格式: $coefficientStr");
+
+            var num = _parseRational(coeffParts[0]);
+            var den = _parseRational(coeffParts[1]);
+            if (den == Rational.zero) throw FormatException("分母不能为零: $coefficientStr");
+
+            k = num / den;
+          } else {
+            k = _parseRational(coefficientStr);
+          }
+        } catch (e) {
+          throw FormatException("无效的系数格式: $coefficientStr");
+        }
+      }
+
+      // 解析底数
+      int q;
+      if (radicandStr.contains('/')) {
+        throw FormatException("分数底数暂不支持: $str");
+      } else {
+        try {
+          // 底数必须是整数,所以使用int.parse而不是_parseRational
+          q = int.parse(radicandStr);
+        } catch (e) {
+          throw FormatException("无效的底数格式: $radicandStr");
+        }
+      }
+
+      return Ksqrt(k, q)..simplify();
+    }
+
+    // 尝试解析为有理数(包括小数)
+    try {
+      return Ksqrt.fromRational(_parseRational(str));
+    } catch (e) {
+      throw FormatException("无法解析的字符串: $str");
+    }
+  }
+
+  // 小数转有理数的辅助函数
+  static Rational _parseRational(String input) {
+    try {
+      input = input.trim();
+
+      if (input.contains('/')) {
+        List<String> parts = input.split('/');
+        if (parts.length == 2) {
+          String numerator = parts[0].trim();
+          String denominator = parts[1].trim();
+
+          // 修复:直接使用 Rational 构造函数而不是 parse
+          BigInt num = BigInt.parse(numerator);
+          BigInt den = BigInt.parse(denominator);
+
+          if (den == BigInt.zero) {
+            throw FormatException('分母不能为零');
+          }
+
+          return Rational(num, den);
+        }
+      }
+
+      // 处理小数
+      if (input.contains('.')) {
+        return _decimalToRational(input);
+      }
+
+      // 处理整数
+      return Rational.parse(input);
+    } catch (e) {
+      throw FormatException('无效的分数格式: $input');
+    }
+  }
+
+  // 小数转有理数的实现
+  static Rational _decimalToRational(String decimalStr) {
+    if (!decimalStr.contains('.')) {
+      return Rational.parse(decimalStr);
+    }
+
+    List<String> parts = decimalStr.split('.');
+    String integerPart = parts[0];
+    String decimalPart = parts[1];
+
+    // 处理负号
+    bool isNegative = integerPart.startsWith('-');
+    if (isNegative) {
+      integerPart = integerPart.substring(1);
+    }
+
+    // 如果整数部分是空的,设为0
+    if (integerPart.isEmpty) {
+      integerPart = '0';
+    }
+
+    // 计算分母:10的decimalPart长度次方
+    BigInt denominator = BigInt.from(10).pow(decimalPart.length);
+    // 计算分子:整数部分 * 分母 + 小数部分
+    BigInt numerator =
+    BigInt.parse(integerPart) * denominator + BigInt.parse(decimalPart);
+
+    if (isNegative) {
+      numerator = -numerator;
+    }
+
+    return Rational(numerator, denominator);
+  }
+
+  // 取反运算
+  Ksqrt operator -() {
+    return Ksqrt(-k, q);
+  }
+
+  // 修复的化简方法
+  void simplify() {
+    if (k == Rational.zero) {
+      q = 1;
+      return;
+    }
+
+    if (q == 0 || q == 1) return;
+
+    // 提取平方因子
+    int squareFactor = 1;
+    int temp = q;
+
+    for (int i = 2; i * i <= temp; i++) {
+      while (temp % (i * i) == 0) {
+        squareFactor *= i;
+        temp ~/= (i * i);
+      }
+    }
+
+    // 更新系数和底数
+    if (squareFactor > 1) {
+      k *= Rational.fromInt(squareFactor);
+      q = temp;
+    }
+  }
+
+  // 判断两个Ksqrt是否同类项
+  bool isLikeTerm(Ksqrt other) {
+    Ksqrt thisCopy = Ksqrt(k, q);
+    Ksqrt otherCopy = Ksqrt(other.k, other.q);
+    thisCopy.simplify();
+    otherCopy.simplify();
+    return thisCopy.q == otherCopy.q;
+  }
+
+  // 加法运算
+  Ksqrt operator +(Ksqrt other) {
+    if (!isLikeTerm(other)) {
+      throw ArgumentError("底数不同无法相加: ${this.q} 和 ${other.q}");
+    }
+
+    Ksqrt thisCopy = Ksqrt(k, q);
+    Ksqrt otherCopy = Ksqrt(other.k, other.q);
+    thisCopy.simplify();
+    otherCopy.simplify();
+
+    return Ksqrt(thisCopy.k + otherCopy.k, thisCopy.q);
+  }
+
+  // 减法运算
+  Ksqrt operator -(Ksqrt other) {
+    if (!isLikeTerm(other)) {
+      throw ArgumentError("底数不同无法相减: ${this.q} 和 ${other.q}");
+    }
+
+    Ksqrt thisCopy = Ksqrt(k, q);
+    Ksqrt otherCopy = Ksqrt(other.k, other.q);
+    thisCopy.simplify();
+    otherCopy.simplify();
+
+    return Ksqrt(thisCopy.k - otherCopy.k, thisCopy.q);
+  }
+
+  // 乘法运算
+  Ksqrt operator *(Ksqrt other) {
+    Rational newK = k * other.k;
+    int newQ = q * other.q;
+
+    Ksqrt result = Ksqrt(newK, newQ);
+    result.simplify();
+    return result;
+  }
+
+  // 修复的除法运算
+  Ksqrt operator /(Ksqrt other) {
+    if (other.k == Rational.zero) {
+      throw ArgumentError("除数不能为零");
+    }
+
+    // 分子分母同乘以分母的根号部分进行有理化
+    Rational newK = k * other.k;
+    int newQ = q * other.q;
+
+    // 除以分母系数的平方(因为分母是k√q,有理化后分母变为k²q)
+    Rational denominator = other.k * other.k * Rational.fromInt(other.q);
+
+    Ksqrt result = Ksqrt(newK / denominator, newQ);
+    result.simplify();
+    return result;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    if (other is! Ksqrt) return false;
+
+    Ksqrt thisSimplified = Ksqrt(k, q)..simplify();
+    Ksqrt otherSimplified = Ksqrt(other.k, other.q)..simplify();
+
+    return thisSimplified.k == otherSimplified.k &&
+    thisSimplified.q == otherSimplified.q;
+  }
+
+  @override
+  int get hashCode {
+    Ksqrt simplified = Ksqrt(k, q)..simplify();
+    return Object.hash(simplified.k, simplified.q);
+  }
+
+  // 计算表达式的近似浮点数值
+  double toDouble() {
+    if (k == Rational.zero) return 0.0;
+
+    double sqrtQ = sqrt(q.toDouble());
+    double kValue = k.toDouble();
+
+    return kValue * sqrtQ;
+  }
+
+  @override
+  String toString() {
+    if (k == Rational.zero) return '0';
+    if (q == 1) return k.toString();
+
+    String kStr;
+    if (k == Rational.one) {
+      kStr = '';
+    } else if (k == -Rational.one) {
+      kStr = '-';
+    } else {
+      if (k.isInteger) {
+        kStr = k.toDouble().toInt().toString();
+      } else {
+        kStr = '($k)';
+      }
+      kStr += '*';
+    }
+
+    return '${kStr}√$q';
+  }
+}
+
+class RationalExtend {
+  List<Ksqrt> terms;
+
+  RationalExtend(this.terms);
+
+  factory RationalExtend.fromInt(int value) {
+    return RationalExtend([Ksqrt.fromInt(value)]);
+  }
+
+  factory RationalExtend.fromRational(Rational rational) {
+    return RationalExtend([Ksqrt.fromRational(rational)]);
+  }
+
+  // 改进的字符串解析 - 现在支持小数
+  factory RationalExtend.parse(String str) {
+    str = str.replaceAll(' ', '');
+
+    if (str.isEmpty) return RationalExtend([]);
+
+    // 处理单个项的情况
+    if (!str.contains('+') && !str.contains('-')) {
+      return RationalExtend([Ksqrt.parse(str)]);
+    }
+
+    // 处理以负号开头的情况
+    if (str.startsWith('-')) {
+      var rest = str.substring(1);
+      if (!rest.contains('+') && !rest.contains('-')) {
+        return RationalExtend([Ksqrt.parse(str)]);
+      }
+    }
+
+    List<Ksqrt> terms = [];
+    List<String> termStrs = [];
+
+    // 改进的分割逻辑:正确处理符号
+    int start = 0;
+    for (int i = 1; i < str.length; i++) {
+      if ((str[i] == '+' || str[i] == '-') &&
+        str[i-1] != '*' && str[i-1] != '/' && str[i-1] != '^') {
+        termStrs.add(str.substring(start, i));
+      start = i;
+        }
+    }
+    termStrs.add(str.substring(start));
+
+    // 解析各项
+    for (var termStr in termStrs) {
+      if (termStr.isEmpty) continue;
+      if (termStr == '+') continue;
+
+      try {
+        terms.add(Ksqrt.parse(termStr));
+      } catch (e) {
+        throw FormatException("无法解析项: $termStr, 错误: $e");
+      }
+    }
+
+    return RationalExtend(terms)..simplify();
+  }
+
+  void addTerm(Ksqrt term) {
+    terms.add(term);
+  }
+
+  // 化简方法:合并同类项
+  void simplify() {
+    if (terms.isEmpty) return;
+
+    // 首先对每个项进行化简
+    for (var term in terms) {
+      term.simplify();
+    }
+
+    // 合并同类项
+    Map<int, Rational> termMap = {};
+
+    for (var term in terms) {
+      if (termMap.containsKey(term.q)) {
+        termMap[term.q] = termMap[term.q]! + term.k;
+      } else {
+        termMap[term.q] = term.k;
+      }
+    }
+
+    // 重建terms列表
+    terms.clear();
+    termMap.forEach((q, k) {
+      if (k != Rational.zero) {
+        terms.add(Ksqrt(k, q));
+      }
+    });
+
+    // 按底数排序
+    terms.sort((a, b) => a.q.compareTo(b.q));
+
+    if (terms.isEmpty) {
+      terms.add(Ksqrt.fromInt(0));
+    }
+  }
+
+  // 获取共轭表达式
+  RationalExtend get conjugate {
+    if (terms.length == 1) {
+      return RationalExtend([terms[0]]);
+    } else if (terms.length == 2) {
+      return RationalExtend([
+        terms[0],
+        Ksqrt(-terms[1].k, terms[1].q)
+      ]);
+    } else {
+      List<Ksqrt> conjugateTerms = [terms[0]];
+      for (int i = 1; i < terms.length; i++) {
+        conjugateTerms.add(Ksqrt(-terms[i].k, terms[i].q));
+      }
+      return RationalExtend(conjugateTerms);
+    }
+  }
+
+  // 检查是否是有理数
+  bool get isRational {
+    for (var term in terms) {
+      if (term.q != 1) return false;
+    }
+    return true;
+  }
+
+  // 获取有理数部分的值
+  Rational get rationalValue {
+    if (!isRational) {
+      throw StateError("表达式包含根号,不是纯有理数");
+    }
+
+    Rational sum = Rational.zero;
+    for (var term in terms) {
+      sum += term.k;
+    }
+    return sum;
+  }
+
+  // 加法运算
+  RationalExtend operator +(RationalExtend other) {
+    List<Ksqrt> newTerms = List.from(terms);
+    newTerms.addAll(other.terms);
+
+    RationalExtend result = RationalExtend(newTerms);
+    result.simplify();
+    return result;
+  }
+
+  // 减法运算
+  RationalExtend operator -(RationalExtend other) {
+    List<Ksqrt> newTerms = List.from(terms);
+    for (var term in other.terms) {
+      newTerms.add(Ksqrt(-term.k, term.q));
+    }
+
+    RationalExtend result = RationalExtend(newTerms);
+    result.simplify();
+    return result;
+  }
+
+  // 乘法运算
+  RationalExtend operator *(RationalExtend other) {
+    List<Ksqrt> productTerms = [];
+
+    for (var term1 in terms) {
+      for (var term2 in other.terms) {
+        productTerms.add(term1 * term2);
+      }
+    }
+
+    RationalExtend result = RationalExtend(productTerms);
+    result.simplify();
+    return result;
+  }
+
+  // 修复的除法运算
+  RationalExtend operator /(RationalExtend other) {
+    if (other.terms.isEmpty) {
+      throw ArgumentError("除数不能为空");
+    }
+
+    // 特殊情况:除数是单项式
+    if (other.terms.length == 1) {
+      List<Ksqrt> newTerms = [];
+      for (var term in terms) {
+        newTerms.add(term / other.terms[0]);
+      }
+      return RationalExtend(newTerms)..simplify();
+    }
+
+    // 一般情况:使用分母有理化
+    RationalExtend conjugate = other.conjugate;
+    RationalExtend numerator = this * conjugate;
+    RationalExtend denominator = other * conjugate;
+
+    if (!denominator.isRational) {
+      throw StateError("分母有理化失败,结果仍包含根号");
+    }
+
+    Rational denomValue = denominator.rationalValue;
+
+    if (denomValue == Rational.zero) {
+      throw ArgumentError("分母有理化后为0,无法进行除法");
+    }
+
+    // 将分子中的每一项除以分母的有理数值
+    List<Ksqrt> resultTerms = [];
+    for (var term in numerator.terms) {
+      resultTerms.add(Ksqrt(term.k / denomValue, term.q));
+    }
+
+    return RationalExtend(resultTerms)..simplify();
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+    if (other is! RationalExtend) return false;
+
+    RationalExtend thisSimplified = RationalExtend(List.from(terms))..simplify();
+    RationalExtend otherSimplified = RationalExtend(List.from(other.terms))..simplify();
+
+    if (thisSimplified.terms.length != otherSimplified.terms.length) return false;
+
+    for (int i = 0; i < thisSimplified.terms.length; i++) {
+      if (thisSimplified.terms[i] != otherSimplified.terms[i]) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  @override
+  int get hashCode {
+    RationalExtend simplified = RationalExtend(List.from(terms))..simplify();
+    return simplified.terms.fold(0, (hash, term) => hash ^ term.hashCode);
+  }
+
+  // 计算表达式的近似浮点数值
+  double toDouble() {
+    double sum = 0.0;
+    for (var term in terms) {
+      sum += term.toDouble();
+    }
+    return sum;
+  }
+
+  @override
+  String toString() {
+    if (terms.isEmpty) return "0";
+
+    RationalExtend simplified = RationalExtend(List.from(terms))..simplify();
+
+    if (simplified.terms.isEmpty) return "0";
+
+    List<String> termStrings = [];
+    for (var term in simplified.terms) {
+      String termStr = term.toString();
+      if (termStr.startsWith('-')) {
+        termStrings.add(termStr);
+      } else if (termStrings.isNotEmpty) {
+        termStrings.add('+$termStr');
+      } else {
+        termStrings.add(termStr);
+      }
+    }
+
+    String result = termStrings.join('');
+    if (result.startsWith('+')) {
+      result = result.substring(1);
+    }
+
+    return result;
+  }
+}
+
+extension RationalExtendMath on RationalExtend {
+  RationalExtend square() {
+    return this * this;
+  }
+
+  RationalExtend abs() {
+    double value = this.toDouble();
+    if (value < 0) {
+      return RationalExtend(terms.map((term) => Ksqrt(-term.k, term.q)).toList());
+    }
+    return this;
+  }
+
+  bool get isZero {
+    RationalExtend simplified = RationalExtend(List.from(terms))..simplify();
+    return simplified.terms.length == 1 &&
+    simplified.terms[0].k == Rational.zero;
+  }
+
+  Rational get rationalPart {
+    Rational sum = Rational.zero;
+    for (var term in terms) {
+      if (term.q == 1) {
+        sum += term.k;
+      }
+    }
+    return sum;
+  }
+
+  RationalExtend get irrationalPart {
+    List<Ksqrt> irrationalTerms = [];
+    for (var term in terms) {
+      if (term.q != 1) {
+        irrationalTerms.add(term);
+      }
+    }
+    return RationalExtend(irrationalTerms)..simplify();
+  }
+}
+/*测试用例
+// 测试函数 - 增加小数测试
+void main() {
+  print('=== Ksqrt 类测试 ===');
+
+  // 基本创建和化简
+  var sqrt8 = Ksqrt(Rational.one, 8);
+  sqrt8.simplify();
+  print('√8 = $sqrt8'); // 应该输出 2√2
+
+  // 算术运算
+  var a = Ksqrt.parse('√2');
+  var b = Ksqrt.parse('3√2');
+  var sum = a + b;
+  print('√2 + 3√2 = $sum'); // 应该输出 4√2
+
+  var product = a * b;
+  print('√2 * 3√2 = $product'); // 应该输出 6
+
+  // 除法测试 - 修复后
+  var c = Ksqrt.parse('√6');
+  var d = Ksqrt.parse('√3');
+  var quotient = c / d;
+  print('√6 / √3 = $quotient'); // 应该输出 √2
+
+  print('\n=== RationalExtend 类测试 ===');
+
+  // 多项式创建和运算
+  var expr1 = RationalExtend.parse('√2 + 2√3');
+  var expr2 = RationalExtend.parse('√2 - √3');
+  print('表达式1: $expr1');
+  print('表达式2: $expr2');
+
+  var polySum = expr1 + expr2;
+  print('相加: $polySum'); // 应该输出 2√2 + √3
+
+  var polyProduct = expr1 * expr2;
+  print('相乘: $polyProduct'); // 应该输出 2 + √6 - 2√3 - 2*3 = -4 + √6 - 2√3
+
+  // 除法测试(分母有理化)- 修复后
+  try {
+    var polyQuotient = expr1 / expr2;
+    print('相除: $polyQuotient'); // 应该输出有理化后的结果
+  } catch (e) {
+    print('除法错误: $e');
+  }
+
+  // 复杂表达式测试
+  print('\n=== 复杂表达式测试 ===');
+  try {
+    var complexExpr = RationalExtend.parse('2/3');
+    print('分数解析: $complexExpr');
+
+    var complexExpr2 = RationalExtend.parse('2/3+√2');
+    print('分数+根号解析: $complexExpr2');
+
+    var complexExpr3 = RationalExtend.parse('1+√2');
+    var mul = complexExpr2 * complexExpr3;
+    print('复杂表达式1: $complexExpr2');
+    print('复杂表达式2: $complexExpr3');
+    print('乘积: $mul');
+    print('有理数部分: ${complexExpr2.rationalPart}');
+    print('无理数部分: ${complexExpr2.irrationalPart}');
+    print('近似值: ${complexExpr2.toDouble()}');
+  } catch (e) {
+    print('解析错误: $e');
+  }
+
+  // 更多测试
+  print('\n=== 更多测试 ===');
+  try {
+    var test1 = Ksqrt.parse('1/2');
+    print('分数 1/2: $test1');
+
+    var test2 = Ksqrt.parse('3/4*√2');
+    print('分数系数 3/4*√2: $test2');
+
+    var test3 = RationalExtend.parse('1/2 + 1/3*√2');
+    print('混合表达式: $test3');
+  } catch (e) {
+    print('测试错误: $e');
+  }
+
+  // 小数测试
+  print('\n=== 小数支持测试 ===');
+  try {
+    var decimal1 = Ksqrt.parse('0.5');
+    print('小数 0.5: $decimal1');
+
+    var decimal2 = Ksqrt.parse('0.25√2');
+    print('小数系数 0.25√2: $decimal2');
+
+    var decimal3 = Ksqrt.parse('1.5√3');
+    print('小数系数 1.5√3: $decimal3');
+
+    var decimal4 = RationalExtend.parse('0.5 + 3/2√2');
+    print('混合小数表达式: $decimal4');
+
+    var decimal5 = RationalExtend.parse('1/3 + 0.666√2');
+    print('混合小数表达式: $decimal5');
+
+    // 小数运算测试
+    var decimalExpr1 = RationalExtend.parse('0.5√2');
+    var decimalExpr2 = RationalExtend.parse('0.25√2');
+    var decimalSum = decimalExpr1 + decimalExpr2;
+    print('0.5√2 + 0.25√2 = $decimalSum'); // 应该输出 0.75√2
+
+    var decimalProduct = decimalExpr1 * decimalExpr2;
+    print('0.5√2 * 0.25√2 = $decimalProduct'); // 应该输出 0.25
+
+  } catch (e) {
+    print('小数测试错误: $e');
+  }
+}
+*/

+ 1 - 0
linux/.gitignore

@@ -0,0 +1 @@
+flutter/ephemeral

+ 128 - 0
linux/CMakeLists.txt

@@ -0,0 +1,128 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.13)
+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 "mathtool2")
+# The unique GTK application identifier for this application. See:
+# https://wiki.gnome.org/HowDoI/ChooseApplicationID
+set(APPLICATION_ID "com.example.mathtool2")
+
+# 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 "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>: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)
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+# 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)
+
+# Copy the native assets provided by the build.dart from all packages.
+set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
+install(DIRECTORY "${NATIVE_ASSETS_DIR}"
+   DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+   COMPONENT Runtime)
+
+# 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()

+ 88 - 0
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}
+)

+ 19 - 0
linux/flutter/generated_plugin_registrant.cc

@@ -0,0 +1,19 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+#include <flutter_js/flutter_js_plugin.h>
+#include <url_launcher_linux/url_launcher_plugin.h>
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) flutter_js_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterJsPlugin");
+  flutter_js_plugin_register_with_registrar(flutter_js_registrar);
+  g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+  url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
+}

+ 15 - 0
linux/flutter/generated_plugin_registrant.h

@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_

+ 25 - 0
linux/flutter/generated_plugins.cmake

@@ -0,0 +1,25 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+  flutter_js
+  url_launcher_linux
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)

+ 26 - 0
linux/runner/CMakeLists.txt

@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.13)
+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}
+  "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 preprocessor definitions for the application ID.
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")

+ 6 - 0
linux/runner/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);
+}

+ 144 - 0
linux/runner/my_application.cc

@@ -0,0 +1,144 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#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)
+
+// Called when first Flutter frame received.
+static void first_frame_cb(MyApplication* self, FlView *view)
+{
+  gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
+}
+
+// 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, "某人的小工具");
+    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, "某人的小工具");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+
+  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);
+  GdkRGBA background_color;
+  // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent.
+  gdk_rgba_parse(&background_color, "#000000");
+  fl_view_set_background_color(view, &background_color);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  // Show the window when Flutter renders.
+  // Requires the view to be realized so we can start rendering.
+  g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self);
+  gtk_widget_realize(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 GApplication::startup.
+static void my_application_startup(GApplication* application) {
+  //MyApplication* self = MY_APPLICATION(object);
+
+  // Perform any actions required at application startup.
+
+  G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
+}
+
+// Implements GApplication::shutdown.
+static void my_application_shutdown(GApplication* application) {
+  //MyApplication* self = MY_APPLICATION(object);
+
+  // Perform any actions required at application shutdown.
+
+  G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
+}
+
+// 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_APPLICATION_CLASS(klass)->startup = my_application_startup;
+  G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  // Set the program name to the application ID, which helps various systems
+  // like GTK and desktop environments map this running application to its
+  // corresponding .desktop file. This ensures better integration by allowing
+  // the application to be recognized beyond its binary name.
+  g_set_prgname(APPLICATION_ID);
+
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID,
+                                     "flags", G_APPLICATION_NON_UNIQUE,
+                                     nullptr));
+}

+ 18 - 0
linux/runner/my_application.h

@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+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_

+ 7 - 0
macos/.gitignore

@@ -0,0 +1,7 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/dgph
+**/xcuserdata/

+ 1 - 0
macos/Flutter/Flutter-Debug.xcconfig

@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"

+ 1 - 0
macos/Flutter/Flutter-Release.xcconfig

@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"

+ 18 - 0
macos/Flutter/GeneratedPluginRegistrant.swift

@@ -0,0 +1,18 @@
+//
+//  Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+import file_picker
+import flutter_js
+import path_provider_foundation
+import url_launcher_macos
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
+  FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin"))
+  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+  UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
+}

+ 705 - 0
macos/Runner.xcodeproj/project.pbxproj

@@ -0,0 +1,705 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 54;
+	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 */
+		331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
+		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 */
+		331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 33CC10EC2044A3C60003C045;
+			remoteInfo = Runner;
+		};
+		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 */
+		331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
+		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
+		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
+		33CC10ED2044A3C60003C045 /* mathtool2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mathtool2.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
+		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
+		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
+		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
+		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
+		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
+		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
+		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
+		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		331C80D2294CF70F00263BE5 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		33CC10EA2044A3C60003C045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		331C80D6294CF71000263BE5 /* RunnerTests */ = {
+			isa = PBXGroup;
+			children = (
+				331C80D7294CF71000263BE5 /* RunnerTests.swift */,
+			);
+			path = RunnerTests;
+			sourceTree = "<group>";
+		};
+		33BA886A226E78AF003329D5 /* Configs */ = {
+			isa = PBXGroup;
+			children = (
+				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+			);
+			path = Configs;
+			sourceTree = "<group>";
+		};
+		33CC10E42044A3C60003C045 = {
+			isa = PBXGroup;
+			children = (
+				33FAB671232836740065AC1E /* Runner */,
+				33CEB47122A05771004F2AC0 /* Flutter */,
+				331C80D6294CF71000263BE5 /* RunnerTests */,
+				33CC10EE2044A3C60003C045 /* Products */,
+				D73912EC22F37F3D000D13A0 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		33CC10EE2044A3C60003C045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10ED2044A3C60003C045 /* mathtool2.app */,
+				331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		33CC11242044D66E0003C045 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F22044A3C60003C045 /* Assets.xcassets */,
+				33CC10F42044A3C60003C045 /* MainMenu.xib */,
+				33CC10F72044A3C60003C045 /* Info.plist */,
+			);
+			name = Resources;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		33CEB47122A05771004F2AC0 /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+			);
+			path = Flutter;
+			sourceTree = "<group>";
+		};
+		33FAB671232836740065AC1E /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+				33E51914231749380026EE4D /* Release.entitlements */,
+				33CC11242044D66E0003C045 /* Resources */,
+				33BA886A226E78AF003329D5 /* Configs */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		331C80D4294CF70F00263BE5 /* RunnerTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+			buildPhases = (
+				331C80D1294CF70F00263BE5 /* Sources */,
+				331C80D2294CF70F00263BE5 /* Frameworks */,
+				331C80D3294CF70F00263BE5 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				331C80DA294CF71000263BE5 /* PBXTargetDependency */,
+			);
+			name = RunnerTests;
+			productName = RunnerTests;
+			productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+		33CC10EC2044A3C60003C045 /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				33CC10E92044A3C60003C045 /* Sources */,
+				33CC10EA2044A3C60003C045 /* Frameworks */,
+				33CC10EB2044A3C60003C045 /* Resources */,
+				33CC110E2044A8840003C045 /* Bundle Framework */,
+				3399D490228B24CF009A79C7 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				33CC11202044C79F0003C045 /* PBXTargetDependency */,
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 33CC10ED2044A3C60003C045 /* mathtool2.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		33CC10E52044A3C60003C045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				BuildIndependentTargetsInParallel = YES;
+				LastSwiftUpdateCheck = 0920;
+				LastUpgradeCheck = 1510;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					331C80D4294CF70F00263BE5 = {
+						CreatedOnToolsVersion = 14.0;
+						TestTargetID = 33CC10EC2044A3C60003C045;
+					};
+					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 */,
+				331C80D4294CF70F00263BE5 /* RunnerTests */,
+				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		331C80D3294CF70F00263BE5 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		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 */
+		3399D490228B24CF009A79C7 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			alwaysOutOfDate = 1;
+			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";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		331C80D1294CF70F00263BE5 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		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 */
+		331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 33CC10EC2044A3C60003C045 /* Runner */;
+			targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
+		};
+		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 = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		331C80DB294CF71000263BE5 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mathtool2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mathtool2";
+			};
+			name = Debug;
+		};
+		331C80DC294CF71000263BE5 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mathtool2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mathtool2";
+			};
+			name = Release;
+		};
+		331C80DD294CF71000263BE5 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.example.mathtool2.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/mathtool2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/mathtool2";
+			};
+			name = Profile;
+		};
+		338D0CE9231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				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;
+				DEAD_CODE_STRIPPING = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = NO;
+				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.15;
+				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;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				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;
+				DEAD_CODE_STRIPPING = YES;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = NO;
+				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.15;
+				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;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				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;
+				DEAD_CODE_STRIPPING = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = NO;
+				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.15;
+				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 */
+		331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				331C80DB294CF71000263BE5 /* Debug */,
+				331C80DC294CF71000263BE5 /* Release */,
+				331C80DD294CF71000263BE5 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		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 */;
+}

+ 8 - 0
macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 99 - 0
macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1510"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+               BuildableName = "mathtool2.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "mathtool2.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "331C80D4294CF70F00263BE5"
+               BuildableName = "RunnerTests.xctest"
+               BlueprintName = "RunnerTests"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      enableGPUValidationMode = "1"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "mathtool2.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "mathtool2.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 7 - 0
macos/Runner.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików