nonvioce 6 miesięcy temu
commit
0dd25c0ce0
100 zmienionych plików z 6682 dodań i 0 usunięć
  1. 29 0
      .gitignore
  2. 125 0
      CHANGELOG.md
  3. 287 0
      CONTRIBUTING.md
  4. 221 0
      ICON_GUIDE.md
  5. 33 0
      LICENSE
  6. 264 0
      PLUGIN_DEVELOPMENT.md
  7. 198 0
      QUICK_REFERENCE.md
  8. 267 0
      README.md
  9. 88 0
      SIGNING_GUIDE.md
  10. 1 0
      app/.gitignore
  11. 124 0
      app/build.gradle.kts
  12. 21 0
      app/proguard-rules.pro
  13. BIN
      app/release/app-release.apk
  14. BIN
      app/release/baselineProfiles/0/app-release.dm
  15. BIN
      app/release/baselineProfiles/1/app-release.dm
  16. 37 0
      app/release/output-metadata.json
  17. 24 0
      app/src/androidTest/java/com/nonvioce/ijs/ExampleInstrumentedTest.kt
  18. 42 0
      app/src/main/AndroidManifest.xml
  19. 1436 0
      app/src/main/assets/console.css
  20. 307 0
      app/src/main/assets/console.html
  21. 1759 0
      app/src/main/assets/console.js
  22. BIN
      app/src/main/assets/katex/fonts/KaTeX_AMS-Regular.ttf
  23. BIN
      app/src/main/assets/katex/fonts/KaTeX_AMS-Regular.woff
  24. BIN
      app/src/main/assets/katex/fonts/KaTeX_AMS-Regular.woff2
  25. BIN
      app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Bold.ttf
  26. BIN
      app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Bold.woff
  27. BIN
      app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Bold.woff2
  28. BIN
      app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Regular.ttf
  29. BIN
      app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Regular.woff
  30. BIN
      app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Regular.woff2
  31. BIN
      app/src/main/assets/katex/fonts/KaTeX_Fraktur-Bold.ttf
  32. BIN
      app/src/main/assets/katex/fonts/KaTeX_Fraktur-Bold.woff
  33. BIN
      app/src/main/assets/katex/fonts/KaTeX_Fraktur-Bold.woff2
  34. BIN
      app/src/main/assets/katex/fonts/KaTeX_Fraktur-Regular.ttf
  35. BIN
      app/src/main/assets/katex/fonts/KaTeX_Fraktur-Regular.woff
  36. BIN
      app/src/main/assets/katex/fonts/KaTeX_Fraktur-Regular.woff2
  37. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Bold.ttf
  38. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Bold.woff
  39. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Bold.woff2
  40. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-BoldItalic.ttf
  41. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-BoldItalic.woff
  42. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-BoldItalic.woff2
  43. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Italic.ttf
  44. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Italic.woff
  45. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Italic.woff2
  46. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Regular.ttf
  47. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Regular.woff
  48. BIN
      app/src/main/assets/katex/fonts/KaTeX_Main-Regular.woff2
  49. BIN
      app/src/main/assets/katex/fonts/KaTeX_Math-BoldItalic.ttf
  50. BIN
      app/src/main/assets/katex/fonts/KaTeX_Math-BoldItalic.woff
  51. BIN
      app/src/main/assets/katex/fonts/KaTeX_Math-BoldItalic.woff2
  52. BIN
      app/src/main/assets/katex/fonts/KaTeX_Math-Italic.ttf
  53. BIN
      app/src/main/assets/katex/fonts/KaTeX_Math-Italic.woff
  54. BIN
      app/src/main/assets/katex/fonts/KaTeX_Math-Italic.woff2
  55. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Bold.ttf
  56. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Bold.woff
  57. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Bold.woff2
  58. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Italic.ttf
  59. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Italic.woff
  60. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Italic.woff2
  61. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Regular.ttf
  62. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Regular.woff
  63. BIN
      app/src/main/assets/katex/fonts/KaTeX_SansSerif-Regular.woff2
  64. BIN
      app/src/main/assets/katex/fonts/KaTeX_Script-Regular.ttf
  65. BIN
      app/src/main/assets/katex/fonts/KaTeX_Script-Regular.woff
  66. BIN
      app/src/main/assets/katex/fonts/KaTeX_Script-Regular.woff2
  67. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size1-Regular.ttf
  68. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size1-Regular.woff
  69. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size1-Regular.woff2
  70. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size2-Regular.ttf
  71. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size2-Regular.woff
  72. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size2-Regular.woff2
  73. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size3-Regular.ttf
  74. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size3-Regular.woff
  75. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size3-Regular.woff2
  76. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size4-Regular.ttf
  77. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size4-Regular.woff
  78. BIN
      app/src/main/assets/katex/fonts/KaTeX_Size4-Regular.woff2
  79. BIN
      app/src/main/assets/katex/fonts/KaTeX_Typewriter-Regular.ttf
  80. BIN
      app/src/main/assets/katex/fonts/KaTeX_Typewriter-Regular.woff
  81. BIN
      app/src/main/assets/katex/fonts/KaTeX_Typewriter-Regular.woff2
  82. 0 0
      app/src/main/assets/katex/katex.min.css
  83. 0 0
      app/src/main/assets/katex/katex.min.js
  84. BIN
      app/src/main/ic_launcher-playstore.png
  85. 366 0
      app/src/main/java/com/nonvioce/ijs/MainActivity.java
  86. 225 0
      app/src/main/java/com/nonvioce/ijs/PluginManager.java
  87. 614 0
      app/src/main/java/com/nonvioce/ijs/SymjaEngine.java
  88. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  89. 14 0
      app/src/main/res/drawable/ic_launcher_foreground.xml
  90. 18 0
      app/src/main/res/layout/activity_main.xml
  91. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  92. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  93. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  94. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  95. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  96. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  97. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  98. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  99. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  100. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Gradle
+.gradle/
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!gradle/wrapper/gradle-wrapper.properties
+
+# IDE
+.idea/
+.vscode/
+*.iml
+*.iws
+*.ipr
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Android / Local
+local.properties
+*.jks
+*.keystore
+keystore.properties
+/captures
+.externalNativeBuild
+.cxx
+
+# Misc
+*.log
+*.tmp

+ 125 - 0
CHANGELOG.md

@@ -0,0 +1,125 @@
+# 更新日志
+
+所有重要的项目更改都将记录在此文件中。
+
+格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
+本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
+
+## [未发布]
+
+### 新增
+- **LaTeX 渲染功能**
+  - 使用 KaTeX 实时渲染数学公式
+  - `TeXForm[]` 输出自动渲染为美观的数学公式
+  - 支持显示/隐藏 LaTeX 源码
+  - 一键复制 LaTeX 代码
+  - 插件可调用 `Symja.renderLatex()` 渲染公式
+- **Material Design 3 界面**
+  - 采用 Google Material Design 3 设计语言
+  - Material Icons 图标系统
+  - Roboto 字体家族
+  - Material 动画和涟漪效果
+  - 响应式 Material 组件
+  - 优雅的阴影和高度系统
+- 多种绘图类型支持
+  - `Plot[]` - 标准函数图
+  - `ParametricPlot[]` - 参数方程绘图
+  - `PolarPlot[]` - 极坐标绘图
+  - `ListPlot[]` - 列表数据绘图
+- 高级数学功能
+  - 傅里叶变换和逆变换
+  - 拉普拉斯变换和逆变换
+  - 优化函数(Maximize, Minimize)
+  - 表格生成(Table)
+- 变量管理功能
+  - 查看所有已定义变量及其值
+  - 清除特定变量
+  - 变量追踪
+- 格式转换功能
+  - `TeXForm[]` - 转换为 LaTeX 格式(带渲染)
+  - `InputForm[]` - 转换为输入格式
+  - `FullForm[]` - 显示完整形式
+  - `JavaForm[]` - 转换为 Java 代码
+- 增强的绘图功能
+  - 自动坐标轴缩放
+  - 网格线显示
+  - 坐标轴标注
+  - 图像保存为 PNG
+- 更多 Symja 函数支持
+  - 级数求和(Sum)
+  - 求积(Product)
+  - 数值计算(N)
+  - 矩阵运算增强
+- **插件 API 增强**
+  - 添加 `Symja.toTeX()` - LaTeX 转换
+  - 添加 `Symja.renderLatex()` - LaTeX 渲染
+
+### 改进
+- 升级 Symja 库到 3.1.0 版本
+- 统一所有命令格式,使用 Mathematica 风格语法
+  - 绘图命令:`Plot[]`, `ParametricPlot[]`, `PolarPlot[]`, `ListPlot[]`
+  - 格式转换:`TeXForm[]`, `InputForm[]`, `FullForm[]`
+  - 表格生成:`Table[]`
+- 优化绘图算法,支持参数方程和极坐标
+- 改进变量显示,显示实际值
+- 增强错误处理和用户提示
+- 优化界面响应速度
+- 移除特殊命令格式,统一使用函数调用
+- **全新 Material Design 界面**
+  - 更现代、更美观的视觉设计
+  - 更好的可访问性
+  - 更流畅的动画效果
+  - 更清晰的视觉层次
+
+### 修复
+- 修复绘图时的坐标轴显示问题
+- 修复括号匹配检测
+- 修复多行输入模式的问题
+
+## [1.0.0] - 2024-XX-XX
+
+### 新增
+- 基础符号计算功能
+  - 微积分(求导、积分、极限)
+  - 代数运算(展开、因式分解、化简)
+  - 方程求解
+  - 矩阵运算
+- 交互式控制台
+  - 命令历史记录
+  - 自动补全
+  - 多行输入
+  - 虚拟键盘
+- 插件系统
+  - JavaScript 插件支持
+  - 插件导入/导出
+  - 内置示例插件
+- 基础绘图功能
+  - 函数图绘制
+  - 图像保存
+- 帮助系统
+  - 完整的函数文档
+  - 使用示例
+  - 插件开发指南
+
+### 技术
+- 基于 Symja 3.0.0
+- Android 原生应用
+- WebView 交互界面
+- Canvas 绘图
+
+---
+
+## 版本说明
+
+### 版本号格式
+- **主版本号**:不兼容的 API 修改
+- **次版本号**:向下兼容的功能性新增
+- **修订号**:向下兼容的问题修正
+
+### 变更类型
+- **新增**:新功能
+- **改进**:对现有功能的改进
+- **修复**:Bug 修复
+- **移除**:移除的功能
+- **废弃**:即将移除的功能
+- **安全**:安全相关的修复

+ 287 - 0
CONTRIBUTING.md

@@ -0,0 +1,287 @@
+# 贡献指南
+
+感谢你考虑为 IJS 项目做出贡献!
+
+## 行为准则
+
+参与本项目即表示你同意遵守我们的行为准则。请保持友好、尊重和包容。
+
+## 如何贡献
+
+### 报告 Bug
+
+如果你发现了 Bug,请创建一个 Issue 并包含以下信息:
+
+1. **清晰的标题**:简要描述问题
+2. **重现步骤**:详细说明如何重现问题
+3. **预期行为**:描述你期望发生什么
+4. **实际行为**:描述实际发生了什么
+5. **环境信息**:
+   - Android 版本
+   - 设备型号
+   - 应用版本
+6. **截图**:如果可能,提供截图
+7. **日志**:如果有错误日志,请附上
+
+### 建议新功能
+
+如果你有新功能的想法:
+
+1. 先检查是否已有类似的 Issue
+2. 创建一个新的 Issue,标记为 "enhancement"
+3. 详细描述:
+   - 功能的用途
+   - 为什么需要这个功能
+   - 如何实现(如果有想法)
+   - 使用示例
+
+### 提交代码
+
+#### 开发流程
+
+1. **Fork 仓库**
+   ```bash
+   # 在 GitHub 上点击 Fork 按钮
+   ```
+
+2. **克隆你的 Fork**
+   ```bash
+   git clone https://github.com/your-username/ijs.git
+   cd ijs
+   ```
+
+3. **创建分支**
+   ```bash
+   git checkout -b feature/your-feature-name
+   # 或
+   git checkout -b fix/your-bug-fix
+   ```
+
+4. **进行更改**
+   - 遵循代码风格指南
+   - 添加必要的注释
+   - 更新文档(如果需要)
+
+5. **测试你的更改**
+   ```bash
+   ./gradlew test
+   ./gradlew connectedAndroidTest
+   ```
+
+6. **提交更改**
+   ```bash
+   git add .
+   git commit -m "feat: add amazing feature"
+   ```
+
+7. **推送到你的 Fork**
+   ```bash
+   git push origin feature/your-feature-name
+   ```
+
+8. **创建 Pull Request**
+   - 在 GitHub 上打开 Pull Request
+   - 填写 PR 模板
+   - 等待审核
+
+#### 提交信息规范
+
+使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式:
+
+```
+<type>(<scope>): <subject>
+
+<body>
+
+<footer>
+```
+
+**类型(type)**:
+- `feat`: 新功能
+- `fix`: Bug 修复
+- `docs`: 文档更新
+- `style`: 代码格式(不影响代码运行)
+- `refactor`: 重构
+- `perf`: 性能优化
+- `test`: 测试相关
+- `chore`: 构建过程或辅助工具的变动
+
+**示例**:
+```
+feat(plot): add parametric plot support
+
+Add ParametricPlot function to draw parametric equations.
+Supports custom parameter ranges and point density.
+
+Closes #123
+```
+
+### 代码风格
+
+#### Java 代码
+- 使用 4 个空格缩进
+- 类名使用 PascalCase
+- 方法名和变量名使用 camelCase
+- 常量使用 UPPER_SNAKE_CASE
+- 添加 JavaDoc 注释
+
+```java
+/**
+ * 计算函数的导数
+ * 
+ * @param expression 函数表达式
+ * @param variable 变量名
+ * @return 导数表达式
+ */
+public String differentiate(String expression, String variable) {
+    // 实现代码
+}
+```
+
+#### JavaScript 代码
+- 使用 4 个空格缩进
+- 使用 ES6+ 语法
+- 变量名使用 camelCase
+- 常量使用 UPPER_SNAKE_CASE
+- 添加 JSDoc 注释
+
+```javascript
+/**
+ * 绘制函数图像
+ * @param {string} func - 函数表达式
+ * @param {string} variable - 变量名
+ * @param {number} xMin - x 最小值
+ * @param {number} xMax - x 最大值
+ */
+plotFunction(func, variable, xMin, xMax) {
+    // 实现代码
+}
+```
+
+#### Kotlin 代码
+- 使用 4 个空格缩进
+- 遵循 [Kotlin 编码规范](https://kotlinlang.org/docs/coding-conventions.html)
+
+### 文档
+
+- 更新 README.md(如果添加了新功能)
+- 更新 CHANGELOG.md
+- 添加代码注释
+- 更新帮助文档(如果需要)
+
+### 测试
+
+- 为新功能添加单元测试
+- 确保所有测试通过
+- 测试不同的 Android 版本
+- 测试不同的屏幕尺寸
+
+## 开发环境设置
+
+### 必需工具
+- Android Studio (最新稳定版)
+- JDK 11 或更高版本
+- Git
+
+### 设置步骤
+
+1. **安装 Android Studio**
+   - 下载:https://developer.android.com/studio
+   - 安装 Android SDK (API 24+)
+
+2. **克隆仓库**
+   ```bash
+   git clone https://github.com/your-username/ijs.git
+   cd ijs
+   ```
+
+3. **打开项目**
+   - 在 Android Studio 中打开项目
+   - 等待 Gradle 同步完成
+
+4. **运行应用**
+   - 连接 Android 设备或启动模拟器
+   - 点击 "Run" 按钮
+
+### 项目结构
+
+```
+ijs/
+├── app/
+│   ├── src/
+│   │   ├── main/
+│   │   │   ├── java/com/nonvioce/ijs/
+│   │   │   │   ├── MainActivity.java      # 主活动
+│   │   │   │   ├── SymjaEngine.java       # Symja 引擎封装
+│   │   │   │   └── PluginManager.java     # 插件管理器
+│   │   │   ├── assets/
+│   │   │   │   ├── console.html           # 控制台 HTML
+│   │   │   │   ├── console.css            # 控制台样式
+│   │   │   │   └── console.js             # 控制台逻辑
+│   │   │   └── res/                       # Android 资源
+│   │   ├── androidTest/                   # Android 测试
+│   │   └── test/                          # 单元测试
+│   └── build.gradle.kts                   # 应用构建配置
+├── gradle/                                # Gradle 配置
+├── README.md                              # 项目说明
+├── CHANGELOG.md                           # 更新日志
+└── CONTRIBUTING.md                        # 贡献指南
+```
+
+## 插件开发
+
+如果你想开发插件:
+
+1. 参考 `app/src/main/assets/console.html` 中的插件开发文档
+2. 查看示例插件代码
+3. 测试你的插件
+4. 分享你的插件(可选)
+
+### 插件结构
+
+```json
+{
+  "id": "unique_plugin_id",
+  "name": "插件名称",
+  "description": "插件描述",
+  "icon": "📐",
+  "command": "cmd",
+  "inputs": [
+    {
+      "name": "input_name",
+      "label": "输入标签",
+      "type": "number",
+      "defaultValue": "0",
+      "placeholder": "提示文本"
+    }
+  ],
+  "jsCode": "function calculate(inputs, Symja) { ... }"
+}
+```
+
+## 发布流程
+
+(仅限维护者)
+
+1. 更新版本号
+2. 更新 CHANGELOG.md
+3. 创建 Git 标签
+4. 构建发布版本
+5. 发布到 GitHub Releases
+
+## 获取帮助
+
+如果你需要帮助:
+
+1. 查看 [README.md](README.md)
+2. 搜索现有的 Issues
+3. 创建新的 Issue
+4. 加入讨论区
+
+## 许可证
+
+通过贡献代码,你同意你的贡献将在 GPL-3.0 许可证下发布。
+
+---
+
+再次感谢你的贡献!🎉

+ 221 - 0
ICON_GUIDE.md

@@ -0,0 +1,221 @@
+# Android 应用图标配置指南
+
+## 📱 图标文件位置
+
+应用图标需要放在以下目录中,每个目录对应不同的屏幕密度:
+
+```
+app/src/main/res/
+├── mipmap-mdpi/          ← 48x48 px (中密度)
+│   ├── ic_launcher.webp
+│   └── ic_launcher_round.webp
+├── mipmap-hdpi/          ← 72x72 px (高密度)
+│   ├── ic_launcher.webp
+│   └── ic_launcher_round.webp
+├── mipmap-xhdpi/         ← 96x96 px (超高密度)
+│   ├── ic_launcher.webp
+│   └── ic_launcher_round.webp
+├── mipmap-xxhdpi/        ← 144x144 px (超超高密度)
+│   ├── ic_launcher.webp
+│   └── ic_launcher_round.webp
+├── mipmap-xxxhdpi/       ← 192x192 px (超超超高密度)
+│   ├── ic_launcher.webp
+│   └── ic_launcher_round.webp
+└── mipmap-anydpi-v26/    ← Android 8.0+ 自适应图标
+    ├── ic_launcher.xml
+    └── ic_launcher_round.xml
+```
+
+## 🎨 图标尺寸要求
+
+| 密度 | 文件夹 | 尺寸 (px) |
+|------|--------|-----------|
+| MDPI | mipmap-mdpi | 48 x 48 |
+| HDPI | mipmap-hdpi | 72 x 72 |
+| XHDPI | mipmap-xhdpi | 96 x 96 |
+| XXHDPI | mipmap-xxhdpi | 144 x 144 |
+| XXXHDPI | mipmap-xxxhdpi | 192 x 192 |
+
+## 📝 两种图标类型
+
+### 1. 标准图标 (ic_launcher)
+- 方形图标
+- 用于大多数设备
+
+### 2. 圆形图标 (ic_launcher_round)
+- 圆形图标
+- 用于支持圆形图标的设备(如某些启动器)
+
+## 🔧 替换图标的步骤
+
+### 方法一:使用 Android Studio(推荐)✨
+
+**支持的输入格式:PNG, JPG, SVG, PSD**
+
+1. 右键点击 `app` 模块
+2. 选择 `New` → `Image Asset`
+3. 在 Asset Studio 中:
+   - **Icon Type**: 选择 `Launcher Icons (Adaptive and Legacy)`
+   - **Foreground Layer**: 
+     - 点击路径选择按钮
+     - 选择你的 SVG 文件(或 PNG/JPG)
+     - Android Studio 会自动转换 SVG
+   - **Background Layer**: 选择颜色(如樱花粉 `#FFB7C5`)或上传背景图
+   - 预览效果
+4. 点击 `Next` → `Finish`
+
+**优点:**
+- ✅ 直接支持 SVG 格式
+- ✅ 自动生成所有尺寸
+- ✅ 自动转换为 WebP/PNG
+- ✅ 实时预览效果
+
+### 方法二:在线转换 SVG
+
+如果你有 SVG 文件,可以使用在线工具转换:
+
+**推荐工具:**
+1. **Android Asset Studio**: https://romannurik.github.io/AndroidAssetStudio/
+   - 支持 SVG 上传
+   - 自动生成所有密度的图标
+   
+2. **CloudConvert**: https://cloudconvert.com/svg-to-png
+   - 将 SVG 转换为高分辨率 PNG (1024x1024)
+   - 然后用 PNG 生成图标
+
+3. **Figma/Sketch/Adobe XD**:
+   - 导入 SVG
+   - 导出为不同尺寸的 PNG
+
+步骤:
+1. 上传 SVG 文件
+2. 设置输出尺寸(建议 1024x1024 px)
+3. 下载 PNG 文件
+4. 使用 PNG 文件在 Android Studio 中生成图标
+
+### 方法二:手动替换
+
+**注意:mipmap 文件夹只支持位图格式(PNG/WebP/JPG),不支持 SVG**
+
+如果你想手动替换:
+
+1. 将 SVG 转换为 PNG(使用上述在线工具)
+2. 准备不同尺寸的图标文件
+3. 将文件重命名为 `ic_launcher.webp` 或 `ic_launcher.png`
+4. 分别放入对应的 mipmap 文件夹中
+
+### 方法三:使用矢量图标(仅用于自适应图标)
+
+**SVG 可以转换为 Android Vector Drawable (XML)**
+
+如果你想在自适应图标中使用矢量图:
+
+1. 在 Android Studio 中:
+   - 右键点击 `res/drawable`
+   - 选择 `New` → `Vector Asset`
+   - 选择 `Local file (SVG, PSD)`
+   - 选择你的 SVG 文件
+   - 点击 `Next` → `Finish`
+
+2. 这会生成一个 XML 矢量图标文件
+3. 然后在 `ic_launcher_foreground.xml` 中引用它
+
+**示例:**
+```xml
+<!-- res/drawable/ic_launcher_foreground.xml -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <!-- SVG 转换后的路径数据 -->
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M50,20 L80,80 L20,80 Z"/>
+</vector>
+```
+
+### 方法三:使用在线工具
+
+推荐工具:
+- **Android Asset Studio**: https://romannurik.github.io/AndroidAssetStudio/
+- **App Icon Generator**: https://appicon.co/
+
+步骤:
+1. 上传一张高分辨率图标(建议 1024x1024 px)
+2. 下载生成的所有尺寸图标
+3. 解压后将文件复制到对应的 mipmap 文件夹
+
+## 🎯 自适应图标(Android 8.0+)
+
+对于 Android 8.0 及以上版本,使用自适应图标:
+
+### 编辑前景图标
+`app/src/main/res/drawable/ic_launcher_foreground.xml`
+
+```xml
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <!-- 你的图标路径 -->
+</vector>
+```
+
+### 编辑背景
+`app/src/main/res/drawable/ic_launcher_background.xml`
+
+可以是纯色或矢量图:
+
+```xml
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#FFB7C5"
+        android:pathData="M0,0h108v108h-108z"/>
+</vector>
+```
+
+## 🎨 樱花粉主题图标建议
+
+根据你的应用主题色(樱花粉 #FFB7C5),建议:
+
+1. **背景色**: 使用樱花粉 `#FFB7C5`
+2. **前景图标**: 使用白色或深色图标,确保对比度
+3. **风格**: 简洁、现代、符合 Material Design 规范
+
+## ✅ 验证图标
+
+替换图标后:
+
+1. 清理项目:`Build` → `Clean Project`
+2. 重新构建:`Build` → `Rebuild Project`
+3. 卸载旧版本应用
+4. 重新安装并检查图标显示
+
+## 📌 注意事项
+
+- ✅ 使用 PNG 或 WebP 格式(WebP 文件更小)
+- ✅ 确保所有密度都有对应的图标
+- ✅ 图标应该有透明背景(对于自适应图标)
+- ✅ 避免在图标中使用文字(除非是品牌标识)
+- ✅ 测试在不同设备和启动器上的显示效果
+- ❌ 不要使用 JPEG 格式
+- ❌ 不要在图标边缘留白(系统会自动添加)
+
+## 🔍 当前配置
+
+你的应用图标在 `AndroidManifest.xml` 中配置:
+
+```xml
+<application
+    android:icon="@mipmap/ic_launcher"
+    android:roundIcon="@mipmap/ic_launcher_round"
+    ...>
+```
+
+如果你想使用不同的图标名称,需要同时修改 AndroidManifest.xml。

+ 33 - 0
LICENSE

@@ -0,0 +1,33 @@
+GNU GENERAL PUBLIC LICENSE
+Version 3, 29 June 2007
+
+Copyright (C) 2024 IJS Project Contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+---
+
+THIRD-PARTY LICENSES
+
+This project uses the following third-party libraries:
+
+1. Symja (matheclipse-core)
+   License: LGPL-3.0
+   URL: https://github.com/axkr/symja_android_library
+
+2. Android Support Libraries
+   License: Apache-2.0
+   URL: https://developer.android.com/
+
+For full license texts, see the LICENSES directory.

+ 264 - 0
PLUGIN_DEVELOPMENT.md

@@ -0,0 +1,264 @@
+# IJS 插件开发文档 (v1.1)
+
+## 目录
+
+- [插件结构](#插件结构)
+- [快速开始](#快速开始)
+- [插件代码规范](#插件代码规范)
+- [Symja API 完整参考](#symja-api-完整参考)
+
+---
+
+## 插件结构
+
+插件是 JSON 格式的配置文件,包含以下字段:
+
+```json
+{
+  "id": "plugin_id",
+  "name": "插件名称",
+  "description": "插件描述",
+  "icon": "📐",
+  "command": "cmd",
+  "inputs": [
+    {
+      "name": "input1",
+      "label": "输入标签",
+      "defaultValue": "0",
+      "placeholder": "提示文本"
+    }
+  ],
+  "jsCode": "function calculate(inputs, Symja) { ... }"
+}
+```
+
+### 字段说明
+
+- `id` - 插件唯一标识符
+- `name` - 插件显示名称
+- `description` - 插件功能描述
+- `icon` - 插件图标(emoji)
+- `command` - 快捷命令(可选),用于命令行执行
+- `inputs` - 输入参数数组
+  - `name` - 参数名称(代码中访问)
+  - `label` - 参数标签(显示给用户)
+  - `defaultValue` - 默认值(可选)
+  - `placeholder` - 提示文本(可选)
+- `jsCode` - 插件计算代码
+
+---
+
+## 快速开始
+
+### 示例 1:圆面积计算器
+
+```json
+{
+  "id": "circle_area",
+  "name": "圆面积计算器",
+  "description": "计算圆的面积",
+  "icon": "⭕",
+  "command": "circle",
+  "inputs": [
+    {
+      "name": "radius",
+      "label": "半径",
+      "defaultValue": "1",
+      "placeholder": "输入半径"
+    }
+  ],
+  "jsCode": "function calculate(inputs, Symja) {\n  const r = inputs.radius;\n  if (!r || parseFloat(r) <= 0) {\n    throw new Error('半径必须是正数');\n  }\n  Symja.print(`计算半径为 ${r} 的圆面积`);\n  return `Pi*${r}^2`;\n}"
+}
+```
+
+**使用方式:**
+- 对话框:点击插件图标打开
+- 命令行:`circle[5]` 直接计算半径为 5 的圆面积
+- 结果引用:`%` 可引用上次结果,如 `% /. Pi->3.14`
+
+### 示例 2:导数计算器
+
+```json
+{
+  "id": "derivative_calculator",
+  "name": "导数计算器",
+  "description": "计算函数的导数",
+  "icon": "📈",
+  "command": "deriv",
+  "inputs": [
+    {
+      "name": "function",
+      "label": "函数表达式",
+      "defaultValue": "x^2 + 3*x",
+      "placeholder": "例如: x^2 + 3*x"
+    },
+    {
+      "name": "variable",
+      "label": "变量",
+      "defaultValue": "x",
+      "placeholder": "例如: x"
+    }
+  ],
+  "jsCode": "function calculate(inputs, Symja) {\n  const func = inputs.function;\n  const variable = inputs.variable;\n  if (!func || !variable) {\n    throw new Error('请输入函数和变量');\n  }\n  Symja.print(`对 ${func} 关于 ${variable} 求导`);\n  return Symja.differentiate(func, variable);\n}"
+}
+```
+
+**使用方式:**
+- 命令行:`deriv[x^3, x]` 计算 x³ 的导数
+- 列表参数:`deriv[{x^2, x^3}, x]` 列表作为一个参数
+
+---
+
+## 插件代码规范
+
+### v1.1 核心特性
+
+1. **输入自动转换**
+   - 用户输入会自动验证并转换为 Symja 可运算类型
+   - 插件可直接使用 `inputs.xxx` 进行计算
+
+2. **Symja.print() 输出**
+   - 用于输出说明文本,不影响返回值
+   - 示例:`Symja.print('计算中...')`
+
+3. **返回值要求**
+   - 必须返回有效的 Symja 表达式(如 `'x^2'`、`'Pi*r^2'`)
+   - 系统会自动验证返回值
+   - 返回值可通过 `%` 在后续计算中引用
+
+4. **命令执行语法**
+   - 定义 `command` 字段后,可用 `command[arg1, arg2]` 直接执行
+   - 支持列表参数:`command[{1,2}, {3,4}]` 有 2 个参数
+   - 支持嵌套:`command[{{1,2},{3,4}}]` 矩阵作为 1 个参数
+
+5. **自动补全**
+   - 输入命令时自动提示参数
+   - 按 Tab 或点击补全
+
+### 代码模板
+
+```javascript
+function calculate(inputs, Symja) {
+  // 1. 获取输入(已自动转换为可运算类型)
+  const param1 = inputs.param1;
+  
+  // 2. 输入验证
+  if (!param1) {
+    throw new Error('请输入参数');
+  }
+  
+  // 3. 输出说明信息(可选)
+  Symja.print('正在计算...');
+  
+  // 4. 执行计算
+  const result = Symja.eval(`SomeFunction[${param1}]`);
+  
+  // 5. 返回可运算的 Symja 表达式
+  return result;
+}
+```
+
+### 重要规则
+
+- ✅ 函数签名必须是 `function calculate(inputs, Symja) { ... }`
+- ✅ 输入值自动转换,可直接用于计算
+- ✅ 必须返回有效的 Symja 表达式
+- ✅ 使用 `Symja.print()` 输出说明,不影响返回值
+- ✅ 使用 `throw new Error()` 抛出错误
+- ✅ 所有 Symja API 参数都是字符串类型
+
+---
+
+## Symja API 完整参考
+
+### 输入输出
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `inputs.xxx` | 获取输入参数(已自动转换) | `inputs.radius` |
+| `Symja.print(msg)` | 输出说明文本 | `Symja.print('计算中...')` |
+| `return expr` | 返回可运算表达式 | `return 'Pi*r^2'` |
+
+### 基础计算
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `Symja.eval(expr)` | 执行任意 Symja 表达式 | `Symja.eval('2+2')` |
+
+### 微积分
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `differentiate(expr, var)` | 求导数 | `Symja.differentiate('x^2', 'x')` |
+| `integrate(expr, var)` | 求不定积分 | `Symja.integrate('x^2', 'x')` |
+| `limit(expr, var, point, dir)` | 求极限 | `Symja.limit('Sin[x]/x', 'x', '0')` |
+| `series(expr, var, point, order)` | 泰勒级数 | `Symja.series('Exp[x]', 'x', '0', '5')` |
+
+### 代数运算
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `solve(eq, var)` | 解方程 | `Symja.solve('x^2-4==0', 'x')` |
+| `simplify(expr)` | 化简 | `Symja.simplify('(x^2-1)/(x-1)')` |
+| `expand(expr)` | 展开 | `Symja.expand('(x+1)^3')` |
+| `factor(expr)` | 因式分解 | `Symja.factor('x^2-4')` |
+| `apart(expr, var)` | 部分分式 | `Symja.apart('1/(x^2-1)', 'x')` |
+| `together(expr)` | 通分 | `Symja.together('1/x + 1/y')` |
+| `trigSimplify(expr)` | 三角化简 | `Symja.trigSimplify('Sin[x]^2+Cos[x]^2')` |
+
+### 求和与求积
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `sum(expr, var, start, end)` | 求和 | `Symja.sum('i^2', 'i', '1', '10')` |
+| `product(expr, var, start, end)` | 求积 | `Symja.product('i', 'i', '1', '5')` |
+
+### 矩阵运算
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `matrix.det(matrix)` | 行列式 | `Symja.matrix.det('{{1,2},{3,4}}')` |
+| `matrix.inverse(matrix)` | 逆矩阵 | `Symja.matrix.inverse('{{1,2},{3,4}}')` |
+| `matrix.transpose(matrix)` | 转置 | `Symja.matrix.transpose('{{1,2},{3,4}}')` |
+| `matrix.eigenvalues(matrix)` | 特征值 | `Symja.matrix.eigenvalues('{{1,2},{3,4}}')` |
+| `matrix.rank(matrix)` | 矩阵秩 | `Symja.matrix.rank('{{1,2},{3,4}}')` |
+
+### 数论
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `gcd(a, b)` | 最大公约数 | `Symja.gcd('12', '18')` |
+| `lcm(a, b)` | 最小公倍数 | `Symja.lcm('12', '18')` |
+| `factorInteger(n)` | 质因数分解 | `Symja.factorInteger('60')` |
+| `isPrime(n)` | 判断素数 | `Symja.isPrime('17')` |
+
+### 组合数学
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `fibonacci(n)` | 斐波那契数 | `Symja.fibonacci('10')` |
+| `binomial(n, k)` | 组合数 C(n,k) | `Symja.binomial('5', '2')` |
+
+### 数值计算
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `numeric(expr, precision)` | 高精度数值计算 | `Symja.numeric('Pi', '50')` |
+
+### LaTeX 支持
+
+| API | 说明 | 示例 |
+|-----|------|------|
+| `toTeX(expr)` | 转换为 LaTeX | `Symja.toTeX('x^2 + 2*x + 1')` |
+| `renderLatex(latex)` | 渲染 LaTeX | `Symja.renderLatex('\\frac{1}{2}')` |
+
+---
+
+## 插件位置
+
+插件文件保存在:
+```
+/storage/emulated/0/Android/data/com.nonvioce.ijs/files/plugins/
+```
+
+可以直接在该目录创建 `.json` 文件来添加插件。

+ 198 - 0
QUICK_REFERENCE.md

@@ -0,0 +1,198 @@
+# IJS 快速参考指南
+
+## 🎯 基本语法
+
+### 函数调用
+```mathematica
+FunctionName[arg1, arg2, ...]
+```
+
+### 列表和范围
+```mathematica
+{1, 2, 3, 4, 5}              // 列表
+{x, 0, 10}                   // 范围:变量 x 从 0 到 10
+{x, 0, 10, 0.5}              // 带步长的范围
+```
+
+### 变量赋值
+```mathematica
+x = 5
+f = x^2 + 3*x
+```
+
+## 📊 绘图函数
+
+| 函数 | 语法 | 示例 |
+|------|------|------|
+| Plot | `Plot[func, {var, min, max}]` | `Plot[Sin[x], {x, -10, 10}]` |
+| ParametricPlot | `ParametricPlot[{xFunc, yFunc}, {var, min, max}]` | `ParametricPlot[{Cos[t], Sin[t]}, {t, 0, 2*Pi}]` |
+| PolarPlot | `PolarPlot[rFunc, {var, min, max}]` | `PolarPlot[1+Cos[t], {t, 0, 2*Pi}]` |
+| ListPlot | `ListPlot[list]` | `ListPlot[{1, 4, 9, 16, 25}]` |
+
+## 🧮 微积分
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| D | 求导 | `D[x^3, x]` |
+| Integrate | 积分 | `Integrate[x^2, x]` |
+| Limit | 极限 | `Limit[Sin[x]/x, x -> 0]` |
+| Series | 级数展开 | `Series[Exp[x], {x, 0, 5}]` |
+| Sum | 求和 | `Sum[i^2, {i, 1, 10}]` |
+| Product | 求积 | `Product[i, {i, 1, 5}]` |
+
+## 🔢 代数
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| Solve | 解方程 | `Solve[x^2 - 4 == 0, x]` |
+| Expand | 展开 | `Expand[(x+1)^3]` |
+| Factor | 因式分解 | `Factor[x^2 - 4]` |
+| Simplify | 化简 | `Simplify[Sin[x]^2 + Cos[x]^2]` |
+| Together | 通分 | `Together[1/x + 1/y]` |
+| Apart | 部分分式 | `Apart[1/(x^2-1), x]` |
+
+## 📐 矩阵
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| Det | 行列式 | `Det[{{1,2},{3,4}}]` |
+| Inverse | 逆矩阵 | `Inverse[{{1,2},{3,4}}]` |
+| Transpose | 转置 | `Transpose[{{1,2},{3,4}}]` |
+| Eigenvalues | 特征值 | `Eigenvalues[{{1,2},{3,4}}]` |
+
+## 🔬 变换
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| FourierTransform | 傅里叶变换 | `FourierTransform[Exp[-x^2], x, w]` |
+| LaplaceTransform | 拉普拉斯变换 | `LaplaceTransform[Sin[t], t, s]` |
+| InverseLaplaceTransform | 逆拉普拉斯变换 | `InverseLaplaceTransform[1/(s^2+1), s, t]` |
+
+## 🎲 数论
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| GCD | 最大公约数 | `GCD[12, 18]` |
+| LCM | 最小公倍数 | `LCM[12, 18]` |
+| FactorInteger | 质因数分解 | `FactorInteger[60]` |
+| PrimeQ | 判断素数 | `PrimeQ[17]` |
+| Prime | 第n个素数 | `Prime[10]` |
+| Fibonacci | 斐波那契数 | `Fibonacci[10]` |
+| Binomial | 组合数 | `Binomial[5, 2]` |
+
+## 🔄 格式转换
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| TeXForm | 转换为 LaTeX | `TeXForm[Sin[x]^2 + Cos[x]^2]` |
+| InputForm | 输入格式 | `InputForm[expr]` |
+| FullForm | 完整形式 | `FullForm[expr]` |
+| JavaForm | Java 代码 | `JavaForm[expr]` |
+| N | 数值化 | `N[Pi, 50]` |
+
+## 📋 数据生成
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| Table | 生成表格 | `Table[i^2, {i, 1, 10}]` |
+| Range | 生成数列 | `Range[1, 10]` |
+
+## 🎯 优化
+
+| 函数 | 说明 | 示例 |
+|------|------|------|
+| Maximize | 求最大值 | `Maximize[x^2 - 4*x + 3, x]` |
+| Minimize | 求最小值 | `Minimize[x^2 - 4*x + 3, x]` |
+
+## 💡 特殊命令
+
+| 命令 | 说明 |
+|------|------|
+| `help` 或 `?` | 显示帮助 |
+| `clear` 或 `cls` | 清空输出 |
+| `reset` | 重置所有变量 |
+| `history` | 显示命令历史 |
+| `vars` | 显示已定义变量 |
+| `clear <var>` | 清除特定变量 |
+| `plugins` | 打开插件管理 |
+
+## ⌨️ 快捷键
+
+| 快捷键 | 功能 |
+|--------|------|
+| `Enter` | 执行命令 |
+| `↑` | 上一条历史命令 |
+| `↓` | 下一条历史命令 |
+| `Tab` | 自动补全函数名 |
+
+## 📝 常用示例
+
+### 绘制美丽的曲线
+```mathematica
+// 利萨茹图形
+ParametricPlot[{Sin[3*t], Cos[5*t]}, {t, 0, 2*Pi}]
+
+// 心形线
+PolarPlot[1+Cos[t], {t, 0, 2*Pi}]
+
+// 玫瑰线
+PolarPlot[Sin[6*t], {t, 0, Pi}]
+
+// 螺旋线
+ParametricPlot[{t*Cos[t], t*Sin[t]}, {t, 0, 10}]
+```
+
+### 求解数学问题
+```mathematica
+// 求导数
+D[x^3 + 2*x^2 - 5*x + 1, x]
+
+// 求积分
+Integrate[x*Sin[x], x]
+
+// 解方程
+Solve[x^3 - 6*x^2 + 11*x - 6 == 0, x]
+
+// 因式分解
+Factor[x^4 - 1]
+
+// 泰勒展开
+Series[Sin[x], {x, 0, 7}]
+```
+
+### 矩阵运算
+```mathematica
+// 定义矩阵
+A = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
+
+// 计算行列式
+Det[A]
+
+// 求特征值
+Eigenvalues[A]
+
+// 矩阵乘法
+{{1,2},{3,4}}.{{5,6},{7,8}}
+```
+
+### 数值计算
+```mathematica
+// 高精度计算 π
+N[Pi, 100]
+
+// 计算平方根
+N[Sqrt[2], 50]
+
+// 数值积分
+NIntegrate[Exp[-x^2], {x, 0, Infinity}]
+```
+
+## 🔗 更多资源
+
+- 完整文档:查看 README.md
+- 插件开发:查看应用内的插件开发文档
+- Symja 官方文档:https://github.com/axkr/symja_android_library
+
+---
+
+**提示**:所有函数名首字母大写,使用方括号 `[]` 表示参数!

+ 267 - 0
README.md

@@ -0,0 +1,267 @@
+# IJS - Interactive Java Symja
+
+<div align="center">
+
+![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg)
+![Platform](https://img.shields.io/badge/platform-Android-green.svg)
+![Java](https://img.shields.io/badge/Java-11-orange.svg)
+
+一个基于 [Symja](https://github.com/axkr/symja_android_library) 的 Android 符号计算应用
+
+提供类似 Mathematica 的交互式数学计算体验
+
+[功能特性](#-功能特性) • [快速开始](#-快速开始) • [插件系统](#-插件系统) • [构建说明](#-构建说明)
+
+</div>
+
+---
+
+## ✨ 功能特性
+
+### 🧮 符号计算引擎
+基于强大的 Symja 库,支持:
+- **微积分**:求导、积分、极限、级数展开
+- **代数**:方程求解、因式分解、化简、展开
+- **矩阵**:行列式、逆矩阵、特征值、秩
+- **数论**:素数、因数分解、最大公约数
+- **特殊函数**:Gamma、Beta、Bessel 等
+
+### 🎨 LaTeX 渲染
+- 使用 KaTeX 实时渲染数学公式
+- 支持 `TeXForm` 自动转换和显示
+- 可复制 LaTeX 源码
+
+### 🔌 插件系统
+- 支持 JavaScript 自定义插件
+- 完整的 Symja API 访问
+- 插件导入/导出/分享
+- 命令快捷方式支持
+
+### 💡 智能交互
+- 自动补全函数名和命令
+- ↑↓ 键浏览历史记录
+- 变量管理和 `%` 引用
+- 虚拟键盘快速输入
+- Jupyter 风格的执行计数器
+
+---
+
+## 🚀 快速开始
+
+### 基础计算
+```mathematica
+2 + 2                          // 算术运算
+Sqrt[2]                        // 平方根
+E^(I*Pi)                       // 欧拉公式
+%                              // 引用上一次输出
+```
+
+### 微积分
+```mathematica
+D[x^3, x]                      // 求导
+Integrate[x^2, {x, 0, 1}]      // 定积分
+Limit[Sin[x]/x, x -> 0]        // 极限
+Series[Exp[x], {x, 0, 5}]      // 泰勒展开
+```
+
+### 代数
+```mathematica
+Solve[x^2 - 5*x + 6 == 0, x]   // 解方程
+Expand[(x+1)^3]                // 展开
+Factor[x^2 - 4]                // 因式分解
+Simplify[Sin[x]^2+Cos[x]^2]    // 化简
+```
+
+### 矩阵
+```mathematica
+Det[{{1,2},{3,4}}]             // 行列式
+Inverse[{{1,2},{3,4}}]         // 逆矩阵
+Eigenvalues[{{1,2},{3,4}}]     // 特征值
+```
+
+### LaTeX 渲染
+```mathematica
+TeXForm[Integrate[x^2, x]]     // 自动渲染为数学公式
+TeXForm[Sum[1/n^2, {n, 1, Infinity}]]
+```
+
+### 特殊命令
+```
+help          显示帮助
+clear         清空输出
+vars          显示已定义变量
+plugins       打开插件管理
+history       显示命令历史
+```
+
+---
+
+## 🔌 插件系统
+
+### 插件结构
+插件是 JSON 格式文件,支持自定义数学计算功能:
+
+```json
+{
+  "id": "circle_area",
+  "name": "圆面积计算器",
+  "icon": "⭕",
+  "command": "circle",
+  "inputs": [
+    {
+      "name": "radius",
+      "label": "半径",
+      "defaultValue": "1"
+    }
+  ],
+  "jsCode": "function calculate(inputs, Symja) {\n  Symja.print('计算圆面积');\n  return `Pi*${inputs.radius}^2`;\n}"
+}
+```
+
+### 使用插件
+```mathematica
+circle[5]                      // 执行插件命令
+```
+
+### Symja API
+插件可访问完整的 Symja API:
+
+```javascript
+// 基础
+Symja.eval(expr)
+Symja.print(message)
+
+// 微积分
+Symja.differentiate(expr, var)
+Symja.integrate(expr, var)
+
+// 代数
+Symja.solve(equation, var)
+Symja.simplify(expr)
+Symja.expand(expr)
+
+// 矩阵
+Symja.matrix.det(matrix)
+Symja.matrix.inverse(matrix)
+
+// 数论
+Symja.gcd(a, b)
+Symja.isPrime(n)
+```
+
+### 插件管理
+在控制台输入 `plugins` 打开插件管理面板:
+- **插件列表**:查看已安装插件
+- **开发文档**:查看 API 文档和示例
+- **导入/分享**:导入、导出、删除插件
+
+插件文件存储在:
+```
+/storage/emulated/0/Android/data/com.nonvioce.ijs/files/plugins/
+```
+
+---
+
+## 🔧 技术栈
+
+- **Android**:原生 Android 应用(Java 11)
+- **Symja 3.1.0**:符号计算引擎
+- **WebView + JavaScript**:交互式控制台
+- **KaTeX 0.16.9**:LaTeX 渲染
+- **Material Design 3**:现代化 UI 设计
+
+---
+
+## 📦 构建说明
+
+### 前置要求
+- Android Studio (最新版本)
+- JDK 11+
+- Android SDK (API 24+)
+
+### 构建步骤
+
+1. 克隆仓库
+```bash
+git clone https://github.com/yourusername/ijs.git
+cd ijs
+```
+
+2. 使用 Android Studio 打开项目
+
+3. 同步 Gradle 依赖
+
+4. 构建 APK
+```bash
+./gradlew assembleDebug
+```
+
+### 主要依赖
+```kotlin
+implementation("org.matheclipse:matheclipse-core:3.1.0")
+implementation("androidx.appcompat:appcompat:1.6.1")
+implementation("com.google.android.material:material:1.11.0")
+```
+
+---
+
+## 📚 关于 Symja
+
+本项目使用 [Symja](https://github.com/axkr/symja_android_library) 作为符号计算引擎。
+
+Symja 是一个用纯 Java 实现的计算机代数系统(CAS),灵感来自 Wolfram Language,支持:
+- 任意精度整数、有理数和复数
+- 多项式、列表函数和关联
+- 微分、积分、方程求解、线性代数
+- 通用的项重写系统和模式匹配
+- Rubi 符号积分规则
+
+### 许可证
+- `matheclipse-core`:LGPL-3.0
+- `matheclipse-gpl`:GPL-3.0
+- `matheclipse-io`:GPL-3.0
+
+---
+
+## 🤝 贡献
+
+欢迎贡献代码、报告问题或提出建议!
+
+1. Fork 本仓库
+2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
+3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
+4. 推送到分支 (`git push origin feature/AmazingFeature`)
+5. 开启 Pull Request
+
+详见 [CONTRIBUTING.md](CONTRIBUTING.md)
+
+---
+
+## 📄 许可证
+
+本项目采用 GPL-3.0 许可证 - 详见 [LICENSE](LICENSE) 文件
+
+---
+
+## 🙏 致谢
+
+- [Symja](https://github.com/axkr/symja_android_library) - 符号计算引擎
+- [KaTeX](https://katex.org/) - LaTeX 渲染引擎
+- [Mathematica](https://www.wolfram.com/mathematica/) - 设计灵感
+
+---
+
+## 📞 联系方式
+
+- 提交 [Issue](https://github.com/yourusername/ijs/issues)
+- 邮箱:nonvioce@qq.com
+
+---
+
+<div align="center">
+
+**如果这个项目对你有帮助,请给它一个 ⭐️!**
+
+Made with ❤️ by [nonvioce](https://github.com/yourusername)
+
+</div>

+ 88 - 0
SIGNING_GUIDE.md

@@ -0,0 +1,88 @@
+# Android 应用签名配置指南
+
+## 配置步骤
+
+### 1. 放置密钥文件
+
+将你的 `key.jks` 文件放在项目根目录:
+
+```
+项目根目录/
+├── key.jks          ← 放在这里
+├── app/
+├── gradle/
+└── ...
+```
+
+### 2. 创建配置文件
+
+复制 `keystore.properties.example` 为 `keystore.properties`:
+
+```bash
+cp keystore.properties keystore.properties
+```
+
+### 3. 填写配置信息
+
+编辑 `keystore.properties` 文件,填入你的实际信息:
+
+```properties
+storePassword=你的密钥库密码
+keyPassword=你的密钥密码
+keyAlias=你的密钥别名
+storeFile=key.jks
+```
+
+### 4. 构建发布版本
+
+```bash
+# Windows
+gradlew assembleRelease
+
+# Linux/Mac
+./gradlew assembleRelease
+```
+
+生成的 APK 文件位于:`app/build/outputs/apk/release/app-release.apk`
+
+## 安全提示
+
+⚠️ **重要:不要将以下文件提交到版本控制系统**
+
+- `key.jks` - 你的密钥文件
+- `keystore.properties` - 包含密码的配置文件
+
+这些文件已经添加到 `.gitignore` 中,确保它们不会被意外提交。
+
+## 如果没有密钥文件
+
+如果你还没有创建密钥文件,可以使用以下命令创建:
+
+```bash
+keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias
+```
+
+按照提示输入信息:
+- 密钥库密码
+- 密钥密码
+- 姓名、组织等信息
+
+## 备份密钥
+
+⚠️ **非常重要:请妥善保管你的密钥文件和密码**
+
+- 将 `key.jks` 文件备份到安全的地方
+- 记录密钥库密码、密钥密码和别名
+- 如果丢失密钥,将无法更新已发布的应用
+
+## 调试版本
+
+调试版本不需要配置签名,Android Studio 会自动使用调试密钥:
+
+```bash
+# Windows
+gradlew assembleDebug
+
+# Linux/Mac
+./gradlew assembleDebug
+```

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 124 - 0
app/build.gradle.kts

@@ -0,0 +1,124 @@
+import java.util.Properties
+import java.io.FileInputStream
+
+plugins {
+    alias(libs.plugins.android.application)
+}
+
+android {
+    namespace = "com.nonvioce.ijs"
+    compileSdk = 34
+
+    defaultConfig {
+        applicationId = "com.nonvioce.ijs"
+        minSdk = 24
+        targetSdk = 34
+        versionCode = 2
+        versionName = "1.1"
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        
+        // 支持多 dex,因为 Symja 库较大
+        multiDexEnabled = true
+    }
+
+    // 读取密钥配置
+    val keystorePropertiesFile = rootProject.file("keystore.properties")
+    val keystoreProperties = Properties()
+    if (keystorePropertiesFile.exists()) {
+        keystoreProperties.load(FileInputStream(keystorePropertiesFile))
+    }
+
+    signingConfigs {
+        create("release") {
+            if (keystorePropertiesFile.exists()) {
+                storeFile = file(keystoreProperties["storeFile"] as String)
+                storePassword = keystoreProperties["storePassword"] as String
+                keyAlias = keystoreProperties["keyAlias"] as String
+                keyPassword = keystoreProperties["keyPassword"] as String
+            }
+        }
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+            if (keystorePropertiesFile.exists()) {
+                signingConfig = signingConfigs.getByName("release")
+            }
+        }
+    }
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_11
+        targetCompatibility = JavaVersion.VERSION_11
+    }
+    buildFeatures {
+        viewBinding = true
+    }
+    
+    // 打包选项,避免重复文件
+    packaging {
+        resources {
+            excludes += setOf(
+                "META-INF/DEPENDENCIES",
+                "META-INF/LICENSE",
+                "META-INF/LICENSE.txt",
+                "META-INF/license.txt",
+                "META-INF/NOTICE",
+                "META-INF/NOTICE.txt",
+                "META-INF/notice.txt",
+                "META-INF/ASL2.0",
+                "META-INF/*.kotlin_module"
+            )
+            pickFirsts += setOf(
+                "graphml.xsd",
+                "xlink.xsd",
+                "viz.xsd",
+                "gexf.xsd"
+            )
+        }
+    }
+}
+
+dependencies {
+    implementation(libs.androidx.core.ktx)
+    implementation(libs.androidx.appcompat)
+    implementation(libs.material)
+    implementation(libs.androidx.constraintlayout)
+    
+    // Symja 库用于符号计算 - 使用最新的 3.1.0 稳定版本
+    // 核心模块 (LGPL 许可证)
+    implementation("org.matheclipse:matheclipse-core:3.1.0") {
+        // 排除 SNAPSHOT 依赖,使用稳定版本
+        exclude(group = "org.hipparchus")
+        exclude(group = "org.apfloat")
+        exclude(group = "org.jgrapht")
+    }
+    
+    // 手动添加稳定版本的依赖
+    implementation("org.hipparchus:hipparchus-core:3.1")
+    implementation("org.hipparchus:hipparchus-clustering:3.1")
+    implementation("org.hipparchus:hipparchus-fft:3.1")
+    implementation("org.hipparchus:hipparchus-fitting:3.1")
+    implementation("org.hipparchus:hipparchus-geometry:3.1")
+    implementation("org.hipparchus:hipparchus-ode:3.1")
+    implementation("org.hipparchus:hipparchus-optim:3.1")
+    implementation("org.hipparchus:hipparchus-stat:3.1")
+    implementation("org.apfloat:apfloat:1.14.0")
+    implementation("org.jgrapht:jgrapht-core:1.5.2")
+    implementation("org.jgrapht:jgrapht-io:1.5.2")
+    
+    // 可选:GPL 模块,包含额外的数学功能
+    // implementation("org.matheclipse:matheclipse-gpl:3.1.0")
+    
+    // 可选:IO 模块,用于更多输入输出功能
+    // implementation("org.matheclipse:matheclipse-io:3.1.0")
+    
+    testImplementation(libs.junit)
+    androidTestImplementation(libs.androidx.junit)
+    androidTestImplementation(libs.androidx.espresso.core)
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

BIN
app/release/app-release.apk


BIN
app/release/baselineProfiles/0/app-release.dm


BIN
app/release/baselineProfiles/1/app-release.dm


+ 37 - 0
app/release/output-metadata.json

@@ -0,0 +1,37 @@
+{
+  "version": 3,
+  "artifactType": {
+    "type": "APK",
+    "kind": "Directory"
+  },
+  "applicationId": "com.nonvioce.ijs",
+  "variantName": "release",
+  "elements": [
+    {
+      "type": "SINGLE",
+      "filters": [],
+      "attributes": [],
+      "versionCode": 2,
+      "versionName": "1.1",
+      "outputFile": "app-release.apk"
+    }
+  ],
+  "elementType": "File",
+  "baselineProfiles": [
+    {
+      "minApi": 28,
+      "maxApi": 30,
+      "baselineProfiles": [
+        "baselineProfiles/1/app-release.dm"
+      ]
+    },
+    {
+      "minApi": 31,
+      "maxApi": 2147483647,
+      "baselineProfiles": [
+        "baselineProfiles/0/app-release.dm"
+      ]
+    }
+  ],
+  "minSdkVersionForDexing": 24
+}

+ 24 - 0
app/src/androidTest/java/com/nonvioce/ijs/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.nonvioce.ijs
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.nonvioce.ijs", appContext.packageName)
+    }
+}

+ 42 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <!-- 保存图像到相册需要的权限 -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="28" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Ijs">
+        
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="${applicationId}.fileprovider"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths" />
+        </provider>
+        
+    </application>
+
+</manifest>

+ 1436 - 0
app/src/main/assets/console.css

@@ -0,0 +1,1436 @@
+/* Material Design 颜色变量 - 浅色主题 */
+:root {
+    --primary-color: #1976D2;
+    --primary-light: #BBDEFB;
+    --primary-dark: #0D47A1;
+    
+    --success-color: #388E3C;
+    --success-light: #C8E6C9;
+    
+    --error-color: #D32F2F;
+    --error-light: #FFCDD2;
+    
+    --warning-color: #F57C00;
+    --warning-light: #FFE0B2;
+    
+    --info-color: #7B1FA2;
+    --info-light: #E1BEE7;
+    
+    /* 终端颜色 - 浅色主题 */
+    --terminal-bg: #FAFAFA;
+    --terminal-fg: #212121;
+    --terminal-prompt: #1976D2;
+    --terminal-input-bg: #FFFFFF;
+    --terminal-output-bg: #F5F5F5;
+    --terminal-border: #E0E0E0;
+    --terminal-selection: #BBDEFB;
+    
+    /* 字体 */
+    --font-mono: 'Courier New', 'Consolas', 'Monaco', monospace;
+    --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
+}
+
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+/* Material Icons 全局样式 */
+.material-icons {
+    font-family: 'Material Icons';
+    font-weight: normal;
+    font-style: normal;
+    font-size: 24px;
+    display: inline-block;
+    line-height: 1;
+    text-transform: none;
+    letter-spacing: normal;
+    word-wrap: normal;
+    white-space: nowrap;
+    direction: ltr;
+    -webkit-font-smoothing: antialiased;
+    text-rendering: optimizeLegibility;
+    -moz-osx-font-smoothing: grayscale;
+    font-feature-settings: 'liga';
+}
+
+body {
+    font-family: var(--font-sans);
+    background: var(--terminal-bg);
+    color: var(--terminal-fg);
+    height: 100vh;
+    overflow: hidden;
+}
+
+/* 应用容器 */
+.app-container {
+    display: flex;
+    flex-direction: column;
+    height: 100vh;
+    background: var(--terminal-bg);
+}
+
+/* 顶部导航栏 */
+.top-nav {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 20px;
+    background: #FFFFFF;
+    border-bottom: 1px solid var(--terminal-border);
+    min-height: 56px;
+}
+
+.mdc-elevation--z4 {
+    box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 
+                0 4px 5px 0 rgba(0, 0, 0, 0.14), 
+                0 1px 10px 0 rgba(0, 0, 0, 0.12);
+}
+
+.nav-brand {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    font-size: 20px;
+    font-weight: 500;
+    color: var(--primary-color);
+}
+
+.nav-brand .material-icons {
+    font-size: 28px;
+}
+
+.brand-text {
+    font-weight: 500;
+    letter-spacing: 0.5px;
+}
+
+.nav-actions {
+    display: flex;
+    gap: 8px;
+}
+
+.nav-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 40px;
+    height: 40px;
+    border: none;
+    border-radius: 50%;
+    background: transparent;
+    color: var(--terminal-fg);
+    cursor: pointer;
+    transition: background 0.2s;
+}
+
+.nav-btn:hover {
+    background: rgba(0, 0, 0, 0.04);
+}
+
+.nav-btn:active {
+    background: rgba(0, 0, 0, 0.12);
+}
+
+.nav-btn .material-icons {
+    font-size: 24px;
+}
+
+/* 终端容器 */
+.terminal-container {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    background: var(--terminal-bg);
+    padding: 16px 20px;
+    position: relative;
+}
+
+/* 终端输出区域 */
+.terminal-output {
+    flex: 1;
+    overflow-y: auto;
+    overflow-x: hidden;
+    font-family: var(--font-mono);
+    font-size: 14px;
+    line-height: 1.6;
+    padding-bottom: 16px;
+}
+
+.terminal-output::-webkit-scrollbar {
+    width: 8px;
+}
+
+.terminal-output::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+.terminal-output::-webkit-scrollbar-thumb {
+    background: #BDBDBD;
+    border-radius: 4px;
+}
+
+.terminal-output::-webkit-scrollbar-thumb:hover {
+    background: #9E9E9E;
+}
+
+/* 终端输出行 */
+.terminal-line {
+    margin-bottom: 12px;
+    animation: fadeIn 0.2s ease-out;
+}
+
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+        transform: translateY(-4px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+/* 命令输入显示 */
+.command-line {
+    display: flex;
+    gap: 8px;
+    margin-bottom: 4px;
+    color: var(--terminal-fg);
+}
+
+.command-prompt {
+    color: var(--primary-color);
+    font-weight: 600;
+    flex-shrink: 0;
+    min-width: 50px;
+}
+
+.command-text {
+    color: var(--terminal-fg);
+    word-break: break-all;
+    flex: 1;
+}
+
+/* 结果输出 */
+.result-line {
+    display: flex;
+    gap: 8px;
+    padding: 12px 16px;
+    margin: 8px 0 8px 0;
+    background: #FFFFFF;
+    border-left: 3px solid var(--success-color);
+    border-radius: 4px;
+    color: var(--terminal-fg);
+    word-break: break-word;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.result-prompt {
+    color: var(--error-color);
+    font-weight: 600;
+    flex-shrink: 0;
+    min-width: 50px;
+}
+
+.result-text {
+    flex: 1;
+}
+
+/* 错误输出 */
+.error-line {
+    padding: 12px 16px;
+    margin: 8px 0 8px 0;
+    background: var(--error-light);
+    border-left: 3px solid var(--error-color);
+    border-radius: 4px;
+    color: var(--error-color);
+    word-break: break-word;
+}
+
+/* 信息输出 */
+.info-line {
+    padding: 12px 16px;
+    margin: 8px 0 8px 0;
+    background: var(--primary-light);
+    border-left: 3px solid var(--primary-color);
+    border-radius: 4px;
+    color: var(--primary-dark);
+    word-break: break-word;
+    white-space: pre-wrap;
+}
+
+/* 警告输出 */
+.warning-line {
+    padding: 12px 16px;
+    margin: 8px 0 8px 24px;
+    background: var(--warning-light);
+    border-left: 3px solid var(--warning-color);
+    border-radius: 4px;
+    color: #E65100;
+    word-break: break-word;
+}
+
+/* LaTeX 渲染输出 */
+.latex-line {
+    display: flex;
+    gap: 8px;
+    padding: 16px;
+    margin: 8px 0 8px 0;
+    background: #FFFFFF;
+    border-left: 3px solid var(--info-color);
+    border-radius: 4px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.latex-content {
+    flex: 1;
+}
+
+.latex-rendered {
+    text-align: center;
+    margin: 12px 0;
+    color: var(--terminal-fg);
+}
+
+.latex-source {
+    font-family: var(--font-mono);
+    font-size: 12px;
+    color: #616161;
+    background: #F5F5F5;
+    padding: 8px 12px;
+    border-radius: 4px;
+    margin-top: 12px;
+    word-break: break-all;
+}
+
+.latex-actions {
+    margin-top: 12px;
+    display: flex;
+    gap: 8px;
+}
+
+.latex-btn {
+    padding: 6px 12px;
+    border: none;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.latex-toggle {
+    background: var(--info-color);
+    color: #FFFFFF;
+}
+
+.latex-toggle:hover {
+    background: #6A1B9A;
+}
+
+.latex-copy {
+    background: var(--success-color);
+    color: #FFFFFF;
+}
+
+.latex-copy:hover {
+    background: #2E7D32;
+}
+
+.latex-source {
+    font-family: var(--font-mono);
+    font-size: 12px;
+    color: #616161;
+    background: #F5F5F5;
+    padding: 8px 12px;
+    border-radius: 4px;
+    margin-top: 12px;
+    display: none;
+    word-break: break-all;
+}
+
+.latex-actions {
+    margin-top: 12px;
+    display: flex;
+    gap: 8px;
+}
+
+.latex-btn {
+    padding: 6px 12px;
+    border: none;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.latex-toggle {
+    background: var(--info-color);
+    color: #FFFFFF;
+}
+
+.latex-toggle:hover {
+    background: #6A1B9A;
+}
+
+.latex-copy {
+    background: var(--success-color);
+    color: #FFFFFF;
+}
+
+.latex-copy:hover {
+    background: #2E7D32;
+}
+
+/* 终端输入行 */
+.terminal-input-line {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 12px 16px;
+    background: #FFFFFF;
+    border: 2px solid var(--terminal-border);
+    border-radius: 8px;
+    margin-top: 16px;
+    transition: border-color 0.2s;
+}
+
+.terminal-input-line:focus-within {
+    border-color: var(--primary-color);
+    box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
+}
+
+.terminal-prompt {
+    font-family: var(--font-mono);
+    font-size: 14px;
+    font-weight: 500;
+    color: var(--primary-color);
+    flex-shrink: 0;
+    user-select: none;
+}
+
+.terminal-input {
+    flex: 1;
+    border: none;
+    outline: none;
+    background: transparent;
+    font-family: var(--font-mono);
+    font-size: 14px;
+    color: var(--terminal-fg);
+    padding: 4px 0;
+}
+
+.terminal-input::placeholder {
+    color: #9E9E9E;
+}
+
+.terminal-input::selection {
+    background: var(--terminal-selection);
+}
+
+/* 自动补全列表 */
+.completion-list {
+    display: none;
+    position: fixed;
+    bottom: 80px;
+    left: 80px;
+    right: 40px;
+    max-height: 200px;
+    overflow-y: auto;
+    background: #FFFFFF;
+    border: 1px solid var(--terminal-border);
+    border-radius: 6px;
+    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.15);
+    z-index: 100;
+}
+
+.completion-list.show {
+    display: block;
+}
+
+.completion-item {
+    padding: 8px 12px;
+    cursor: pointer;
+    font-family: var(--font-mono);
+    font-size: 13px;
+    color: var(--terminal-fg);
+    border-bottom: 1px solid #F5F5F5;
+    transition: background 0.1s;
+}
+
+.completion-item:last-child {
+    border-bottom: none;
+}
+
+.completion-item:hover {
+    background: var(--primary-light);
+    color: var(--primary-dark);
+}
+
+.completion-item:active {
+    background: var(--primary-color);
+    color: #FFFFFF;
+}
+
+.completion-list::-webkit-scrollbar {
+    width: 6px;
+}
+
+.completion-list::-webkit-scrollbar-track {
+    background: #F5F5F5;
+}
+
+.completion-list::-webkit-scrollbar-thumb {
+    background: #BDBDBD;
+    border-radius: 3px;
+}
+
+/* 快捷键提示栏 */
+.shortcuts-bar {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    padding: 12px 20px;
+    background: #FFFFFF;
+    border-top: 1px solid var(--terminal-border);
+    gap: 8px;
+    overflow-x: auto;
+}
+
+.mdc-elevation--z2 {
+    box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 
+                0 2px 2px 0 rgba(0, 0, 0, 0.14), 
+                0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+
+.shortcuts-group {
+    display: flex;
+    gap: 8px;
+    flex-shrink: 0;
+}
+
+.shortcut-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 36px;
+    height: 36px;
+    padding: 0 12px;
+    border: 1px solid var(--terminal-border);
+    border-radius: 6px;
+    background: #FAFAFA;
+    color: var(--terminal-fg);
+    font-family: var(--font-mono);
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.shortcut-btn:hover {
+    background: var(--primary-light);
+    border-color: var(--primary-color);
+    color: var(--primary-dark);
+}
+
+.shortcut-btn:active {
+    transform: scale(0.95);
+}
+
+.shortcut-btn .material-icons {
+    font-size: 20px;
+    font-family: 'Material Icons';
+    font-weight: normal;
+    font-style: normal;
+    display: inline-block;
+    line-height: 1;
+    text-transform: none;
+    letter-spacing: normal;
+    word-wrap: normal;
+    white-space: nowrap;
+    direction: ltr;
+    -webkit-font-smoothing: antialiased;
+    text-rendering: optimizeLegibility;
+    -moz-osx-font-smoothing: grayscale;
+    font-feature-settings: 'liga';
+}
+
+.icon-symbol {
+    font-size: 18px;
+    font-weight: 500;
+    display: inline-block;
+    line-height: 1;
+}
+
+.shortcut-key {
+    font-family: var(--font-mono);
+}
+
+/* 帮助页面 */
+.help-page {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.5);
+    display: none;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+    animation: fadeIn 0.2s;
+}
+
+.help-page.show {
+    display: flex;
+}
+
+.help-container {
+    width: 90%;
+    max-width: 900px;
+    max-height: 90vh;
+    background: #FFFFFF;
+    border-radius: 12px;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    animation: slideUp 0.3s ease-out;
+}
+
+@keyframes slideUp {
+    from {
+        transform: translateY(50px);
+        opacity: 0;
+    }
+    to {
+        transform: translateY(0);
+        opacity: 1;
+    }
+}
+
+.help-header {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    padding: 12px 16px;
+    background: #FFB7C5;
+    color: #FFFFFF;
+    border-bottom: 1px solid #FF9FB0;
+}
+
+.close-help-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 36px;
+    height: 36px;
+    border: none;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.2);
+    color: #FFFFFF;
+    font-size: 20px;
+    cursor: pointer;
+    transition: background 0.2s;
+}
+
+.close-help-btn:hover {
+    background: rgba(255, 255, 255, 0.3);
+}
+
+.close-help-btn .icon-symbol {
+    font-size: 20px;
+}
+
+.help-content {
+    flex: 1;
+    overflow-y: auto;
+    padding: 0;
+}
+
+.help-content::-webkit-scrollbar {
+    width: 8px;
+}
+
+.help-content::-webkit-scrollbar-track {
+    background: #F5F5F5;
+}
+
+.help-content::-webkit-scrollbar-thumb {
+    background: #BDBDBD;
+    border-radius: 4px;
+}
+
+.help-table {
+    width: 100%;
+    border-collapse: collapse;
+    font-size: 14px;
+}
+
+.help-table thead {
+    position: sticky;
+    top: 0;
+    background: #FFB7C5;
+    color: #FFFFFF;
+    z-index: 10;
+}
+
+.help-table th {
+    padding: 12px 16px;
+    text-align: left;
+    font-weight: 500;
+    border-bottom: 2px solid #FF9FB0;
+}
+
+.help-table td {
+    padding: 10px 16px;
+    border-bottom: 1px solid var(--terminal-border);
+    vertical-align: top;
+}
+
+.help-table td:first-child {
+    color: #424242;
+    white-space: nowrap;
+    width: 140px;
+}
+
+.help-table td:nth-child(2) {
+    font-family: var(--font-mono);
+    font-size: 13px;
+    color: var(--terminal-fg);
+    word-break: break-all;
+}
+
+.help-table tbody tr:hover td {
+    background: #FFE4E9;
+}
+
+/* 插件面板 - 保持原样但调整颜色 */
+.plugin-panel {
+    position: fixed;
+    top: 0;
+    right: -100%;
+    width: 90%;
+    max-width: 500px;
+    height: 100vh;
+    background: #FFFFFF;
+    box-shadow: -4px 0 16px rgba(0, 0, 0, 0.2);
+    transition: right 0.3s ease-out;
+    z-index: 1000;
+    display: flex;
+    flex-direction: column;
+}
+
+.plugin-panel.show {
+    right: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 600px) {
+    .top-nav {
+        padding: 8px 16px;
+    }
+    
+    .nav-brand {
+        font-size: 18px;
+    }
+    
+    .nav-brand .material-icons {
+        font-size: 24px;
+    }
+    
+    .terminal-container {
+        padding: 12px 16px;
+    }
+    
+    .terminal-output {
+        font-size: 13px;
+    }
+    
+    .terminal-input {
+        font-size: 13px;
+    }
+    
+    .shortcuts-bar {
+        padding: 8px 16px;
+    }
+    
+    .help-container {
+        width: 95%;
+        max-height: 95vh;
+    }
+    
+    .help-content {
+        padding: 16px;
+    }
+}
+
+/* 欢迎消息样式 */
+.welcome-message {
+    padding: 20px;
+    margin: 16px 0;
+    background: linear-gradient(135deg, var(--primary-light) 0%, var(--info-light) 100%);
+    border-radius: 8px;
+    border-left: 4px solid var(--primary-color);
+}
+
+.welcome-message h2 {
+    color: var(--primary-dark);
+    font-size: 18px;
+    font-weight: 500;
+    margin-bottom: 12px;
+}
+
+.welcome-message p {
+    color: #424242;
+    line-height: 1.6;
+    margin: 8px 0;
+}
+
+.welcome-message code {
+    font-family: var(--font-mono);
+    background: rgba(255, 255, 255, 0.7);
+    padding: 2px 6px;
+    border-radius: 3px;
+    color: var(--primary-dark);
+}
+
+/* 插件面板样式 */
+.plugin-panel {
+    position: fixed;
+    top: 0;
+    right: -100%;
+    width: 90%;
+    max-width: 500px;
+    height: 100vh;
+    background: #FFF5F7;
+    box-shadow: -4px 0 16px rgba(255, 183, 197, 0.3);
+    transition: right 0.3s ease-out;
+    z-index: 1000;
+    display: flex;
+    flex-direction: column;
+}
+
+.plugin-panel.show {
+    right: 0;
+}
+
+.plugin-panel-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px 24px;
+    background: #FFB7C5;
+    color: #FFFFFF;
+    border-bottom: 1px solid #FF9FB0;
+}
+
+.plugin-panel-header h2 {
+    font-size: 20px;
+    font-weight: 500;
+}
+
+.close-btn {
+    width: 32px;
+    height: 32px;
+    border: none;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.2);
+    color: #FFFFFF;
+    font-size: 20px;
+    cursor: pointer;
+    transition: background 0.2s;
+}
+
+.close-btn:hover {
+    background: rgba(255, 255, 255, 0.3);
+}
+
+.plugin-panel-tabs {
+    display: flex;
+    background: #FFE4E9;
+    border-bottom: 1px solid #FFCDD6;
+}
+
+.plugin-tab {
+    flex: 1;
+    padding: 12px 16px;
+    border: none;
+    background: transparent;
+    color: #8B5A6B;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    border-bottom: 2px solid transparent;
+    transition: all 0.2s;
+}
+
+.plugin-tab:hover {
+    background: rgba(255, 183, 197, 0.2);
+}
+
+.plugin-tab.active {
+    color: #D84A6B;
+    border-bottom-color: #FFB7C5;
+    background: #FFF5F7;
+}
+
+.tab-content {
+    display: none;
+    flex: 1;
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding: 20px;
+}
+
+.tab-content.active {
+    display: flex;
+    flex-direction: column;
+}
+
+.plugin-list {
+    flex-direction: column;
+    gap: 12px;
+}
+
+.plugin-item {
+    display: flex;
+    gap: 16px;
+    padding: 16px;
+    background: #FFFFFF;
+    border: 1px solid #FFCDD6;
+    border-radius: 8px;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.plugin-item:hover {
+    background: #FFE4E9;
+    border-color: #FFB7C5;
+    transform: translateY(-2px);
+    box-shadow: 0 4px 8px rgba(255, 183, 197, 0.2);
+}
+
+.plugin-icon {
+    font-size: 32px;
+    flex-shrink: 0;
+}
+
+.plugin-info {
+    flex: 1;
+}
+
+.plugin-name {
+    font-size: 16px;
+    font-weight: 500;
+    color: #5A3A47;
+    margin-bottom: 4px;
+}
+
+.plugin-desc {
+    font-size: 13px;
+    color: #8B5A6B;
+    line-height: 1.4;
+}
+
+.plugin-command {
+    font-size: 12px;
+    color: #D84A6B;
+    margin-top: 4px;
+    font-family: var(--font-mono);
+}
+
+.no-plugins {
+    text-align: center;
+    padding: 40px 20px;
+    color: #C99AA8;
+    font-size: 14px;
+}
+
+/* 插件文档样式 */
+.plugin-docs {
+    font-size: 14px;
+    line-height: 1.6;
+}
+
+.doc-section {
+    margin-bottom: 24px;
+}
+
+.doc-section h3 {
+    color: var(--primary-color);
+    font-size: 18px;
+    font-weight: 500;
+    margin-bottom: 12px;
+}
+
+.doc-section h4 {
+    color: var(--terminal-fg);
+    font-size: 16px;
+    font-weight: 500;
+    margin: 16px 0 8px 0;
+}
+
+.doc-section h5 {
+    color: var(--terminal-fg);
+    font-size: 14px;
+    font-weight: 500;
+    margin: 12px 0 8px 0;
+}
+
+.doc-section p {
+    margin: 8px 0;
+    color: #424242;
+}
+
+.doc-section ul {
+    margin: 8px 0 8px 20px;
+}
+
+.doc-section li {
+    margin: 4px 0;
+    color: #424242;
+}
+
+.doc-section code {
+    font-family: var(--font-mono);
+    font-size: 13px;
+    background: #F5F5F5;
+    padding: 2px 6px;
+    border-radius: 3px;
+    color: var(--primary-dark);
+}
+
+.code-block {
+    font-family: var(--font-mono);
+    font-size: 12px;
+    background: #F5F5F5;
+    padding: 12px;
+    border-radius: 6px;
+    border-left: 3px solid var(--primary-color);
+    overflow-x: auto;
+    margin: 12px 0;
+    white-space: pre-wrap;
+    word-break: break-word;
+}
+
+.api-table {
+    width: 100%;
+    border-collapse: collapse;
+    margin: 12px 0;
+    font-size: 13px;
+}
+
+.api-table th,
+.api-table td {
+    padding: 8px 12px;
+    border: 1px solid var(--terminal-border);
+    text-align: left;
+}
+
+.api-table th {
+    background: var(--primary-light);
+    color: var(--primary-dark);
+    font-weight: 500;
+}
+
+.api-table tr:nth-child(even) {
+    background: #FAFAFA;
+}
+
+.api-table code {
+    background: rgba(25, 118, 210, 0.1);
+}
+
+/* 插件导入/分享样式 */
+.plugin-import {
+    flex-direction: column;
+    gap: 24px;
+}
+
+.import-section {
+    padding: 16px;
+    background: #FFFFFF;
+    border-radius: 8px;
+    border: 1px solid #FFCDD6;
+}
+
+.import-section h3 {
+    color: #D84A6B;
+    font-size: 16px;
+    font-weight: 500;
+    margin-bottom: 8px;
+}
+
+.import-section p {
+    color: #8B5A6B;
+    font-size: 13px;
+    margin-bottom: 12px;
+}
+
+.share-select {
+    width: 100%;
+    padding: 10px 12px;
+    border: 1px solid #FFCDD6;
+    border-radius: 6px;
+    background: #FFFFFF;
+    font-size: 14px;
+    margin-bottom: 12px;
+    cursor: pointer;
+    transition: border-color 0.2s;
+}
+
+.share-select:focus {
+    outline: none;
+    border-color: #FFB7C5;
+}
+
+.share-textarea {
+    width: 100%;
+    min-height: 150px;
+    padding: 12px;
+    border: 1px solid #FFCDD6;
+    border-radius: 6px;
+    font-family: var(--font-mono);
+    font-size: 12px;
+    resize: vertical;
+    margin-bottom: 12px;
+    transition: border-color 0.2s;
+}
+
+.share-textarea:focus {
+    outline: none;
+    border-color: #FFB7C5;
+}
+
+.action-btn {
+    padding: 10px 20px;
+    border: none;
+    border-radius: 6px;
+    background: #FFB7C5;
+    color: #FFFFFF;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+    margin-right: 8px;
+}
+
+.action-btn:hover {
+    background: #FF9FB0;
+    transform: translateY(-1px);
+    box-shadow: 0 2px 4px rgba(255, 183, 197, 0.3);
+}
+
+.action-btn.secondary {
+    background: #FFC9D4;
+}
+
+.action-btn.secondary:hover {
+    background: #FFB7C5;
+}
+
+.action-btn.danger {
+    background: #FF6B8A;
+}
+
+.action-btn.danger:hover {
+    background: #FF5278;
+}
+
+.plugin-path {
+    font-size: 13px;
+    color: #424242;
+    line-height: 1.6;
+}
+
+.plugin-path code {
+    font-family: var(--font-mono);
+    font-size: 12px;
+    background: #F5F5F5;
+    padding: 4px 8px;
+    border-radius: 3px;
+    display: block;
+    margin: 8px 0;
+    word-break: break-all;
+}
+
+/* 插件对话框样式 */
+.plugin-dialog {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.5);
+    display: none;
+    align-items: center;
+    justify-content: center;
+    z-index: 2000;
+    animation: fadeIn 0.2s;
+}
+
+.plugin-dialog.show {
+    display: flex;
+}
+
+.plugin-dialog-content {
+    width: 90%;
+    max-width: 600px;
+    max-height: 90vh;
+    background: #FFFFFF;
+    border-radius: 12px;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    animation: slideUp 0.3s ease-out;
+}
+
+.plugin-dialog-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px 24px;
+    background: #FFB7C5;
+    color: #FFFFFF;
+}
+
+.plugin-dialog-header h3 {
+    font-size: 20px;
+    font-weight: 500;
+}
+
+.plugin-dialog-desc {
+    padding: 16px 24px;
+    color: #8B5A6B;
+    font-size: 14px;
+    border-bottom: 1px solid #FFCDD6;
+    background: #FFF5F7;
+}
+
+.plugin-inputs {
+    flex: 1;
+    overflow-y: auto;
+    padding: 20px 24px;
+}
+
+.input-group {
+    margin-bottom: 16px;
+}
+
+.input-group label {
+    display: block;
+    font-size: 14px;
+    font-weight: 500;
+    color: var(--terminal-fg);
+    margin-bottom: 6px;
+}
+
+.input-group input {
+    width: 100%;
+    padding: 10px 12px;
+    border: 1px solid var(--terminal-border);
+    border-radius: 6px;
+    font-size: 14px;
+    transition: border-color 0.2s;
+}
+
+.input-group input:focus {
+    outline: none;
+    border-color: var(--primary-color);
+    box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
+}
+
+.plugin-dialog-actions {
+    display: flex;
+    gap: 12px;
+    padding: 16px 24px;
+    background: #FAFAFA;
+    border-top: 1px solid var(--terminal-border);
+}
+
+.execute-btn {
+    flex: 1;
+    padding: 12px 24px;
+    border: none;
+    border-radius: 6px;
+    background: var(--primary-color);
+    color: #FFFFFF;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.execute-btn:hover {
+    background: var(--primary-dark);
+    transform: translateY(-1px);
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.cancel-btn {
+    flex: 1;
+    padding: 12px 24px;
+    border: 1px solid var(--terminal-border);
+    border-radius: 6px;
+    background: #FFFFFF;
+    color: var(--terminal-fg);
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.cancel-btn:hover {
+    background: #F5F5F5;
+}
+
+/* 绘图对话框样式 */
+.plot-dialog-content {
+    max-width: 900px;
+    max-height: 95vh;
+}
+
+.plot-info {
+    padding: 12px 24px;
+    background: #FFE4E9;
+    color: #8B5A6B;
+    font-family: var(--font-mono);
+    font-size: 12px;
+    border-bottom: 1px solid #FFCDD6;
+    text-align: center;
+}
+
+.plot-container {
+    padding: 20px;
+    background: #FFF5F7;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+#plotCanvas {
+    max-width: 100%;
+    height: auto;
+    border: 2px solid #FFCDD6;
+    border-radius: 8px;
+    box-shadow: 0 4px 12px rgba(255, 183, 197, 0.2);
+}
+
+/* 插件信息输出(通过 Symja.print 输出的内容)*/
+.plugin-info-line {
+    padding: 10px 16px;
+    margin: 4px 0 4px 24px;
+    background: #FFF9E6;
+    border-left: 3px solid #FFC107;
+    border-radius: 4px;
+    color: #5D4037;
+    font-size: 13px;
+    word-break: break-word;
+    white-space: pre-wrap;
+}
+
+/* 插件结果输出(插件的返回值)*/
+.plugin-line {
+    display: flex;
+    gap: 8px;
+    padding: 12px 16px;
+    margin: 8px 0 8px 0;
+    background: #E8F5E9;
+    border-left: 3px solid #4CAF50;
+    border-radius: 4px;
+    color: #1B5E20;
+    word-break: break-word;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+    font-weight: 500;
+}
+
+/* 反馈样式 */
+.feedback-line {
+    display: flex;
+    gap: 12px;
+    padding: 12px 16px;
+    background: linear-gradient(135deg, #E3F2FD 0%, #F3E5F5 100%);
+    border-radius: 6px;
+    border-left: 3px solid var(--primary-color);
+    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+    max-width: 100%;
+    overflow: hidden;
+}
+
+.feedback-icon {
+    font-size: 24px;
+    flex-shrink: 0;
+    line-height: 1;
+}
+
+.feedback-content {
+    flex: 1;
+    min-width: 0;
+}
+
+.feedback-title {
+    font-size: 15px;
+    font-weight: 500;
+    color: var(--primary-dark);
+    margin-bottom: 4px;
+}
+
+.feedback-text {
+    font-size: 13px;
+    color: #424242;
+    line-height: 1.4;
+    margin-bottom: 8px;
+}
+
+.feedback-email-container {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 12px;
+    background: #FFFFFF;
+    border-radius: 4px;
+    border: 1px solid var(--terminal-border);
+    max-width: 100%;
+    overflow: hidden;
+}
+
+.feedback-email {
+    flex: 1;
+    font-family: var(--font-mono);
+    font-size: 13px;
+    color: var(--primary-color);
+    font-weight: 500;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.feedback-copy-btn {
+    padding: 6px 12px;
+    border: none;
+    border-radius: 4px;
+    background: var(--primary-color);
+    color: #FFFFFF;
+    font-size: 12px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: all 0.2s;
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.feedback-copy-btn:hover {
+    background: var(--primary-dark);
+    transform: translateY(-1px);
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.feedback-copy-btn:active {
+    transform: translateY(0);
+}

+ 307 - 0
app/src/main/assets/console.html

@@ -0,0 +1,307 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>某人的计算器</title>
+    
+    <!-- KaTeX for LaTeX rendering (本地) -->
+    <link rel="stylesheet" href="katex/katex.min.css">
+    <script defer src="katex/katex.min.js"></script>
+    
+    <link rel="stylesheet" href="console.css">
+</head>
+<body>
+    <!-- 主容器 -->
+    <div class="app-container">
+        <!-- 终端容器 -->
+        <div class="terminal-container">
+            <!-- 终端输出区域 -->
+            <div id="output" class="terminal-output"></div>
+            
+            <!-- 终端输入区域 -->
+            <div class="terminal-input-line">
+                <span class="terminal-prompt" id="inputPrompt">[1]:</span>
+                <input type="text" 
+                       id="input" 
+                       class="terminal-input" 
+                       placeholder="输入命令或表达式..." 
+                       autofocus 
+                       autocomplete="off"
+                       spellcheck="false">
+            </div>
+            
+            <!-- 自动补全列表 -->
+            <div id="completionList" class="completion-list"></div>
+        </div>
+
+        <!-- 快捷键提示栏 -->
+        <div class="shortcuts-bar mdc-elevation--z2">
+            <div class="shortcuts-group">
+                <button class="shortcut-btn" data-action="bracket" data-value="[" title="插入 [">
+                    <span class="shortcut-key">[</span>
+                </button>
+                <button class="shortcut-btn" data-action="bracket" data-value="]" title="插入 ]">
+                    <span class="shortcut-key">]</span>
+                </button>
+                <button class="shortcut-btn" data-action="bracket" data-value="{" title="插入 {">
+                    <span class="shortcut-key">{</span>
+                </button>
+                <button class="shortcut-btn" data-action="bracket" data-value="}" title="插入 }">
+                    <span class="shortcut-key">}</span>
+                </button>
+                <button class="shortcut-btn" data-action="insert" data-value="->" title="插入 →">
+                    <span class="shortcut-key">→</span>
+                </button>
+                <button class="shortcut-btn" data-action="insert" data-value="/." title="插入 /.">
+                    <span class="shortcut-key">/.</span>
+                </button>
+            </div>
+            <div class="shortcuts-group">
+                <button class="shortcut-btn" data-action="history-up" title="上一条命令">
+                    <span class="icon-symbol">↑</span>
+                </button>
+                <button class="shortcut-btn" data-action="history-down" title="下一条命令">
+                    <span class="icon-symbol">↓</span>
+                </button>
+            </div>
+        </div>
+    </div>
+
+    <!-- 帮助页面 -->
+    <div id="helpPage" class="help-page">
+        <div class="help-container">
+            <div class="help-header">
+                <button id="closeHelp" class="close-help-btn">
+                    <span class="icon-symbol">✕</span>
+                </button>
+            </div>
+            <div class="help-content" id="helpContent">
+                <!-- 帮助内容将通过 JavaScript 动态加载 -->
+            </div>
+        </div>
+    </div>
+
+    <!-- 插件面板 -->
+    <div id="pluginPanel" class="plugin-panel">
+        <div class="plugin-panel-header">
+            <h2>🔌 插件管理</h2>
+            <button id="closePluginPanel" class="close-btn">✕</button>
+        </div>
+        
+        <!-- 插件标签页 -->
+        <div class="plugin-panel-tabs">
+            <button class="plugin-tab active" data-tab="list">插件列表</button>
+            <button class="plugin-tab" data-tab="docs">开发文档</button>
+            <button class="plugin-tab" data-tab="import">导入/分享</button>
+        </div>
+        
+        <!-- 插件列表 -->
+        <div id="pluginList" class="plugin-list tab-content active" data-content="list"></div>
+        
+        <!-- 开发文档 -->
+        <div class="plugin-docs tab-content" data-content="docs">
+            <div class="doc-section">
+                <h3>📖 插件开发指南 (v1.1)</h3>
+                
+                <h4>基本结构</h4>
+                <div class="code-block">{
+  "id": "circle_area",
+  "name": "圆面积计算器",
+  "icon": "⭕",
+  "command": "circle",
+  "inputs": [
+    {
+      "name": "radius",
+      "label": "半径",
+      "defaultValue": "1"
+    }
+  ],
+  "jsCode": "function calculate(inputs, Symja) {\n  Symja.print('计算圆面积');\n  return `Pi*${inputs.radius}^2`;\n}"
+}</div>
+            </div>
+            
+            <div class="doc-section">
+                <h4>🎉 v1.1 核心特性</h4>
+                <ul>
+                    <li><strong>输入自动转换</strong> - 用户输入自动转换为 Symja 可运算类型</li>
+                    <li><strong>Symja.print()</strong> - 输出说明文本,不影响返回值</li>
+                    <li><strong>命令执行</strong> - <code>circle[5]</code> 直接执行插件</li>
+                    <li><strong>列表参数</strong> - <code>cmd[{1,2}, {3,4}]</code> 支持列表作为参数</li>
+                    <li><strong>% 引用</strong> - 返回值可用 <code>%</code> 引用</li>
+                    <li><strong>自动补全</strong> - 输入命令时自动提示参数</li>
+                </ul>
+            </div>
+            
+            <div class="doc-section">
+                <h4>代码模板</h4>
+                <div class="code-block">function calculate(inputs, Symja) {
+  // 1. 获取输入(已自动转换)
+  const param = inputs.param;
+  
+  // 2. 验证输入
+  if (!param) {
+    throw new Error('请输入参数');
+  }
+  
+  // 3. 输出说明(可选)
+  Symja.print('正在计算...');
+  
+  // 4. 执行计算
+  const result = Symja.eval(`Func[${param}]`);
+  
+  // 5. 返回 Symja 表达式
+  return result;
+}</div>
+            </div>
+            
+            <div class="doc-section">
+                <h4>常用 API</h4>
+                <table class="api-table">
+                    <tr>
+                        <th>分类</th>
+                        <th>方法</th>
+                    </tr>
+                    <tr>
+                        <td>输入输出</td>
+                        <td><code>Symja.print(msg)</code></td>
+                    </tr>
+                    <tr>
+                        <td>基础</td>
+                        <td><code>Symja.eval(expr)</code></td>
+                    </tr>
+                    <tr>
+                        <td>微积分</td>
+                        <td><code>differentiate(expr, var)</code></td>
+                    </tr>
+                    <tr>
+                        <td></td>
+                        <td><code>integrate(expr, var)</code></td>
+                    </tr>
+                    <tr>
+                        <td>代数</td>
+                        <td><code>solve(eq, var)</code></td>
+                    </tr>
+                    <tr>
+                        <td></td>
+                        <td><code>simplify(expr)</code></td>
+                    </tr>
+                    <tr>
+                        <td></td>
+                        <td><code>expand(expr)</code></td>
+                    </tr>
+                    <tr>
+                        <td></td>
+                        <td><code>factor(expr)</code></td>
+                    </tr>
+                    <tr>
+                        <td>矩阵</td>
+                        <td><code>matrix.det(m)</code></td>
+                    </tr>
+                    <tr>
+                        <td></td>
+                        <td><code>matrix.inverse(m)</code></td>
+                    </tr>
+                    <tr>
+                        <td>数论</td>
+                        <td><code>gcd(a, b)</code></td>
+                    </tr>
+                    <tr>
+                        <td>数值</td>
+                        <td><code>numeric(expr, prec)</code></td>
+                    </tr>
+                </table>
+                <p style="font-size: 13px; color: #616161; margin-top: 8px;">
+                    更多 API 请参考完整文档或使用 <code>help</code> 命令
+                </p>
+            </div>
+            
+            <div class="doc-section">
+                <h4>示例插件</h4>
+                <div class="code-block">{
+  "id": "deriv",
+  "name": "导数计算器",
+  "icon": "📈",
+  "command": "deriv",
+  "inputs": [
+    {"name": "func", "label": "函数", "defaultValue": "x^2"},
+    {"name": "var", "label": "变量", "defaultValue": "x"}
+  ],
+  "jsCode": "function calculate(inputs, Symja) {\n  Symja.print(`对 ${inputs.func} 求导`);\n  return Symja.differentiate(inputs.func, inputs.var);\n}"
+}</div>
+                <p style="font-size: 13px; color: #616161; margin-top: 8px;">
+                    <strong>使用:</strong><code>deriv[x^3, x]</code> 或 <code>deriv[{x^2,x^3}, x]</code>(列表参数)
+                </p>
+            </div>
+            
+            <div class="doc-section">
+                <h4>⚠️ 重要规则</h4>
+                <ul>
+                    <li>函数签名:<code>function calculate(inputs, Symja) { ... }</code></li>
+                    <li>输入自动转换为 Symja 可运算类型</li>
+                    <li>必须返回有效的 Symja 表达式</li>
+                    <li>使用 <code>Symja.print()</code> 输出说明</li>
+                    <li>使用 <code>throw new Error()</code> 抛出错误</li>
+                    <li>返回值可用 <code>%</code> 引用</li>
+                    <li>列表参数:<code>cmd[{1,2}, {3,4}]</code> 有 2 个参数</li>
+                </ul>
+            </div>
+        </div>
+        
+        <!-- 导入/分享 -->
+        <div class="plugin-import tab-content" data-content="import">
+            <div class="import-section">
+                <h3>📤 分享插件</h3>
+                <p>选择一个插件生成分享代码</p>
+                <select id="sharePluginSelect" class="share-select">
+                    <option value="">选择插件...</option>
+                </select>
+                <button id="generateShareCode" class="action-btn">生成分享码</button>
+                <textarea id="shareCodeOutput" class="share-textarea" readonly placeholder="分享码将显示在这里..."></textarea>
+                <button id="copyShareCode" class="action-btn secondary" style="display:none;">复制分享码</button>
+            </div>
+            
+            <div class="import-section">
+                <h3>📥 导入插件</h3>
+                <p>粘贴插件分享码或 JSON 代码</p>
+                <textarea id="importCodeInput" class="share-textarea" placeholder="粘贴插件代码..."></textarea>
+                <button id="importPlugin" class="action-btn">导入插件</button>
+            </div>
+            
+            <div class="import-section">
+                <h3>🗑️ 管理插件</h3>
+                <p>删除不需要的插件</p>
+                <select id="deletePluginSelect" class="share-select">
+                    <option value="">选择要删除的插件...</option>
+                </select>
+                <button id="deletePlugin" class="action-btn danger">删除插件</button>
+            </div>
+            
+            <div class="import-section">
+                <h3>📁 插件位置</h3>
+                <p class="plugin-path">插件文件保存在:</p>
+                <textarea class="share-textarea" readonly style="min-height: 60px; font-size: 12px;">/storage/emulated/0/Android/data/com.nonvioce.ijs/files/plugins/</textarea>
+                <p style="font-size: 13px; color: #616161; margin-top: 8px;">你可以直接在该目录创建 .json 文件来添加插件</p>
+            </div>
+        </div>
+    </div>
+
+    <!-- 插件对话框 -->
+    <div id="pluginDialog" class="plugin-dialog">
+        <div class="plugin-dialog-content">
+            <div class="plugin-dialog-header">
+                <h3 id="pluginDialogTitle"></h3>
+                <button id="closePluginDialog" class="close-btn">✕</button>
+            </div>
+            <p id="pluginDialogDesc" class="plugin-dialog-desc"></p>
+            <div id="pluginInputs" class="plugin-inputs"></div>
+            <div class="plugin-dialog-actions">
+                <button id="executePlugin" class="execute-btn">执行</button>
+                <button id="cancelPlugin" class="cancel-btn">取消</button>
+            </div>
+        </div>
+    </div>
+
+    <script src="console.js"></script>
+</body>
+</html>

+ 1759 - 0
app/src/main/assets/console.js

@@ -0,0 +1,1759 @@
+// 控制台交互逻辑
+class SymjaConsole {
+    constructor() {
+        this.output = document.getElementById('output');
+        this.input = document.getElementById('input');
+        this.inputPrompt = document.getElementById('inputPrompt');
+        this.completionList = document.getElementById('completionList');
+        this.helpBtn = document.getElementById('helpBtn');
+        this.pluginsBtn = document.getElementById('pluginsBtn');
+        this.settingsBtn = document.getElementById('settingsBtn');
+        
+        // 历史记录
+        this.history = this.loadHistory();
+        this.historyIndex = this.history.length;
+        this.tempInput = '';
+        
+        // 插件相关
+        this.plugins = [];
+        this.currentPlugin = null;
+        
+        // 自动补全相关
+        this.completions = [];
+        this.completionIndex = -1;
+        
+        // 变量追踪
+        this.variables = new Set();
+        
+        // 执行计数器
+        this.executionCount = this.loadExecutionCount();
+
+        this.init();
+        this.loadPlugins();
+        this.refreshPluginList(); // 初始化时刷新插件列表
+        this.showWelcome();
+        this.updatePrompt();
+    }
+
+    init() {
+        // 输入框事件
+        this.input.addEventListener('keydown', (e) => this.handleKeyDown(e));
+        this.input.addEventListener('input', () => this.handleInput());
+        
+        // 快捷键工具栏
+        this.initShortcutsBar();
+        
+        // 帮助页面关闭按钮
+        document.getElementById('closeHelp')?.addEventListener('click', () => {
+            this.hideHelp();
+        });
+
+        // 插件面板事件
+        document.getElementById('closePluginPanel')?.addEventListener('click', () => {
+            this.hidePluginPanel();
+        });
+
+        document.getElementById('closePluginDialog')?.addEventListener('click', () => {
+            this.hidePluginDialog();
+        });
+
+        document.getElementById('cancelPlugin')?.addEventListener('click', () => {
+            this.hidePluginDialog();
+        });
+
+        document.getElementById('executePlugin')?.addEventListener('click', () => {
+            this.executeCurrentPlugin();
+        });
+    }
+
+    initShortcutsBar() {
+        const buttons = document.querySelectorAll('.shortcut-btn');
+        buttons.forEach(btn => {
+            btn.addEventListener('click', (e) => {
+                e.preventDefault();
+                const action = btn.dataset.action;
+                const value = btn.dataset.value;
+                
+                switch(action) {
+                    case 'bracket':
+                    case 'insert':
+                        this.insertText(value);
+                        break;
+                    case 'history-up':
+                        this.navigateHistory(-1);
+                        break;
+                    case 'history-down':
+                        this.navigateHistory(1);
+                        break;
+                }
+                
+                // 保持输入框焦点
+                this.input.focus();
+            });
+        });
+        
+        // 初始化插件标签页
+        this.initPluginTabs();
+        
+        // 初始化插件分享功能
+        this.initPluginShare();
+    }
+
+    initPluginTabs() {
+        const tabs = document.querySelectorAll('.plugin-tab');
+        const contents = document.querySelectorAll('.tab-content');
+        
+        tabs.forEach(tab => {
+            tab.addEventListener('click', () => {
+                const targetTab = tab.dataset.tab;
+                
+                // 切换标签激活状态
+                tabs.forEach(t => t.classList.remove('active'));
+                tab.classList.add('active');
+                
+                // 切换内容显示
+                contents.forEach(content => {
+                    if (content.dataset.content === targetTab) {
+                        content.classList.add('active');
+                    } else {
+                        content.classList.remove('active');
+                    }
+                });
+                
+                // 如果切换到导入/分享标签,更新插件列表
+                if (targetTab === 'import') {
+                    this.updatePluginSelects();
+                }
+            });
+        });
+    }
+
+    initPluginShare() {
+        // 使用事件委托,在document上监听点击事件
+        document.addEventListener('click', (e) => {
+            const target = e.target;
+            
+            // 生成分享码
+            if (target.id === 'generateShareCode') {
+                console.log('generateShareCode clicked');
+                e.preventDefault();
+                this.generateShareCode();
+            }
+            
+            // 复制分享码
+            if (target.id === 'copyShareCode') {
+                console.log('copyShareCode clicked');
+                e.preventDefault();
+                this.copyShareCode();
+            }
+            
+            // 导入插件
+            if (target.id === 'importPlugin') {
+                console.log('importPlugin clicked');
+                e.preventDefault();
+                this.importPlugin();
+            }
+            
+            // 删除插件
+            if (target.id === 'deletePlugin') {
+                console.log('deletePlugin clicked');
+                e.preventDefault();
+                this.deletePluginConfirm();
+            }
+        });
+    }
+
+    updatePluginSelects() {
+        const shareSelect = document.getElementById('sharePluginSelect');
+        const deleteSelect = document.getElementById('deletePluginSelect');
+        
+        if (shareSelect) {
+            shareSelect.innerHTML = '<option value="">选择插件...</option>';
+            this.plugins.forEach((plugin, index) => {
+                const option = document.createElement('option');
+                option.value = index;
+                option.textContent = `${plugin.icon} ${plugin.name}`;
+                shareSelect.appendChild(option);
+            });
+        }
+        
+        if (deleteSelect) {
+            deleteSelect.innerHTML = '<option value="">选择要删除的插件...</option>';
+            this.plugins.forEach((plugin, index) => {
+                const option = document.createElement('option');
+                option.value = index;
+                option.textContent = `${plugin.icon} ${plugin.name}`;
+                deleteSelect.appendChild(option);
+            });
+        }
+    }
+
+    generateShareCode() {
+        const select = document.getElementById('sharePluginSelect');
+        const output = document.getElementById('shareCodeOutput');
+        const copyBtn = document.getElementById('copyShareCode');
+        
+        if (!select || !output) return;
+        
+        const index = parseInt(select.value);
+        if (isNaN(index) || index < 0 || index >= this.plugins.length) {
+            this.addOutput('❌ 请先选择一个插件', 'error');
+            this.scrollToBottom();
+            return;
+        }
+        
+        const plugin = this.plugins[index];
+        const shareCode = JSON.stringify(plugin, null, 2);
+        
+        output.value = shareCode;
+        if (copyBtn) copyBtn.style.display = 'block';
+        
+        this.addOutput(`✅ 已生成 "${plugin.name}" 的分享码`, 'help');
+        this.scrollToBottom();
+    }
+
+    copyShareCode() {
+        const output = document.getElementById('shareCodeOutput');
+        if (!output || !output.value) return;
+        
+        output.select();
+        document.execCommand('copy');
+        
+        this.addOutput('📋 分享码已复制到剪贴板', 'help');
+        this.scrollToBottom();
+    }
+
+    importPlugin() {
+        const input = document.getElementById('importCodeInput');
+        if (!input || !input.value.trim()) {
+            this.addOutput('❌ 请粘贴插件代码', 'error');
+            this.scrollToBottom();
+            return;
+        }
+        
+        try {
+            const pluginData = JSON.parse(input.value);
+            
+            // 验证插件格式
+            if (!pluginData.id || !pluginData.name || !pluginData.jsCode) {
+                throw new Error('插件格式不正确,缺少必要字段');
+            }
+            
+            // 调用 Android 接口保存插件
+            try {
+                const result = Android.savePlugin(JSON.stringify(pluginData));
+                
+                // 重新加载插件
+                this.loadPlugins();
+                this.updatePluginSelects();
+                
+                // 刷新插件列表显示
+                this.refreshPluginList();
+                
+                // 显示成功消息
+                this.addOutput(`✅ ${result}`, 'help');
+                this.addOutput(`📦 插件 "${pluginData.name}" 已添加到列表`, 'help');
+                
+                // 清空输入
+                input.value = '';
+            } catch (e) {
+                this.addOutput(`❌ 保存失败: ${e.message}`, 'error');
+            }
+            
+            this.scrollToBottom();
+        } catch (e) {
+            this.addOutput(`❌ 解析失败: ${e.message}`, 'error');
+            this.scrollToBottom();
+        }
+    }
+
+    deletePluginConfirm() {
+        const select = document.getElementById('deletePluginSelect');
+        if (!select) {
+            console.error('deletePluginSelect element not found');
+            return;
+        }
+        
+        console.log('Select value:', select.value);
+        console.log('Plugins length:', this.plugins.length);
+        
+        const index = parseInt(select.value);
+        if (isNaN(index) || index < 0 || index >= this.plugins.length) {
+            this.addOutput('❌ 请先选择要删除的插件', 'error');
+            this.scrollToBottom();
+            return;
+        }
+        
+        const plugin = this.plugins[index];
+        console.log('Deleting plugin:', plugin.name, 'ID:', plugin.id);
+        
+        // 使用自定义确认对话框或直接删除
+        const confirmMessage = `确定要删除插件 "${plugin.name}" 吗?`;
+        console.log('Showing confirm dialog:', confirmMessage);
+        
+        let userConfirmed = false;
+        try {
+            userConfirmed = confirm(confirmMessage);
+            console.log('User confirmed:', userConfirmed);
+        } catch (e) {
+            console.error('Confirm dialog error:', e);
+            // 如果confirm失败,直接删除
+            userConfirmed = true;
+        }
+        
+        if (userConfirmed) {
+            console.log('Proceeding with deletion...');
+            try {
+                const result = Android.deletePlugin(plugin.id);
+                console.log('Delete result:', result);
+                this.addOutput(`✅ ${result}`, 'help');
+                
+                // 重新加载插件
+                this.loadPlugins();
+                this.updatePluginSelects();
+                
+                // 刷新插件列表显示
+                this.refreshPluginList();
+            } catch (e) {
+                console.error('Delete error:', e);
+                this.addOutput(`❌ 删除失败: ${e.message}`, 'error');
+            }
+            
+            this.scrollToBottom();
+        } else {
+            console.log('User cancelled deletion');
+        }
+    }
+
+    refreshPluginList() {
+        const list = document.getElementById('pluginList');
+        console.log('refreshPluginList called, list element:', list);
+        console.log('Current plugins count:', this.plugins.length);
+        
+        if (!list) return;
+        
+        list.innerHTML = '';
+        
+        if (this.plugins.length === 0) {
+            console.log('No plugins, showing empty message');
+            list.innerHTML = '<div class="no-plugins">暂无插件</div>';
+        } else {
+            console.log('Rendering', this.plugins.length, 'plugins');
+            this.plugins.forEach(plugin => {
+                const item = document.createElement('div');
+                item.className = 'plugin-item';
+                const commandInfo = plugin.command ? `<div class="plugin-command">命令: ${plugin.command}</div>` : '';
+                item.innerHTML = `
+                    <div class="plugin-icon">${plugin.icon}</div>
+                    <div class="plugin-info">
+                        <div class="plugin-name">${plugin.name}</div>
+                        <div class="plugin-desc">${plugin.description}</div>
+                        ${commandInfo}
+                    </div>
+                `;
+                item.addEventListener('click', () => this.openPlugin(plugin));
+                list.appendChild(item);
+            });
+            console.log('Finished rendering plugins, list children:', list.children.length);
+        }
+    }
+
+    insertText(text) {
+        const start = this.input.selectionStart;
+        const end = this.input.selectionEnd;
+        const value = this.input.value;
+        
+        // 插入文本
+        this.input.value = value.substring(0, start) + text + value.substring(end);
+        
+        // 设置光标位置
+        const newPos = start + text.length;
+        this.input.setSelectionRange(newPos, newPos);
+        
+        // 触发输入事件
+        this.handleInput();
+    }
+
+    handleKeyDown(e) {
+        if (e.key === 'Enter') {
+            e.preventDefault();
+            this.hideCompletionList();
+            this.evaluate();
+        } else if (e.key === 'ArrowUp') {
+            e.preventDefault();
+            this.hideCompletionList();
+            this.navigateHistory(-1);
+        } else if (e.key === 'ArrowDown') {
+            e.preventDefault();
+            this.hideCompletionList();
+            this.navigateHistory(1);
+        } else if (e.key === 'Escape') {
+            this.input.value = '';
+            this.completions = [];
+            this.completionIndex = -1;
+            this.hideCompletionList();
+        }
+    }
+
+    handleInput() {
+        // 更新自动补全列表
+        console.log('handleInput called, value:', this.input.value);
+        this.updateCompletionList();
+    }
+
+    showWelcome() {
+        // 不显示欢迎消息
+    }
+
+    evaluate() {
+        let expression = this.input.value.trim();
+        
+        if (!expression) {
+            return;
+        }
+
+        // 处理特殊命令
+        if (this.handleSpecialCommand(expression)) {
+            // 特殊命令也要添加到历史记录
+            this.addToHistory(expression);
+            this.input.value = '';
+            return;
+        }
+
+        // 添加到历史记录
+        this.addToHistory(expression);
+
+        // 显示输入(带计数器)
+        this.addOutput(expression, 'input', { count: this.executionCount });
+
+        // 调用Android接口进行计算
+        try {
+            const result = Android.evaluate(expression);
+            
+            // 检查是否是特殊功能请求
+            if (result.startsWith('##TEXFORM##')) {
+                const latex = result.substring(11);
+                this.addOutput(latex, 'result', { renderLatex: true, count: this.executionCount });
+                this.scrollToBottom();
+            } else {
+                this.addOutput(result, 'result', { count: this.executionCount });
+            }
+            
+            // 追踪变量
+            this.trackVariables(expression);
+        } catch (e) {
+            this.addOutput('错误: 无法连接到计算引擎', 'error');
+        }
+
+        // 增加计数器
+        this.executionCount++;
+        this.saveExecutionCount();
+        this.updatePrompt();
+
+        // 清空输入框
+        this.input.value = '';
+        this.completions = [];
+        this.completionIndex = -1;
+        
+        // 滚动到底部
+        this.scrollToBottom();
+    }
+
+    handleSpecialCommand(cmd) {
+        const lower = cmd.toLowerCase();
+        
+        if (lower === 'help' || lower === '?') {
+            this.showHelp();
+            return true;
+        }
+        if (lower === 'clear' || lower === 'cls') {
+            this.clear();
+            return true;
+        }
+        if (lower === 'history') {
+            this.showHistory();
+            return true;
+        }
+        if (lower === 'plugins') {
+            this.showPluginPanel();
+            return true;
+        }
+        if (lower === 'feedback') {
+            this.showFeedback();
+            return true;
+        }
+        if (lower.startsWith('clear ')) {
+            const varName = cmd.substring(6).trim();
+            this.clearVariable(varName);
+            return true;
+        }
+        
+        // 检查是否是插件命令
+        if (this.handlePluginCommand(lower)) {
+            return true;
+        }
+        
+        return false;
+    }
+
+    handlePluginCommand(cmd) {
+        // 检查是否是带参数的插件命令格式: command[arg1, arg2, ...]
+        const paramMatch = cmd.match(/^([a-z0-9_-]+)\[(.*)\]$/i);
+        
+        if (paramMatch) {
+            const commandName = paramMatch[1].toLowerCase();
+            const argsString = paramMatch[2];
+            
+            // 查找匹配的插件
+            const plugin = this.plugins.find(p => p.command && p.command.toLowerCase() === commandName);
+            
+            if (plugin) {
+                // 解析参数
+                const args = this.parsePluginArgs(argsString);
+                
+                // 验证参数数量
+                if (args.length !== plugin.inputs.length) {
+                    this.addOutput(`❌ 参数数量不匹配。${plugin.name} 需要 ${plugin.inputs.length} 个参数,但提供了 ${args.length} 个`, 'error');
+                    this.scrollToBottom();
+                    return true;
+                }
+                
+                // 执行插件(直接模式)
+                this.executePluginDirect(plugin, args);
+                return true;
+            }
+        }
+        
+        // 查找匹配的插件命令(无参数,打开对话框)
+        for (const plugin of this.plugins) {
+            if (plugin.command && plugin.command.toLowerCase() === cmd) {
+                this.addOutput(`🔌 打开插件: ${plugin.name}`, 'help');
+                this.scrollToBottom();
+                this.openPlugin(plugin);
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    parsePluginArgs(argsString) {
+        // 解析逗号分隔的参数,支持引号、大括号、方括号内的逗号
+        const args = [];
+        let current = '';
+        let inQuotes = false;
+        let quoteChar = '';
+        let bracketDepth = 0; // 跟踪 {} 的嵌套深度
+        let squareBracketDepth = 0; // 跟踪 [] 的嵌套深度
+        
+        for (let i = 0; i < argsString.length; i++) {
+            const char = argsString[i];
+            
+            // 处理引号
+            if ((char === '"' || char === "'") && (i === 0 || argsString[i-1] !== '\\')) {
+                if (!inQuotes) {
+                    inQuotes = true;
+                    quoteChar = char;
+                } else if (char === quoteChar) {
+                    inQuotes = false;
+                    quoteChar = '';
+                }
+                current += char;
+            }
+            // 处理大括号
+            else if (char === '{' && !inQuotes) {
+                bracketDepth++;
+                current += char;
+            }
+            else if (char === '}' && !inQuotes) {
+                bracketDepth--;
+                current += char;
+            }
+            // 处理方括号
+            else if (char === '[' && !inQuotes) {
+                squareBracketDepth++;
+                current += char;
+            }
+            else if (char === ']' && !inQuotes) {
+                squareBracketDepth--;
+                current += char;
+            }
+            // 处理逗号分隔符
+            else if (char === ',' && !inQuotes && bracketDepth === 0 && squareBracketDepth === 0) {
+                args.push(current.trim().replace(/^["']|["']$/g, ''));
+                current = '';
+            }
+            else {
+                current += char;
+            }
+        }
+        
+        if (current.trim()) {
+            args.push(current.trim().replace(/^["']|["']$/g, ''));
+        }
+        
+        return args;
+    }
+    
+    executePluginDirect(plugin, args) {
+        try {
+            console.log('executePluginDirect called with args:', args);
+            
+            // 构建输入对象,并转换为 Symja 可运算类型
+            const inputValues = {};
+            plugin.inputs.forEach((input, index) => {
+                const originalArg = args[index];
+                const convertedArg = this.convertInputToSymjaType(originalArg);
+                console.log(`Converting arg[${index}]: "${originalArg}" -> "${convertedArg}"`);
+                inputValues[input.name] = convertedArg;
+            });
+            
+            console.log('Final inputValues:', inputValues);
+            
+            // 用于存储插件的控制台输出
+            const pluginOutputs = [];
+            
+            // 创建 Symja API 对象
+            const symjaAPI = this.createSymjaAPI(pluginOutputs);
+            
+            // 执行插件代码
+            const func = new Function('inputs', 'Symja', plugin.jsCode + '\nreturn calculate(inputs, Symja);');
+            const result = func(inputValues, symjaAPI);
+            
+            console.log('Plugin result:', result);
+            
+            // 验证返回值
+            if (typeof result !== 'string' || result.trim() === '') {
+                throw new Error('插件必须返回非空字符串类型的 Symja 表达式');
+            }
+            
+            // 验证返回值是否是有效的 Symja 表达式
+            try {
+                Android.symjaEval(result);
+            } catch (e) {
+                throw new Error('插件返回值必须是有效的 Symja 表达式,当前返回: ' + result);
+            }
+            
+            // 显示插件名称和参数
+            const argsDisplay = args.map((arg, i) => `${plugin.inputs[i].name}=${arg}`).join(', ');
+            this.addOutput(`${plugin.icon} ${plugin.name}[${argsDisplay}]`, 'input', { count: this.executionCount });
+            
+            // 显示插件的控制台输出(如果有)
+            if (pluginOutputs.length > 0) {
+                pluginOutputs.forEach(output => {
+                    this.addOutput(output, 'plugin-info');
+                });
+            }
+            
+            // 显示插件的返回值(可运算结果)
+            this.addOutput(result, 'result', { count: this.executionCount });
+            
+            // 将插件结果保存到 Symja 引擎,使其可以通过 % 引用
+            try {
+                Android.evaluate(`Out = ${result}`);
+            } catch (e) {
+                console.log('Plugin result storage error:', e);
+            }
+            
+            // 增加计数器
+            this.executionCount++;
+            this.saveExecutionCount();
+            this.updatePrompt();
+            
+            this.scrollToBottom();
+        } catch (e) {
+            console.log('Plugin execution error:', e);
+            this.addOutput(`插件执行错误: ${e.message}`, 'error');
+            this.scrollToBottom();
+        }
+    }
+    
+    // 将用户输入转换为 Symja 可运算的类型
+    convertInputToSymjaType(value) {
+        console.log(`convertInputToSymjaType called with: "${value}"`);
+        
+        if (!value || typeof value !== 'string') {
+            console.log('Value is not a string, returning as-is');
+            return value;
+        }
+        
+        const trimmed = value.trim();
+        
+        // 空值直接返回
+        if (trimmed === '') {
+            console.log('Value is empty, returning as-is');
+            return trimmed;
+        }
+        
+        // 检查是否是单个变量名(字母开头,后跟字母数字下划线)或 % 符号
+        const varNameMatch = trimmed.match(/^([a-zA-Z]\w*|%)$/);
+        console.log(`Variable name match result:`, varNameMatch);
+        
+        if (varNameMatch) {
+            // 直接尝试求值,如果是已定义的变量会返回值,否则返回变量名本身
+            try {
+                const evalResult = Android.symjaEval(trimmed);
+                console.log(`Evaluated "${trimmed}" to: "${evalResult}"`);
+                
+                // 如果求值结果与输入不同,说明变量已定义
+                if (evalResult !== trimmed) {
+                    console.log(`Variable ${trimmed} is defined, using value: ${evalResult}`);
+                    return evalResult;
+                } else {
+                    console.log(`Variable ${trimmed} is not defined or equals itself`);
+                }
+            } catch (e) {
+                console.error(`Failed to evaluate ${trimmed}:`, e);
+            }
+        }
+        
+        // 尝试通过 Symja 验证,如果能成功解析说明是有效的表达式
+        console.log(`Trying to validate as Symja expression: "${trimmed}"`);
+        try {
+            Android.symjaEval(trimmed);
+            console.log(`Valid Symja expression, returning: "${trimmed}"`);
+            return trimmed;
+        } catch (e) {
+            // 如果无法解析为 Symja 表达式,返回原值
+            // 插件内部可以根据需要进一步处理
+            console.log(`Not a valid Symja expression, returning as literal: "${trimmed}"`);
+            return trimmed;
+        }
+    }
+    
+    createSymjaAPI(pluginOutputs) {
+        return {
+            // 控制台输出 API
+            print: function(message) {
+                pluginOutputs.push(String(message));
+            },
+            
+            // 基础计算
+            eval: function(expression) {
+                try {
+                    return Android.symjaEval(expression);
+                } catch (e) {
+                    return '错误: 无法调用 Symja';
+                }
+            },
+            
+            // 微积分
+            differentiate: function(expr, variable) {
+                return this.eval(`D[${expr}, ${variable}]`);
+            },
+            integrate: function(expr, variable) {
+                return this.eval(`Integrate[${expr}, ${variable}]`);
+            },
+            limit: function(expr, variable, point, direction) {
+                try {
+                    return Android.limit(expr, variable, point, direction || '');
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            series: function(expr, variable, point, order) {
+                try {
+                    return Android.series(expr, variable, point, order || 5);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // 代数
+            solve: function(equation, variable) {
+                return this.eval(`Solve[${equation}, ${variable}]`);
+            },
+            simplify: function(expr) {
+                return this.eval(`Simplify[${expr}]`);
+            },
+            expand: function(expr) {
+                return this.eval(`Expand[${expr}]`);
+            },
+            factor: function(expr) {
+                return this.eval(`Factor[${expr}]`);
+            },
+            apart: function(expr, variable) {
+                try {
+                    return Android.apart(expr, variable);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            together: function(expr) {
+                try {
+                    return Android.together(expr);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            trigSimplify: function(expr) {
+                try {
+                    return Android.trigSimplify(expr);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // 求和与求积
+            sum: function(expr, variable, start, end) {
+                try {
+                    return Android.sum(expr, variable, String(start), String(end));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            product: function(expr, variable, start, end) {
+                try {
+                    return Android.product(expr, variable, String(start), String(end));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // 矩阵运算
+            matrix: {
+                det: function(matrix) {
+                    try {
+                        return Android.matrixOp('Det', matrix);
+                    } catch (e) {
+                        return '错误: ' + e.message;
+                    }
+                },
+                inverse: function(matrix) {
+                    try {
+                        return Android.matrixOp('Inverse', matrix);
+                    } catch (e) {
+                        return '错误: ' + e.message;
+                    }
+                },
+                transpose: function(matrix) {
+                    try {
+                        return Android.matrixOp('Transpose', matrix);
+                    } catch (e) {
+                        return '错误: ' + e.message;
+                    }
+                },
+                eigenvalues: function(matrix) {
+                    try {
+                        return Android.matrixOp('Eigenvalues', matrix);
+                    } catch (e) {
+                        return '错误: ' + e.message;
+                    }
+                },
+                rank: function(matrix) {
+                    try {
+                        return Android.matrixOp('MatrixRank', matrix);
+                    } catch (e) {
+                        return '错误: ' + e.message;
+                    }
+                }
+            },
+            
+            // 数论
+            gcd: function(a, b) {
+                try {
+                    return Android.gcd(String(a), String(b));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            lcm: function(a, b) {
+                try {
+                    return Android.lcm(String(a), String(b));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            factorInteger: function(n) {
+                try {
+                    return Android.factorInteger(String(n));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            isPrime: function(n) {
+                try {
+                    return Android.isPrime(String(n));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // 组合数学
+            fibonacci: function(n) {
+                try {
+                    return Android.fibonacci(n);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            binomial: function(n, k) {
+                try {
+                    return Android.binomial(String(n), String(k));
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // 数值计算
+            numeric: function(expr, precision) {
+                try {
+                    return Android.numericEval(expr, precision || 16);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // LaTeX 转换和渲染
+            toTeX: function(expr) {
+                try {
+                    return Android.toTeX(expr);
+                } catch (e) {
+                    return '错误: ' + e.message;
+                }
+            },
+            
+            // 渲染 LaTeX(供插件使用)
+            renderLatex: (latex) => {
+                symjaConsole.addOutput(latex, 'result', { renderLatex: true });
+                return '已渲染 LaTeX';
+            }
+        };
+    }
+
+    trackVariables(expression) {
+        // 简单的变量赋值检测
+        const match = expression.match(/^([a-zA-Z]\w*)\s*=/);
+        if (match) {
+            this.variables.add(match[1]);
+        }
+    }
+
+    clearVariable(varName) {
+        try {
+            const result = Android.clearVariable(varName);
+            this.addOutput(result, 'help');
+            this.variables.delete(varName);
+        } catch (e) {
+            this.addOutput(`❌ 清除失败: ${e.message}`, 'error');
+        }
+        this.scrollToBottom();
+    }
+
+    showHistory() {
+        if (this.history.length === 0) {
+            this.addOutput('📜 历史记录为空', 'help');
+        } else {
+            const recent = this.history.slice(-10).reverse();
+            const historyText = recent.map((cmd, i) => `${recent.length - i}. ${cmd}`).join('\n');
+            this.addOutput(`📜 最近的命令:\n${historyText}`, 'help');
+        }
+        this.scrollToBottom();
+    }
+
+    showFeedback() {
+        const email = 'nonvioce@qq.com';
+        const feedbackId = 'feedback-' + Date.now();
+        
+        // 创建带复制按钮的反馈信息
+        const line = document.createElement('div');
+        line.className = 'terminal-line';
+        line.innerHTML = `
+            <div class="feedback-line">
+                <div class="feedback-icon">📧</div>
+                <div class="feedback-content">
+                    <div class="feedback-title">反馈与建议</div>
+                    <div class="feedback-text">
+                        欢迎通过邮件向我们反馈问题或提出建议!
+                    </div>
+                    <div class="feedback-email-container">
+                        <span class="feedback-email">${email}</span>
+                        <button class="feedback-copy-btn" onclick="symjaConsole.copyFeedbackEmail('${email}', this)">
+                            📋 复制邮箱
+                        </button>
+                    </div>
+                </div>
+            </div>
+        `;
+        
+        this.output.appendChild(line);
+        this.scrollToBottom();
+    }
+
+    copyFeedbackEmail(email, button) {
+        // 创建临时文本区域
+        const textarea = document.createElement('textarea');
+        textarea.value = email;
+        textarea.style.position = 'fixed';
+        textarea.style.opacity = '0';
+        document.body.appendChild(textarea);
+        textarea.select();
+        
+        try {
+            document.execCommand('copy');
+            button.textContent = '✅ 已复制';
+            button.style.background = '#4CAF50';
+            setTimeout(() => {
+                button.textContent = '📋 复制邮箱';
+                button.style.background = '';
+            }, 2000);
+        } catch (e) {
+            console.error('Copy failed:', e);
+            button.textContent = '❌ 复制失败';
+            setTimeout(() => {
+                button.textContent = '📋 复制邮箱';
+            }, 2000);
+        }
+        
+        document.body.removeChild(textarea);
+    }
+
+    addToHistory(cmd) {
+        // 避免重复的连续命令
+        if (this.history.length === 0 || this.history[this.history.length - 1] !== cmd) {
+            this.history.push(cmd);
+            // 限制历史记录大小
+            if (this.history.length > 100) {
+                this.history.shift();
+            }
+            this.saveHistory();
+        }
+        this.historyIndex = this.history.length;
+    }
+
+    loadHistory() {
+        try {
+            const saved = localStorage.getItem('symja_history');
+            return saved ? JSON.parse(saved) : [];
+        } catch (e) {
+            return [];
+        }
+    }
+
+    saveHistory() {
+        try {
+            localStorage.setItem('symja_history', JSON.stringify(this.history));
+        } catch (e) {
+            // 忽略存储错误
+        }
+    }
+
+    saveSession(name) {
+        try {
+            const session = {
+                history: this.history,
+                variables: Array.from(this.variables),
+                timestamp: new Date().toISOString()
+            };
+            localStorage.setItem(`symja_session_${name}`, JSON.stringify(session));
+            this.addOutput(`💾 会话已保存: ${name}`, 'help');
+        } catch (e) {
+            this.addOutput(`❌ 保存失败: ${e.message}`, 'error');
+        }
+        this.scrollToBottom();
+    }
+
+    addOutput(text, type, options = {}) {
+        const line = document.createElement('div');
+        line.className = 'terminal-line';
+
+        if (type === 'input') {
+            // 命令输入显示(带计数器)
+            const count = options.count !== undefined ? options.count : '';
+            line.innerHTML = `
+                <div class="command-line">
+                    <span class="command-prompt">[${count}]:</span>
+                    <span class="command-text">${this.escapeHtml(text)}</span>
+                </div>
+            `;
+        } else if (type === 'result') {
+            const isError = text.startsWith('错误:');
+            
+            // 检查是否需要 LaTeX 渲染
+            if (options.renderLatex && !isError) {
+                const count = options.count !== undefined ? options.count : '';
+                line.innerHTML = `
+                    <div class="latex-line">
+                        <div class="result-prompt">[${count}]:</div>
+                        <div class="latex-content">
+                            <div class="latex-rendered" id="latex-${Date.now()}"></div>
+                            <div class="latex-source" style="display: none;">${this.escapeHtml(text)}</div>
+                            <div class="latex-actions">
+                                <button class="latex-btn latex-toggle" onclick="symjaConsole.toggleLatexSource(this)">显示源码</button>
+                                <button class="latex-btn latex-copy" onclick="symjaConsole.copyLatex(this)">复制 LaTeX</button>
+                            </div>
+                        </div>
+                    </div>
+                `;
+                this.output.appendChild(line);
+                
+                // 渲染 LaTeX
+                const renderedDiv = line.querySelector('.latex-rendered');
+                this.renderLatex(text, renderedDiv);
+            } else {
+                const className = isError ? 'error-line' : 'result-line';
+                const count = options.count !== undefined ? options.count : '';
+                line.innerHTML = `
+                    <div class="${className}">
+                        <span class="result-prompt">[${count}]:</span>
+                        <span class="result-text">${this.escapeHtml(text)}</span>
+                    </div>
+                `;
+            }
+        } else if (type === 'help') {
+            line.innerHTML = `<div class="info-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'error') {
+            line.innerHTML = `<div class="error-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'plugin-info') {
+            // 插件的控制台输出(通过 Symja.print() 输出的内容)
+            line.innerHTML = `<div class="plugin-info-line">${this.escapeHtml(text)}</div>`;
+        } else if (type === 'plugin') {
+            // 插件的返回值(可运算结果)
+            line.innerHTML = `<div class="plugin-line">${this.escapeHtml(text)}</div>`;
+        }
+        
+        this.output.appendChild(line);
+    }
+
+    // LaTeX 渲染功能
+    renderLatex(latex, element) {
+        try {
+            // 等待 KaTeX 加载
+            if (typeof katex !== 'undefined') {
+                // 清理 LaTeX 字符串
+                let cleanLatex = latex.trim();
+                
+                // 如果是 Symja 的 TeXForm 输出,可能需要一些转换
+                if (cleanLatex.startsWith('TeXForm:')) {
+                    cleanLatex = cleanLatex.substring(8).trim();
+                }
+                
+                // 使用 KaTeX 渲染
+                katex.render(cleanLatex, element, {
+                    throwOnError: false,
+                    displayMode: true,
+                    output: 'html',
+                    trust: false,
+                    strict: false
+                });
+            } else {
+                // KaTeX 未加载,显示格式化的源码
+                element.style.fontFamily = 'var(--font-mono)';
+                element.style.fontSize = '14px';
+                element.style.padding = '12px';
+                element.style.background = '#F5F5F5';
+                element.style.borderRadius = '4px';
+                element.style.overflowX = 'auto';
+                element.textContent = latex;
+                console.warn('KaTeX not loaded, showing source code');
+            }
+        } catch (e) {
+            // 渲染失败,显示源码
+            element.style.fontFamily = 'var(--font-mono)';
+            element.style.fontSize = '14px';
+            element.style.padding = '12px';
+            element.style.background = '#F5F5F5';
+            element.style.borderRadius = '4px';
+            element.style.overflowX = 'auto';
+            element.textContent = latex;
+            console.error('LaTeX rendering error:', e);
+        }
+    }
+
+    // 切换显示 LaTeX 源码
+    toggleLatexSource(button) {
+        const parent = button.closest('.latex-line');
+        if (!parent) return;
+        
+        const source = parent.querySelector('.latex-source');
+        if (!source) return;
+        
+        if (source.style.display === 'none') {
+            source.style.display = 'block';
+            button.textContent = '隐藏源码';
+        } else {
+            source.style.display = 'none';
+            button.textContent = '显示源码';
+        }
+    }
+
+    // 复制 LaTeX 代码
+    copyLatex(button) {
+        const parent = button.closest('.latex-line');
+        if (!parent) return;
+        
+        const source = parent.querySelector('.latex-source');
+        if (!source) return;
+        
+        const text = source.textContent;
+        
+        // 创建临时文本区域
+        const textarea = document.createElement('textarea');
+        textarea.value = text;
+        textarea.style.position = 'fixed';
+        textarea.style.opacity = '0';
+        document.body.appendChild(textarea);
+        textarea.select();
+        
+        try {
+            document.execCommand('copy');
+            button.textContent = '已复制!';
+            setTimeout(() => {
+                button.textContent = '复制 LaTeX';
+            }, 2000);
+        } catch (e) {
+            console.error('Copy failed:', e);
+        }
+        
+        document.body.removeChild(textarea);
+    }
+
+    showHelp() {
+        const helpPage = document.getElementById('helpPage');
+        const helpContent = document.getElementById('helpContent');
+        
+        if (!helpPage || !helpContent) return;
+        
+        // 隐藏键盘
+        this.hideKeyboard();
+        
+        helpContent.innerHTML = `
+            <table class="help-table">
+                <thead>
+                    <tr>
+                        <th>说明</th>
+                        <th>命令</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr><td>求导</td><td>D[x^2, x]</td></tr>
+                    <tr><td>复合函数求导</td><td>D[Sin[x]*Cos[x], x]</td></tr>
+                    <tr><td>不定积分</td><td>Integrate[x^2, x]</td></tr>
+                    <tr><td>定积分</td><td>Integrate[x^2, {x, 0, 1}]</td></tr>
+                    <tr><td>求极限</td><td>Limit[Sin[x]/x, x -> 0]</td></tr>
+                    <tr><td>泰勒展开</td><td>Series[Exp[x], {x, 0, 5}]</td></tr>
+                    <tr><td>求和</td><td>Sum[i^2, {i, 1, 10}]</td></tr>
+                    <tr><td>求积(阶乘)</td><td>Product[i, {i, 1, 5}]</td></tr>
+                    
+                    <tr><td>展开</td><td>Expand[(x+1)^3]</td></tr>
+                    <tr><td>因式分解</td><td>Factor[x^2-4]</td></tr>
+                    <tr><td>化简</td><td>Simplify[Sin[x]^2+Cos[x]^2]</td></tr>
+                    <tr><td>通分</td><td>Together[(1/x)+(1/y)]</td></tr>
+                    <tr><td>部分分式</td><td>Apart[(x+1)/(x^2-1), x]</td></tr>
+                    <tr><td>约分</td><td>Cancel[(x^2-1)/(x-1)]</td></tr>
+                    <tr><td>合并同类项</td><td>Collect[x^2+3*x+2*x^2, x]</td></tr>
+                    
+                    <tr><td>解方程</td><td>Solve[x^2-5*x+6==0, x]</td></tr>
+                    <tr><td>方程组</td><td>Solve[{x+y==1, x-y==3}, {x,y}]</td></tr>
+                    <tr><td>数值求解</td><td>NSolve[x^3-2*x+1==0, x]</td></tr>
+                    <tr><td>求根</td><td>Roots[x^2-4==0, x]</td></tr>
+                    
+                    <tr><td>行列式</td><td>Det[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>逆矩阵</td><td>Inverse[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>转置</td><td>Transpose[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>特征值</td><td>Eigenvalues[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>特征向量</td><td>Eigenvectors[{{1,2},{3,4}}]</td></tr>
+                    <tr><td>矩阵秩</td><td>MatrixRank[{{1,2},{2,4}}]</td></tr>
+                    
+                    <tr><td>最大公约数</td><td>GCD[12, 18]</td></tr>
+                    <tr><td>最小公倍数</td><td>LCM[12, 18]</td></tr>
+                    <tr><td>质因数分解</td><td>FactorInteger[60]</td></tr>
+                    <tr><td>判断素数</td><td>PrimeQ[17]</td></tr>
+                    <tr><td>第 n 个素数</td><td>Prime[10]</td></tr>
+                    <tr><td>斐波那契数</td><td>Fibonacci[10]</td></tr>
+                    <tr><td>组合数 C(n,k)</td><td>Binomial[5, 2]</td></tr>
+                    
+                    <tr><td>高精度计算(50位)</td><td>N[Pi, 50]</td></tr>
+                    <tr><td>数值化</td><td>N[Sqrt[2]]</td></tr>
+                    <tr><td>四舍五入</td><td>Round[3.7]</td></tr>
+                    <tr><td>向下取整</td><td>Floor[3.7]</td></tr>
+                    <tr><td>向上取整</td><td>Ceiling[3.2]</td></tr>
+                    
+                    <tr><td>三角函数</td><td>Sin[x], Cos[x], Tan[x]</td></tr>
+                    <tr><td>反三角函数</td><td>ArcSin[x], ArcCos[x], ArcTan[x]</td></tr>
+                    <tr><td>双曲函数</td><td>Sinh[x], Cosh[x], Tanh[x]</td></tr>
+                    <tr><td>三角展开</td><td>TrigExpand[Sin[2*x]]</td></tr>
+                    <tr><td>三角化简</td><td>TrigReduce[Sin[x]^2]</td></tr>
+                    
+                    <tr><td>实部</td><td>Re[3+4*I]</td></tr>
+                    <tr><td>虚部</td><td>Im[3+4*I]</td></tr>
+                    <tr><td>模</td><td>Abs[3+4*I]</td></tr>
+                    <tr><td>辐角</td><td>Arg[3+4*I]</td></tr>
+                    <tr><td>共轭</td><td>Conjugate[3+4*I]</td></tr>
+                    
+                    <tr><td>生成表格</td><td>Table[i^2, {i, 1, 10}]</td></tr>
+                    <tr><td>生成数列</td><td>Range[1, 10]</td></tr>
+                    <tr><td>带步长的数列</td><td>Range[0, 1, 0.1]</td></tr>
+                    <tr><td>列表长度</td><td>Length[{1,2,3,4}]</td></tr>
+                    <tr><td>第一个元素</td><td>First[{1,2,3}]</td></tr>
+                    <tr><td>最后一个元素</td><td>Last[{1,2,3}]</td></tr>
+                    <tr><td>取前 n 个</td><td>Take[{1,2,3,4,5}, 3]</td></tr>
+                    <tr><td>删除前 n 个</td><td>Drop[{1,2,3,4,5}, 2]</td></tr>
+                    <tr><td>连接列表</td><td>Join[{1,2}, {3,4}]</td></tr>
+                    <tr><td>反转列表</td><td>Reverse[{1,2,3}]</td></tr>
+                    <tr><td>排序</td><td>Sort[{3,1,4,1,5}]</td></tr>
+                    
+                    <tr><td>替换规则</td><td>b+c /. b->1</td></tr>
+                    <tr><td>变量替换</td><td>x^2+2*x+1 /. x->3</td></tr>
+                    <tr><td>多重替换</td><td>expr /. {a->1, b->2}</td></tr>
+                    
+                    <tr><td>微分方程</td><td>DSolve[y'[x]==y[x], y[x], x]</td></tr>
+                    <tr><td>递推方程</td><td>RSolve[a[n]==2*a[n-1]+1, a[n], n]</td></tr>
+                    
+                    <tr><td>拉普拉斯变换</td><td>LaplaceTransform[Sin[t], t, s]</td></tr>
+                    <tr><td>逆拉普拉斯变换</td><td>InverseLaplaceTransform[1/(s^2+1), s, t]</td></tr>
+                    <tr><td>傅里叶变换</td><td>FourierTransform[Exp[-x^2], x, w]</td></tr>
+                    
+                    <tr><td>求最大值</td><td>Maximize[x^2-4*x+3, x]</td></tr>
+                    <tr><td>求最小值</td><td>Minimize[x^2-4*x+3, x]</td></tr>
+                    
+                    <tr><td>平均值</td><td>Mean[{1,2,3,4,5}]</td></tr>
+                    <tr><td>中位数</td><td>Median[{1,2,3,4,5}]</td></tr>
+                    <tr><td>方差</td><td>Variance[{1,2,3,4,5}]</td></tr>
+                    <tr><td>标准差</td><td>StandardDeviation[{1,2,3,4,5}]</td></tr>
+                    
+                    <tr><td>LaTeX 格式</td><td>TeXForm[x^2 + 2*x + 1]</td></tr>
+                    <tr><td>完整形式</td><td>FullForm[x+y]</td></tr>
+                    <tr><td>输入形式</td><td>InputForm[expr]</td></tr>
+                    
+                    <tr><td>显示帮助</td><td>help 或 ?</td></tr>
+                    <tr><td>清空输出</td><td>clear 或 cls</td></tr>
+                    <tr><td>命令历史</td><td>history</td></tr>
+                    <tr><td>清除变量</td><td>clear &lt;变量&gt;</td></tr>
+                    <tr><td>插件管理</td><td>plugins</td></tr>
+                    <tr><td>显示反馈邮箱</td><td>feedback</td></tr>
+                    
+                    <tr><td>圆周率 π</td><td>Pi</td></tr>
+                    <tr><td>自然常数 e</td><td>E</td></tr>
+                    <tr><td>虚数单位 i</td><td>I</td></tr>
+                    <tr><td>无穷大 ∞</td><td>Infinity</td></tr>
+                    <tr><td>布尔值</td><td>True, False</td></tr>
+                    <tr><td>上一次的输出结果</td><td>%</td></tr>
+                </tbody>
+            </table>
+        `;
+        
+        helpPage.classList.add('show');
+    }
+    
+    hideHelp() {
+        const helpPage = document.getElementById('helpPage');
+        if (helpPage) {
+            helpPage.classList.remove('show');
+        }
+    }
+    
+    showSettings() {
+        // 设置功能待实现
+        this.addOutput('⚙️ 设置功能即将推出...', 'info');
+        this.scrollToBottom();
+    }
+
+    clear() {
+        this.output.innerHTML = '';
+        this.showWelcome();
+        // 重置计数器
+        this.executionCount = 1;
+        this.saveExecutionCount();
+        this.updatePrompt();
+    }
+
+    reset() {
+        try {
+            const result = Android.reset();
+            this.variables.clear();
+            this.addOutput('🔄 ' + result, 'help');
+            this.scrollToBottom();
+            // 重置计数器
+            this.executionCount = 1;
+            this.saveExecutionCount();
+            this.updatePrompt();
+        } catch (e) {
+            this.addOutput('错误: 无法重置计算器', 'error');
+        }
+    }
+
+    updatePrompt() {
+        if (this.inputPrompt) {
+            this.inputPrompt.textContent = `[${this.executionCount}]:`;
+        }
+    }
+
+    loadExecutionCount() {
+        // 每次打开应用都从 1 开始
+        return 1;
+    }
+
+    saveExecutionCount() {
+        // 不再保存到 localStorage,每次打开应用都重置
+    }
+
+    navigateHistory(direction) {
+        if (this.history.length === 0) {
+            return;
+        }
+
+        // 保存当前输入
+        if (this.historyIndex === this.history.length) {
+            this.tempInput = this.input.value;
+        }
+
+        this.historyIndex += direction;
+
+        if (this.historyIndex < 0) {
+            this.historyIndex = 0;
+        } else if (this.historyIndex >= this.history.length) {
+            this.historyIndex = this.history.length;
+            this.input.value = this.tempInput;
+            return;
+        }
+
+        this.input.value = this.history[this.historyIndex];
+        // 光标移到末尾
+        setTimeout(() => {
+            this.input.setSelectionRange(this.input.value.length, this.input.value.length);
+        }, 0);
+    }
+
+    // 自动补全功能
+    updateCompletionList() {
+        const value = this.input.value;
+        const cursorPos = this.input.selectionStart;
+        const textBeforeCursor = value.substring(0, cursorPos);
+        const match = textBeforeCursor.match(/[A-Za-z0-9_-]+$/);
+        
+        console.log('updateCompletionList:', { value, textBeforeCursor, match });
+        
+        if (match && match[0].length >= 2) {
+            const prefix = match[0];
+            console.log('Searching completions for prefix:', prefix);
+            
+            // 特殊命令列表
+            const specialCommands = ['help', 'clear', 'cls', 'history', 'plugins', 'feedback'];
+            
+            // 获取插件命令列表
+            const pluginCommands = this.plugins
+                .filter(p => p.command && p.command.length > 0)
+                .map(p => p.command);
+            
+            try {
+                const completionsJson = Android.getCompletions(prefix);
+                console.log('Raw completions JSON:', completionsJson);
+                
+                // 解析补全结果 - 可能是逗号分隔的字符串或 JSON 数组
+                let symjaCompletions = [];
+                if (completionsJson.startsWith('[')) {
+                    // JSON 数组格式
+                    symjaCompletions = JSON.parse(completionsJson);
+                } else {
+                    // 逗号分隔的字符串格式
+                    symjaCompletions = completionsJson.split(',').map(s => s.trim()).filter(s => s.length > 0);
+                }
+                
+                // 添加匹配的特殊命令
+                const lowerPrefix = prefix.toLowerCase();
+                const matchingCommands = specialCommands.filter(cmd => cmd.startsWith(lowerPrefix));
+                
+                // 添加匹配的插件命令
+                const matchingPluginCommands = pluginCommands.filter(cmd => cmd.toLowerCase().startsWith(lowerPrefix));
+                
+                // 合并并去重(插件命令优先显示)
+                this.completions = [...new Set([...matchingPluginCommands, ...matchingCommands, ...symjaCompletions])];
+                
+                console.log('Parsed completions:', this.completions);
+                
+                if (this.completions.length > 0) {
+                    this.showCompletionList();
+                } else {
+                    this.hideCompletionList();
+                }
+            } catch (e) {
+                console.error('Completion error:', e);
+                this.completions = [];
+                this.hideCompletionList();
+            }
+        } else {
+            this.completions = [];
+            this.hideCompletionList();
+        }
+    }
+
+    showCompletionList() {
+        console.log('showCompletionList called');
+        console.log('completionList element:', this.completionList);
+        console.log('completions:', this.completions);
+        
+        if (!this.completionList) {
+            console.error('completionList element not found');
+            return;
+        }
+        
+        if (this.completions.length === 0) {
+            console.log('No completions to show');
+            return;
+        }
+        
+        this.completionList.innerHTML = '';
+        
+        // 最多显示 8 个补全项
+        const itemsToShow = this.completions.slice(0, 8);
+        console.log('Items to show:', itemsToShow);
+        
+        itemsToShow.forEach(completion => {
+            const item = document.createElement('div');
+            item.className = 'completion-item';
+            
+            // 检查是否是插件命令
+            const plugin = this.plugins.find(p => p.command === completion);
+            if (plugin) {
+                // 插件命令,显示图标和名称
+                item.innerHTML = `<span style="margin-right: 8px;">${plugin.icon}</span>${completion} <span style="color: #999; font-size: 12px; margin-left: 8px;">(${plugin.name})</span>`;
+            } else {
+                // 普通命令或函数
+                item.textContent = completion;
+            }
+            
+            item.addEventListener('click', () => {
+                this.applyCompletion(completion);
+            });
+            this.completionList.appendChild(item);
+        });
+        
+        this.completionList.classList.add('show');
+        console.log('Completion list should now be visible');
+    }
+
+    hideCompletionList() {
+        if (this.completionList) {
+            this.completionList.classList.remove('show');
+        }
+    }
+
+    applyCompletion(completion) {
+        const value = this.input.value;
+        const cursorPos = this.input.selectionStart;
+        const textBeforeCursor = value.substring(0, cursorPos);
+        const textAfterCursor = value.substring(cursorPos);
+
+        const match = textBeforeCursor.match(/[A-Za-z0-9_-]+$/);
+        if (match) {
+            const prefix = match[0];
+            const beforePrefix = textBeforeCursor.substring(0, textBeforeCursor.length - prefix.length);
+            
+            // 检查是否是插件命令
+            const plugin = this.plugins.find(p => p.command === completion);
+            if (plugin && plugin.inputs && plugin.inputs.length > 0) {
+                // 插件命令,添加参数括号和提示
+                const paramHints = plugin.inputs.map(input => input.name).join(', ');
+                this.input.value = beforePrefix + completion + '[' + paramHints + ']' + textAfterCursor;
+                
+                // 将光标放在第一个参数位置
+                const newCursorPos = beforePrefix.length + completion.length + 1;
+                this.input.setSelectionRange(newCursorPos, newCursorPos + plugin.inputs[0].name.length);
+            } else {
+                // 普通命令或函数
+                this.input.value = beforePrefix + completion + textAfterCursor;
+                
+                const newCursorPos = beforePrefix.length + completion.length;
+                this.input.setSelectionRange(newCursorPos, newCursorPos);
+            }
+        }
+        
+        this.hideCompletionList();
+        this.input.focus();
+    }
+
+    handleAutoComplete() {
+        // 保留这个方法以防其他地方调用,但不再使用
+        this.updateCompletionList();
+    }
+
+    // 插件相关功能
+    loadPlugins() {
+        try {
+            const pluginsJson = Android.getPlugins();
+            console.log('Loaded plugins JSON:', pluginsJson);
+            this.plugins = JSON.parse(pluginsJson);
+            console.log('Parsed plugins count:', this.plugins.length);
+            console.log('Plugins:', this.plugins);
+        } catch (e) {
+            console.error('Failed to load plugins:', e);
+            this.plugins = [];
+        }
+    }
+
+    showPluginPanel() {
+        this.loadPlugins();
+        const panel = document.getElementById('pluginPanel');
+        
+        if (!panel) return;
+        
+        // 隐藏键盘
+        this.hideKeyboard();
+        
+        // 确保默认显示插件列表标签
+        const tabs = panel.querySelectorAll('.plugin-tab');
+        const contents = panel.querySelectorAll('.tab-content');
+        
+        tabs.forEach(tab => {
+            if (tab.dataset.tab === 'list') {
+                tab.classList.add('active');
+            } else {
+                tab.classList.remove('active');
+            }
+        });
+        
+        contents.forEach(content => {
+            if (content.dataset.content === 'list') {
+                content.classList.add('active');
+            } else {
+                content.classList.remove('active');
+            }
+        });
+        
+        // 更新插件下拉列表(用于分享和删除)
+        this.updatePluginSelects();
+        
+        // 使用统一的刷新方法
+        this.refreshPluginList();
+        
+        panel.classList.add('show');
+    }
+
+    hidePluginPanel() {
+        const panel = document.getElementById('pluginPanel');
+        if (panel) panel.classList.remove('show');
+    }
+
+    openPlugin(plugin) {
+        this.currentPlugin = plugin;
+        this.hidePluginPanel();
+        
+        const dialog = document.getElementById('pluginDialog');
+        const title = document.getElementById('pluginDialogTitle');
+        const desc = document.getElementById('pluginDialogDesc');
+        const inputs = document.getElementById('pluginInputs');
+        
+        if (!dialog || !title || !desc || !inputs) return;
+        
+        title.textContent = plugin.icon + ' ' + plugin.name;
+        desc.textContent = plugin.description;
+        
+        inputs.innerHTML = '';
+        plugin.inputs.forEach(input => {
+            const inputGroup = document.createElement('div');
+            inputGroup.className = 'input-group';
+            inputGroup.innerHTML = `
+                <label>${input.label}</label>
+                <input type="text" 
+                       name="${input.name}" 
+                       value="${input.defaultValue || ''}"
+                       placeholder="${input.placeholder || ''}">
+            `;
+            inputs.appendChild(inputGroup);
+        });
+        
+        dialog.classList.add('show');
+    }
+
+    hidePluginDialog() {
+        const dialog = document.getElementById('pluginDialog');
+        if (dialog) dialog.classList.remove('show');
+        this.currentPlugin = null;
+    }
+
+    executeCurrentPlugin() {
+        if (!this.currentPlugin) return;
+        
+        const inputsDiv = document.getElementById('pluginInputs');
+        if (!inputsDiv) return;
+        
+        const inputElements = inputsDiv.querySelectorAll('input');
+        const inputValues = {};
+        
+        // 收集输入值并转换为 Symja 可运算类型
+        inputElements.forEach(input => {
+            inputValues[input.name] = this.convertInputToSymjaType(input.value);
+        });
+        
+        try {
+            // 用于存储插件的控制台输出
+            const pluginOutputs = [];
+            
+            // 创建 Symja API 对象供插件使用
+            const symjaAPI = this.createSymjaAPI(pluginOutputs);
+            
+            // 执行插件代码,传入 inputs 和 Symja API
+            const func = new Function('inputs', 'Symja', this.currentPlugin.jsCode + '\nreturn calculate(inputs, Symja);');
+            const result = func(inputValues, symjaAPI);
+            
+            // 验证返回值是否为有效的可运算类型
+            if (typeof result !== 'string' || result.trim() === '') {
+                throw new Error('插件必须返回非空字符串类型的 Symja 表达式');
+            }
+            
+            // 验证返回值是否是有效的 Symja 表达式(尝试计算)
+            try {
+                Android.symjaEval(result);
+            } catch (e) {
+                throw new Error('插件返回值必须是有效的 Symja 表达式,当前返回: ' + result);
+            }
+            
+            // 显示插件名称
+            this.addOutput(`${this.currentPlugin.icon} ${this.currentPlugin.name}`, 'input');
+            
+            // 显示插件的控制台输出(如果有)
+            if (pluginOutputs.length > 0) {
+                pluginOutputs.forEach(output => {
+                    this.addOutput(output, 'plugin-info');
+                });
+            }
+            
+            // 显示插件的返回值(可运算结果)
+            this.addOutput(result, 'plugin');
+            
+            // 将插件结果保存到 Symja 引擎,使其可以通过 % 引用
+            try {
+                Android.evaluate(`Out = ${result}`);
+            } catch (e) {
+                console.log('Plugin result storage error:', e);
+            }
+            
+            this.scrollToBottom();
+            this.hidePluginDialog();
+        } catch (e) {
+            this.addOutput(`插件执行错误: ${e.message}`, 'error');
+        }
+    }
+
+    scrollToBottom() {
+        this.output.scrollTop = this.output.scrollHeight;
+    }
+
+    escapeHtml(text) {
+        const div = document.createElement('div');
+        div.textContent = text;
+        return div.innerHTML;
+    }
+
+    hideKeyboard() {
+        // 移除输入框焦点
+        if (this.input) {
+            this.input.blur();
+        }
+        
+        // 调用 Android 接口隐藏键盘
+        try {
+            if (typeof Android !== 'undefined' && Android.hideKeyboard) {
+                Android.hideKeyboard();
+            }
+        } catch (e) {
+            console.log('hideKeyboard not available:', e);
+        }
+    }
+}
+
+// 初始化控制台
+let symjaConsole;
+document.addEventListener('DOMContentLoaded', () => {
+    symjaConsole = new SymjaConsole();
+});

BIN
app/src/main/assets/katex/fonts/KaTeX_AMS-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_AMS-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_AMS-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Bold.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Bold.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Bold.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Caligraphic-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Fraktur-Bold.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Fraktur-Bold.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Fraktur-Bold.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Fraktur-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Fraktur-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Fraktur-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Bold.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Bold.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Bold.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-BoldItalic.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-BoldItalic.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-BoldItalic.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Italic.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Italic.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Italic.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Main-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Math-BoldItalic.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Math-BoldItalic.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Math-BoldItalic.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Math-Italic.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Math-Italic.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Math-Italic.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Bold.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Bold.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Bold.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Italic.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Italic.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Italic.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_SansSerif-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Script-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Script-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Script-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Size1-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Size1-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Size1-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Size2-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Size2-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Size2-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Size3-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Size3-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Size3-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Size4-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Size4-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Size4-Regular.woff2


BIN
app/src/main/assets/katex/fonts/KaTeX_Typewriter-Regular.ttf


BIN
app/src/main/assets/katex/fonts/KaTeX_Typewriter-Regular.woff


BIN
app/src/main/assets/katex/fonts/KaTeX_Typewriter-Regular.woff2


Plik diff jest za duży
+ 0 - 0
app/src/main/assets/katex/katex.min.css


Plik diff jest za duży
+ 0 - 0
app/src/main/assets/katex/katex.min.js


BIN
app/src/main/ic_launcher-playstore.png


+ 366 - 0
app/src/main/java/com/nonvioce/ijs/MainActivity.java

@@ -0,0 +1,366 @@
+package com.nonvioce.ijs;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+import androidx.appcompat.app.AppCompatActivity;
+import com.nonvioce.ijs.databinding.ActivityMainBinding;
+
+public class MainActivity extends AppCompatActivity {
+    private ActivityMainBinding binding;
+    private SymjaEngine symjaEngine;
+    private PluginManager pluginManager;
+
+    @SuppressLint("SetJavaScriptEnabled")
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        binding = ActivityMainBinding.inflate(getLayoutInflater());
+        setContentView(binding.getRoot());
+
+        symjaEngine = new SymjaEngine();
+        pluginManager = new PluginManager(this);
+
+        // 配置WebView
+        binding.webView.getSettings().setJavaScriptEnabled(true);
+        binding.webView.getSettings().setDomStorageEnabled(true);
+        binding.webView.getSettings().setAllowFileAccess(true);
+        binding.webView.getSettings().setAllowContentAccess(true);
+        binding.webView.getSettings().setMixedContentMode(android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+        
+        // 设置WebChromeClient以支持JavaScript对话框
+        binding.webView.setWebChromeClient(new android.webkit.WebChromeClient() {
+            @Override
+            public boolean onJsConfirm(android.webkit.WebView view, String url, String message, 
+                                      android.webkit.JsResult result) {
+                new android.app.AlertDialog.Builder(MainActivity.this)
+                    .setTitle("确认")
+                    .setMessage(message)
+                    .setPositiveButton("确定", (dialog, which) -> result.confirm())
+                    .setNegativeButton("取消", (dialog, which) -> result.cancel())
+                    .setOnCancelListener(dialog -> result.cancel())
+                    .create()
+                    .show();
+                return true;
+            }
+            
+            @Override
+            public boolean onJsAlert(android.webkit.WebView view, String url, String message, 
+                                    android.webkit.JsResult result) {
+                new android.app.AlertDialog.Builder(MainActivity.this)
+                    .setTitle("提示")
+                    .setMessage(message)
+                    .setPositiveButton("确定", (dialog, which) -> result.confirm())
+                    .setOnCancelListener(dialog -> result.confirm())
+                    .create()
+                    .show();
+                return true;
+            }
+        });
+        
+        binding.webView.addJavascriptInterface(new WebAppInterface(), "Android");
+        binding.webView.loadUrl("file:///android_asset/console.html");
+    }
+
+    public class WebAppInterface {
+        @JavascriptInterface
+        public String evaluate(String expression) {
+            return symjaEngine.evaluate(expression);
+        }
+
+        @JavascriptInterface
+        public String reset() {
+            symjaEngine.reset();
+            return "已重置计算器状态,所有变量已清除";
+        }
+
+        @JavascriptInterface
+        public String getCompletions(String prefix) {
+            return symjaEngine.getCompletionsJson(prefix);
+        }
+
+        @JavascriptInterface
+        public String getPlugins() {
+            return pluginManager.getPluginsJson();
+        }
+
+        @JavascriptInterface
+        public String reloadPlugins() {
+            pluginManager.reloadPlugins();
+            return pluginManager.getPluginsJson();
+        }
+
+        @JavascriptInterface
+        public String savePlugin(String pluginJson) {
+            return pluginManager.savePlugin(pluginJson);
+        }
+
+        @JavascriptInterface
+        public String deletePlugin(String pluginId) {
+            return pluginManager.deletePlugin(pluginId);
+        }
+
+        @JavascriptInterface
+        public String getHelp() {
+            return "═══════════════════════════════════════════════════\n" +
+                    "    Symja 符号计算控制台 - 完整帮助文档\n" +
+                    "═══════════════════════════════════════════════════\n\n" +
+                    
+                    "【基本操作】\n" +
+                    "• 输入表达式后按回车或点击\"计算\"按钮执行\n" +
+                    "• 使用 ↑↓ 按钮浏览历史命令\n" +
+                    "• 使用 Tab 按钮自动补全函数名\n" +
+                    "• 使用 ⏎+ 按钮进入多行输入模式\n" +
+                    "• 使用 ✕ 按钮清空当前输入\n" +
+                    "• 使用 % 引用上一次的输出结果\n\n" +
+                    
+                    "【变量赋值】\n" +
+                    "  a = 5              // 定义变量\n" +
+                    "  b = 10\n" +
+                    "  c = a + b          // 使用变量\n" +
+                    "  f = x^2 + 3*x      // 定义函数表达式\n" +
+                    "  %                  // 引用上一次的输出\n" +
+                    "  % + 10             // 在上一次结果基础上计算\n\n" +
+                    
+                    "【微积分】\n" +
+                    "  D[x^2, x]                    // 求导\n" +
+                    "  D[Sin[x]*Cos[x], x]          // 复合函数求导\n" +
+                    "  Integrate[x^2, x]            // 不定积分\n" +
+                    "  Integrate[x^2, {x, 0, 1}]    // 定积分\n" +
+                    "  Limit[Sin[x]/x, x -> 0]      // 求极限\n" +
+                    "  Series[Exp[x], {x, 0, 5}]    // 泰勒展开\n" +
+                    "  Sum[i^2, {i, 1, 10}]         // 求和\n\n" +
+                    
+                    "【代数运算】\n" +
+                    "  Expand[(x+1)^3]              // 展开\n" +
+                    "  Factor[x^2-4]                // 因式分解\n" +
+                    "  Simplify[Sin[x]^2+Cos[x]^2]  // 化简\n" +
+                    "  Together[(1/x)+(1/y)]        // 通分\n" +
+                    "  Apart[(x+1)/(x^2-1), x]      // 部分分式\n\n" +
+                    
+                    "【方程求解】\n" +
+                    "  Solve[x^2-5*x+6==0, x]       // 解方程\n" +
+                    "  Solve[{x+y==1, x-y==3}, {x,y}] // 方程组\n" +
+                    "  NSolve[x^3-2*x+1==0, x]      // 数值求解\n" +
+                    "  Roots[x^2-4==0, x]           // 求根\n\n" +
+                    
+                    "【矩阵运算】\n" +
+                    "  {{1,2},{3,4}}                // 定义矩阵\n" +
+                    "  Det[{{1,2},{3,4}}]           // 行列式\n" +
+                    "  Inverse[{{1,2},{3,4}}]       // 逆矩阵\n" +
+                    "  Transpose[{{1,2},{3,4}}]     // 转置\n" +
+                    "  Eigenvalues[{{1,2},{3,4}}]   // 特征值\n\n" +
+                    
+                    "【数论】\n" +
+                    "  GCD[12, 18]                  // 最大公约数\n" +
+                    "  LCM[12, 18]                  // 最小公倍数\n" +
+                    "  FactorInteger[60]            // 质因数分解\n" +
+                    "  PrimeQ[17]                   // 判断素数\n" +
+                    "  Prime[10]                    // 第n个素数\n" +
+                    "  Fibonacci[10]                // 斐波那契数\n" +
+                    "  Binomial[5, 2]               // 组合数\n\n" +
+                    
+                    "【三角函数】\n" +
+                    "  Sin[x], Cos[x], Tan[x]       // 基本三角函数\n" +
+                    "  ArcSin[x], ArcCos[x]         // 反三角函数\n" +
+                    "  Sinh[x], Cosh[x]             // 双曲函数\n" +
+                    "  TrigExpand[Sin[2*x]]         // 三角展开\n\n" +
+                    
+                    "【复数运算】\n" +
+                    "  3 + 4*I                      // 定义复数\n" +
+                    "  Re[3+4*I]                    // 实部\n" +
+                    "  Im[3+4*I]                    // 虚部\n" +
+                    "  Abs[3+4*I]                   // 模\n" +
+                    "  Conjugate[3+4*I]             // 共轭\n\n" +
+                    
+                    "【数值计算】\n" +
+                    "  N[Pi, 50]                    // 高精度计算\n" +
+                    "  N[Sqrt[2]]                   // 数值化\n" +
+                    "  Round[3.7]                   // 四舍五入\n\n" +
+                    
+                    "【变换与级数】\n" +
+                    "  FourierTransform[Exp[-x^2], x, w]  // 傅里叶变换\n" +
+                    "  LaplaceTransform[Sin[t], t, s]     // 拉普拉斯变换\n" +
+                    "  InverseLaplaceTransform[1/(s^2+1), s, t]  // 逆拉普拉斯\n" +
+                    "  Series[Exp[x], {x, 0, 5}]          // 泰勒级数展开\n" +
+                    "  Sum[1/n^2, {n, 1, Infinity}]       // 无穷级数求和\n\n" +
+                    
+                    "【优化与极值】\n" +
+                    "  Maximize[x^2-4*x+3, x]             // 求最大值\n" +
+                    "  Minimize[x^2-4*x+3, x]             // 求最小值\n" +
+                    "  FindMinimum[x^4-3*x^2+2, x]        // 数值求最小值\n\n" +
+                    
+                    "【表格与数据】\n" +
+                    "  Table[i^2, {i, 1, 10}]             // 生成表格\n" +
+                    "  Table[Sin[x], {x, 0, Pi, Pi/10}]   // 带步长的表格\n" +
+                    "  Range[1, 10]                       // 生成数列\n" +
+                    "  Range[0, 1, 0.1]                   // 带步长的数列\n\n" +
+                    
+                    "【高级数学】\n" +
+                    "  DSolve[y'[x]==y[x], y[x], x]       // 微分方程求解\n" +
+                    "  RSolve[a[n]==2*a[n-1]+1, a[n], n]  // 递推方程\n" +
+                    "  Residue[1/(z^2+1), {z, I}]         // 留数计算\n\n" +
+                    
+                    "【特殊命令】\n" +
+                    "  help / ?        显示此帮助\n" +
+                    "  clear / cls     清空输出\n" +
+                    "  reset           重置所有变量\n" +
+                    "  history         显示命令历史\n" +
+                    "  vars            显示已定义变量及其值\n" +
+                    "  save <name>     保存当前会话\n" +
+                    "  plugins         打开插件管理\n" +
+                    "  feedback        显示反馈邮箱\n" +
+                    "  clear <var>     清除特定变量\n\n" +
+                    
+                    "【常用常量】\n" +
+                    "  Pi              圆周率 π\n" +
+                    "  E               自然常数 e\n" +
+                    "  I               虚数单位 i\n" +
+                    "  Infinity        无穷大 ∞\n\n" +
+                    
+                    "【格式转换】\n" +
+                    "  TeXForm[Sin[x]^2 + Cos[x]^2]     // 转换为 LaTeX 格式\n" +
+                    "  InputForm[expr]                  // 转换为输入格式\n" +
+                    "  FullForm[expr]                   // 显示完整形式\n\n" +
+                    
+                    "【提示】\n" +
+                    "• 函数名首字母大写(如 Sin, Cos, Log, TeXForm)\n" +
+                    "• 使用方括号 [] 表示函数参数\n" +
+                    "• 使用花括号 {} 表示列表或范围\n" +
+                    "• 乘法可以使用 * 或空格\n" +
+                    "• 使用 % 可以引用上一次的计算结果\n" +
+                    "• 支持多种绘图类型:函数图、参数方程、极坐标、列表图\n" +
+                    "• 支持高级数学:傅里叶变换、拉普拉斯变换、微分方程\n" +
+                    "• 点击\"插件\"按钮查看更多扩展功能\n\n" +
+                    
+                    "═══════════════════════════════════════════════════";
+        }
+
+        @JavascriptInterface
+        public String symjaEval(String expression) {
+            // 供插件调用的 Symja 计算接口
+            return symjaEngine.evaluate(expression);
+        }
+
+        @JavascriptInterface
+        public String toTeX(String expression) {
+            return symjaEngine.toTeX(expression);
+        }
+
+        @JavascriptInterface
+        public String getDefinedVariables() {
+            return symjaEngine.getDefinedVariables();
+        }
+
+        @JavascriptInterface
+        public String clearVariable(String varName) {
+            return symjaEngine.clearVariable(varName);
+        }
+
+        @JavascriptInterface
+        public String table(String expression, String variable, String start, String end, String step) {
+            return symjaEngine.table(expression, variable, start, end, step);
+        }
+
+        @JavascriptInterface
+        public String maximize(String expression, String variable) {
+            return symjaEngine.maximize(expression, variable);
+        }
+
+        @JavascriptInterface
+        public String minimize(String expression, String variable) {
+            return symjaEngine.minimize(expression, variable);
+        }
+
+        @JavascriptInterface
+        public String matrixOp(String operation, String matrix) {
+            return symjaEngine.matrixOperation(operation, matrix);
+        }
+
+        @JavascriptInterface
+        public String limit(String expression, String variable, String point, String direction) {
+            return symjaEngine.limit(expression, variable, point, direction);
+        }
+
+        @JavascriptInterface
+        public String series(String expression, String variable, String point, int order) {
+            return symjaEngine.series(expression, variable, point, order);
+        }
+
+        @JavascriptInterface
+        public String sum(String expression, String variable, String start, String end) {
+            return symjaEngine.sum(expression, variable, start, end);
+        }
+
+        @JavascriptInterface
+        public String product(String expression, String variable, String start, String end) {
+            return symjaEngine.product(expression, variable, start, end);
+        }
+
+        @JavascriptInterface
+        public String numericEval(String expression, int precision) {
+            return symjaEngine.numericEval(expression, precision);
+        }
+
+        @JavascriptInterface
+        public String trigSimplify(String expression) {
+            return symjaEngine.trigSimplify(expression);
+        }
+
+        @JavascriptInterface
+        public String apart(String expression, String variable) {
+            return symjaEngine.apart(expression, variable);
+        }
+
+        @JavascriptInterface
+        public String together(String expression) {
+            return symjaEngine.together(expression);
+        }
+
+        @JavascriptInterface
+        public String gcd(String a, String b) {
+            return symjaEngine.gcd(a, b);
+        }
+
+        @JavascriptInterface
+        public String lcm(String a, String b) {
+            return symjaEngine.lcm(a, b);
+        }
+
+        @JavascriptInterface
+        public String factorInteger(String n) {
+            return symjaEngine.factorInteger(n);
+        }
+
+        @JavascriptInterface
+        public String isPrime(String n) {
+            return symjaEngine.isPrime(n);
+        }
+
+        @JavascriptInterface
+        public String fibonacci(int n) {
+            return symjaEngine.fibonacci(n);
+        }
+
+        @JavascriptInterface
+        public String binomial(String n, String k) {
+            return symjaEngine.binomial(n, k);
+        }
+
+        @JavascriptInterface
+        public void hideKeyboard() {
+            runOnUiThread(() -> {
+                try {
+                    android.view.inputmethod.InputMethodManager imm = 
+                        (android.view.inputmethod.InputMethodManager) getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
+                    if (imm != null && getCurrentFocus() != null) {
+                        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            });
+        }
+    }
+}

+ 225 - 0
app/src/main/java/com/nonvioce/ijs/PluginManager.java

@@ -0,0 +1,225 @@
+package com.nonvioce.ijs;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import androidx.core.content.FileProvider;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+class PluginInput {
+    String name;
+    String label;
+    String type;
+    String defaultValue;
+    String placeholder;
+
+    public PluginInput(String name, String label, String type, String defaultValue, String placeholder) {
+        this.name = name;
+        this.label = label;
+        this.type = type;
+        this.defaultValue = defaultValue;
+        this.placeholder = placeholder;
+    }
+}
+
+class Plugin {
+    String id;
+    String name;
+    String description;
+    String icon;
+    String command;
+    List<PluginInput> inputs;
+    String jsCode;
+
+    public Plugin(String id, String name, String description, String icon, String command, List<PluginInput> inputs, String jsCode) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.icon = icon;
+        this.command = command;
+        this.inputs = inputs;
+        this.jsCode = jsCode;
+    }
+}
+
+public class PluginManager {
+    private final Context context;
+    private final List<Plugin> plugins;
+    private final File pluginsDir;
+
+    public PluginManager(Context context) {
+        this.context = context;
+        this.plugins = new ArrayList<>();
+        
+        // 创建插件目录
+        this.pluginsDir = new File(context.getExternalFilesDir(null), "plugins");
+        if (!pluginsDir.exists()) {
+            pluginsDir.mkdirs();
+            createSamplePlugins();
+        }
+        loadPlugins();
+    }
+
+    private void createSamplePlugins() {
+        // 不再创建示例插件
+    }
+
+    private void writeFile(File file, String content) throws Exception {
+        java.io.FileWriter writer = new java.io.FileWriter(file);
+        writer.write(content);
+        writer.close();
+    }
+
+    private void loadPlugins() {
+        plugins.clear();
+        File[] files = pluginsDir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.getName().endsWith(".json")) {
+                    try {
+                        String content = readFile(file);
+                        JSONObject json = new JSONObject(content);
+                        Plugin plugin = parsePlugin(json);
+                        plugins.add(plugin);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    private String readFile(File file) throws Exception {
+        StringBuilder content = new StringBuilder();
+        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(file));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            content.append(line).append("\n");
+        }
+        reader.close();
+        return content.toString();
+    }
+
+    private Plugin parsePlugin(JSONObject json) throws Exception {
+        JSONArray inputsArray = json.getJSONArray("inputs");
+        List<PluginInput> inputs = new ArrayList<>();
+        
+        for (int i = 0; i < inputsArray.length(); i++) {
+            JSONObject inputJson = inputsArray.getJSONObject(i);
+            inputs.add(new PluginInput(
+                inputJson.getString("name"),
+                inputJson.getString("label"),
+                inputJson.optString("type", "text"),
+                inputJson.optString("defaultValue", ""),
+                inputJson.optString("placeholder", "")
+            ));
+        }
+
+        return new Plugin(
+            json.getString("id"),
+            json.getString("name"),
+            json.optString("description", ""),
+            json.optString("icon", "🔌"),
+            json.optString("command", ""),
+            inputs,
+            json.getString("jsCode")
+        );
+    }
+
+    public void reloadPlugins() {
+        loadPlugins();
+    }
+
+    public String getPluginsJson() {
+        JSONArray jsonArray = new JSONArray();
+        for (Plugin plugin : plugins) {
+            try {
+                JSONObject pluginJson = new JSONObject();
+                pluginJson.put("id", plugin.id);
+                pluginJson.put("name", plugin.name);
+                pluginJson.put("description", plugin.description);
+                pluginJson.put("icon", plugin.icon);
+                pluginJson.put("command", plugin.command);
+                pluginJson.put("jsCode", plugin.jsCode);
+                
+                JSONArray inputsArray = new JSONArray();
+                for (PluginInput input : plugin.inputs) {
+                    JSONObject inputJson = new JSONObject();
+                    inputJson.put("name", input.name);
+                    inputJson.put("label", input.label);
+                    inputJson.put("type", input.type);
+                    inputJson.put("defaultValue", input.defaultValue);
+                    inputJson.put("placeholder", input.placeholder);
+                    inputsArray.put(inputJson);
+                }
+                pluginJson.put("inputs", inputsArray);
+                
+                jsonArray.put(pluginJson);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return jsonArray.toString();
+    }
+
+    public String savePlugin(String pluginJson) {
+        try {
+            JSONObject json = new JSONObject(pluginJson);
+            String id = json.getString("id");
+            String name = json.getString("name");
+            
+            // 检查是否已存在
+            File pluginFile = new File(pluginsDir, id + ".json");
+            boolean isUpdate = pluginFile.exists();
+            
+            // 保存或更新插件
+            writeFile(pluginFile, pluginJson);
+            
+            // 重新加载
+            loadPlugins();
+            
+            if (isUpdate) {
+                return "✅ 插件已存在,已更新: " + name;
+            } else {
+                return "✅ 插件导入成功: " + name;
+            }
+        } catch (Exception e) {
+            return "❌ 导入失败: " + e.getMessage();
+        }
+    }
+
+    public String deletePlugin(String pluginId) {
+        try {
+            File pluginFile = new File(pluginsDir, pluginId + ".json");
+            if (pluginFile.exists()) {
+                String pluginName = "";
+                for (Plugin p : plugins) {
+                    if (p.id.equals(pluginId)) {
+                        pluginName = p.name;
+                        break;
+                    }
+                }
+                
+                if (pluginFile.delete()) {
+                    loadPlugins();
+                    return "插件已删除: " + pluginName;
+                } else {
+                    return "删除失败";
+                }
+            } else {
+                return "插件不存在";
+            }
+        } catch (Exception e) {
+            return "删除失败: " + e.getMessage();
+        }
+    }
+
+    public String getPluginsFolderPath() {
+        return pluginsDir.getAbsolutePath();
+    }
+}

+ 614 - 0
app/src/main/java/com/nonvioce/ijs/SymjaEngine.java

@@ -0,0 +1,614 @@
+package com.nonvioce.ijs;
+
+import org.json.JSONArray;
+import org.matheclipse.core.eval.ExprEvaluator;
+import org.matheclipse.core.expression.F;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SymjaEngine {
+    // 使用单个evaluator实例保持会话状态,这样变量会被保留
+    private final ExprEvaluator evaluator;
+    
+    // 保存上一次的输出结果
+    private String lastOutput = "";
+    
+    // Symja 常用函数列表(用于自动补全)
+    private final List<String> functions;
+    private final List<String> constants;
+
+    public SymjaEngine() {
+        evaluator = new ExprEvaluator(false, (short) 100);
+        
+        // 初始化Symja引擎
+        F.initSymbols();
+        
+        // 设置更多选项以支持高级功能
+        try {
+            // 启用数值计算
+            evaluator.eval("$RecursionLimit = 256");
+            // 设置精度
+            evaluator.eval("$MaxPrecision = 100");
+        } catch (Exception e) {
+            // 忽略配置错误
+        }
+        
+        // 初始化函数列表
+        functions = Arrays.asList(
+            // 基本数学函数
+            "Abs", "Sign", "Max", "Min", "Sqrt", "Power", "Exp", "Log", "Log10", "Log2",
+            
+            // 三角函数
+            "Sin", "Cos", "Tan", "Cot", "Sec", "Csc",
+            "ArcSin", "ArcCos", "ArcTan", "ArcCot", "ArcSec", "ArcCsc",
+            "Sinh", "Cosh", "Tanh", "Coth", "Sech", "Csch",
+            "ArcSinh", "ArcCosh", "ArcTanh", "ArcCoth", "ArcSech", "ArcCsch",
+            
+            // 三角函数展开和化简
+            "TrigExpand", "TrigReduce", "TrigToExp", "ExpToTrig",
+            
+            // 微积分
+            "D", "Derivative", "Integrate", "Limit", "Sum", "Product", "Series",
+            "DSolve", "NDSolve",
+            
+            // 代数
+            "Expand", "Factor", "Simplify", "FullSimplify", "Together", "Apart",
+            "Cancel", "ExpandAll", "Collect", "Coefficient", "Numerator", "Denominator",
+            
+            // 方程求解
+            "Solve", "NSolve", "Reduce", "FindRoot", "Eliminate", "Roots",
+            
+            // 矩阵运算
+            "Det", "Inverse", "Transpose", "Eigenvalues", "Eigenvectors",
+            "MatrixPower", "MatrixRank", "Trace", "Norm", "IdentityMatrix",
+            "DiagonalMatrix", "RowReduce",
+            
+            // 线性代数
+            "Dot", "Cross", "LinearSolve", "LeastSquares", "QRDecomposition",
+            "LUDecomposition", "CholeskyDecomposition", "SingularValueDecomposition",
+            
+            // 多项式
+            "PolynomialQ", "PolynomialQuotient", "PolynomialRemainder",
+            "PolynomialGCD", "PolynomialLCM", "Roots", "CoefficientList",
+            "Exponent", "Variables",
+            
+            // 数论
+            "Prime", "PrimeQ", "NextPrime", "PrimePi", "FactorInteger",
+            "GCD", "LCM", "Mod", "Divisors", "EulerPhi", "MoebiusMu",
+            "DivisorSigma", "JacobiSymbol",
+            
+            // 组合数学
+            "Factorial", "Binomial", "Multinomial", "Permutations", "Combinations",
+            "Fibonacci", "LucasL", "BernoulliB", "StirlingS1", "StirlingS2",
+            "CatalanNumber", "PartitionsP",
+            
+            // 特殊函数
+            "Gamma", "Beta", "Erf", "Erfc", "BesselJ", "BesselY", "BesselI", "BesselK",
+            "HermiteH", "LaguerreL", "LegendreP", "ChebyshevT", "ChebyshevU",
+            "Zeta", "PolyGamma", "HarmonicNumber",
+            
+            // 统计函数
+            "Mean", "Median", "Variance", "StandardDeviation",
+            "Covariance", "Correlation", "Quantile", "Quartiles",
+            
+            // 列表操作
+            "Table", "Range", "Length", "First", "Last", "Rest", "Most",
+            "Take", "Drop", "Join", "Append", "Prepend", "Insert", "Delete",
+            "Select", "Map", "Apply", "Sort", "Reverse", "Flatten", "Partition",
+            "Riffle", "Split", "Union", "Intersection", "Complement",
+            
+            // 逻辑运算
+            "And", "Or", "Not", "Xor", "Implies", "Equivalent",
+            "Equal", "Unequal", "Less", "Greater", "LessEqual", "GreaterEqual",
+            
+            // 条件和控制
+            "If", "Which", "Switch", "Do", "While", "For", "Nest", "NestList",
+            
+            // 数值计算
+            "N", "Round", "Floor", "Ceiling", "Rationalize", "RealDigits",
+            "Precision", "Accuracy", "Chop",
+            
+            // 复数
+            "Re", "Im", "Conjugate", "Arg", "ComplexExpand", "Abs",
+            
+            // 随机数
+            "Random", "RandomInteger", "RandomReal", "RandomComplex", "SeedRandom",
+            "RandomChoice", "RandomSample",
+            
+            // 变换
+            "FourierTransform", "InverseFourierTransform", 
+            "LaplaceTransform", "InverseLaplaceTransform",
+            "ZTransform", "InverseZTransform",
+            
+            // 微分方程
+            "DSolve", "NDSolve", "RSolve",
+            
+            // 优化
+            "Minimize", "Maximize", "FindMinimum", "FindMaximum",
+            "NMinimize", "NMaximize",
+            
+            // 插值与拟合
+            "Interpolation", "Fit", "LinearModelFit", "FindFit",
+            
+            // 集合操作
+            "Union", "Intersection", "Complement", "Subsets", "MemberQ",
+            
+            // 字符串操作
+            "StringLength", "StringJoin", "StringReverse", "StringTake",
+            "StringDrop", "StringReplace", "StringSplit", "ToString",
+            
+            // 日期时间
+            "Now", "Today", "DateString", "AbsoluteTime", "DateList",
+            
+            // 单位转换
+            "Quantity", "UnitConvert",
+            
+            // 替换规则
+            "ReplaceAll", "Replace", "ReplaceRepeated",
+            
+            // 函数操作
+            "Function", "Composition", "Identity", "InverseFunction",
+            
+            // 其他实用函数
+            "Timing", "MemoryInUse", "ByteCount", "Hash", "Total", "Accumulate",
+            "Position", "Count", "Cases", "DeleteCases",
+            
+            // 格式转换
+            "TeXForm", "InputForm", "FullForm", "JavaForm", "CForm", "FortranForm",
+            "TraditionalForm", "StandardForm"
+        );
+        
+        // 常用常量
+        constants = Arrays.asList(
+            "Pi", "E", "I", "Infinity", "ComplexInfinity",
+            "True", "False", "Null"
+        );
+    }
+
+    public String evaluate(String expression) {
+        try {
+            if (expression == null || expression.trim().isEmpty()) {
+                return "";
+            }
+
+            // 检查是否是特殊函数调用
+            String trimmed = expression.trim();
+            if (trimmed.matches("(?i)^TeXForm\\s*\\[.*\\]\\s*$")) {
+                return "##TEXFORM##" + toTeX(expression);
+            }
+
+            // 替换 % 为上一次的输出
+            if (trimmed.contains("%") && !lastOutput.isEmpty()) {
+                // 将 % 替换为上一次的输出值
+                trimmed = trimmed.replace("%", "(" + lastOutput + ")");
+            }
+
+            // 使用Symja评估表达式
+            Object result = evaluator.eval(trimmed);
+            String resultStr = result.toString();
+            
+            // 保存这次的输出(不包括错误和特殊命令)
+            if (!resultStr.startsWith("错误:") && !resultStr.startsWith("##")) {
+                lastOutput = resultStr;
+            }
+            
+            return resultStr;
+        } catch (Exception e) {
+            return "错误: " + (e.getMessage() != null ? e.getMessage() : "无法计算表达式");
+        }
+    }
+
+    public void reset() {
+        // 重置计算器状态,清除所有变量
+        evaluator.clearVariables();
+        // 清除上一次的输出
+        lastOutput = "";
+    }
+
+    public List<String> getCompletions(String prefix) {
+        List<String> allSymbols = new ArrayList<>();
+        allSymbols.addAll(functions);
+        allSymbols.addAll(constants);
+        
+        if (prefix == null || prefix.isEmpty()) {
+            return allSymbols;
+        }
+        
+        List<String> result = new ArrayList<>();
+        String lowerPrefix = prefix.toLowerCase();
+        for (String symbol : allSymbols) {
+            if (symbol.toLowerCase().startsWith(lowerPrefix)) {
+                result.add(symbol);
+            }
+        }
+        
+        // 按长度排序
+        result.sort((a, b) -> Integer.compare(a.length(), b.length()));
+        return result;
+    }
+
+    public String getCompletionsJson(String prefix) {
+        List<String> completions = getCompletions(prefix);
+        JSONArray jsonArray = new JSONArray(completions);
+        return jsonArray.toString();
+    }
+
+
+    // 矩阵运算
+    public String matrixOperation(String operation, String matrix) {
+        try {
+            String expr = operation + "[" + matrix + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求极限
+    public String limit(String expression, String variable, String point, String direction) {
+        try {
+            String expr;
+            if (direction != null && !direction.isEmpty()) {
+                expr = "Limit[" + expression + ", " + variable + " -> " + point + ", Direction -> " + direction + "]";
+            } else {
+                expr = "Limit[" + expression + ", " + variable + " -> " + point + "]";
+            }
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 级数展开
+    public String series(String expression, String variable, String point, int order) {
+        try {
+            String expr = "Series[" + expression + ", {" + variable + ", " + point + ", " + order + "}]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求和
+    public String sum(String expression, String variable, String start, String end) {
+        try {
+            String expr = "Sum[" + expression + ", {" + variable + ", " + start + ", " + end + "}]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求积
+    public String product(String expression, String variable, String start, String end) {
+        try {
+            String expr = "Product[" + expression + ", {" + variable + ", " + start + ", " + end + "}]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 数值计算
+    public String numericEval(String expression, int precision) {
+        try {
+            String expr = "N[" + expression + ", " + precision + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 化简三角函数
+    public String trigSimplify(String expression) {
+        try {
+            String expr = "TrigExpand[TrigReduce[" + expression + "]]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 部分分式分解
+    public String apart(String expression, String variable) {
+        try {
+            String expr = "Apart[" + expression + ", " + variable + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 通分
+    public String together(String expression) {
+        try {
+            String expr = "Together[" + expression + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 替换
+    public String replace(String expression, String rule) {
+        try {
+            String expr = expression + " /. " + rule;
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求最大公约数
+    public String gcd(String a, String b) {
+        try {
+            String expr = "GCD[" + a + ", " + b + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求最小公倍数
+    public String lcm(String a, String b) {
+        try {
+            String expr = "LCM[" + a + ", " + b + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 因数分解(整数)
+    public String factorInteger(String n) {
+        try {
+            String expr = "FactorInteger[" + n + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 判断素数
+    public String isPrime(String n) {
+        try {
+            String expr = "PrimeQ[" + n + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求第n个素数
+    public String nthPrime(int n) {
+        try {
+            String expr = "Prime[" + n + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 组合数
+    public String binomial(String n, String k) {
+        try {
+            String expr = "Binomial[" + n + ", " + k + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 排列数
+    public String permutations(String n, String k) {
+        try {
+            String expr = "Permutations[Range[" + n + "], {" + k + "}]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 斐波那契数
+    public String fibonacci(int n) {
+        try {
+            String expr = "Fibonacci[" + n + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 复数运算
+    public String complexPart(String expression, String part) {
+        try {
+            String expr = part + "[" + expression + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求根
+    public String roots(String polynomial, String variable) {
+        try {
+            String expr = "Roots[" + polynomial + " == 0, " + variable + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 多项式除法
+    public String polynomialQuotient(String p, String q, String variable) {
+        try {
+            String expr = "PolynomialQuotient[" + p + ", " + q + ", " + variable + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 多项式余数
+    public String polynomialRemainder(String p, String q, String variable) {
+        try {
+            String expr = "PolynomialRemainder[" + p + ", " + q + ", " + variable + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 拉普拉斯变换
+    public String laplaceTransform(String expression, String t, String s) {
+        try {
+            String expr = "LaplaceTransform[" + expression + ", " + t + ", " + s + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 傅里叶变换
+    public String fourierTransform(String expression, String t, String w) {
+        try {
+            String expr = "FourierTransform[" + expression + ", " + t + ", " + w + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+
+
+
+    // 表达式转 LaTeX
+    public String toTeX(String expression) {
+        try {
+            // 如果已经是 TeXForm[...] 格式,直接评估
+            if (expression.trim().matches("(?i)^TeXForm\\s*\\[.*\\]\\s*$")) {
+                Object result = evaluator.eval(expression);
+                return result.toString();
+            }
+            // 否则包装为 TeXForm
+            String expr = "TeXForm[" + expression + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 获取所有已定义变量
+    public String getDefinedVariables() {
+        try {
+            // 尝试多种方法获取变量列表
+            
+            // 方法1: 使用 Names
+            Object result = evaluator.eval("Names[\"Global`*\"]");
+            String symjaList = result.toString();
+            android.util.Log.d("SymjaEngine", "Names result: " + symjaList);
+            
+            // 检查是否返回了有效的列表
+            if (symjaList.startsWith("{") && symjaList.endsWith("}")) {
+                String content = symjaList.substring(1, symjaList.length() - 1).trim();
+                android.util.Log.d("SymjaEngine", "Content after removing braces: " + content);
+                
+                if (!content.isEmpty()) {
+                    // 分割变量名并构建 JSON 数组
+                    String[] vars = content.split(",");
+                    StringBuilder jsonArray = new StringBuilder("[");
+                    for (int i = 0; i < vars.length; i++) {
+                        if (i > 0) jsonArray.append(",");
+                        jsonArray.append("\"").append(vars[i].trim()).append("\"");
+                    }
+                    jsonArray.append("]");
+                    
+                    String finalResult = jsonArray.toString();
+                    android.util.Log.d("SymjaEngine", "Final JSON result: " + finalResult);
+                    return finalResult;
+                }
+            }
+            
+            // 方法2: 如果 Names 不工作,尝试直接从 evaluator 获取
+            // 这是一个备用方案
+            android.util.Log.d("SymjaEngine", "Names didn't work, trying alternative method");
+            
+            // 返回空数组
+            android.util.Log.d("SymjaEngine", "No variables found, returning []");
+            return "[]";
+        } catch (Exception e) {
+            android.util.Log.e("SymjaEngine", "Error in getDefinedVariables: " + e.getMessage());
+            return "[]";
+        }
+    }
+
+    // 清除特定变量
+    public String clearVariable(String varName) {
+        try {
+            String expr = "Clear[" + varName + "]";
+            evaluator.eval(expr);
+            return "已清除变量: " + varName;
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 表格生成
+    public String table(String expression, String variable, String start, String end, String step) {
+        try {
+            String expr = "Table[" + expression + ", {" + variable + ", " + start + ", " + end + ", " + step + "}]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求最大值
+    public String maximize(String expression, String variable) {
+        try {
+            String expr = "Maximize[" + expression + ", " + variable + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+
+    // 求最小值
+    public String minimize(String expression, String variable) {
+        try {
+            String expr = "Minimize[" + expression + ", " + variable + "]";
+            Object result = evaluator.eval(expr);
+            return result.toString();
+        } catch (Exception e) {
+            return "错误: " + e.getMessage();
+        }
+    }
+}

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 14 - 0
app/src/main/res/drawable/ic_launcher_foreground.xml

@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <group android:scaleX="0.4686"
+      android:scaleY="0.4686"
+      android:translateX="272.0768"
+      android:translateY="272.0768">
+    <path
+        android:pathData="M207.6,347.6v117.1c0,25.8 21,46.8 46.8,46.8 25.7,0 46.8,-21.1 46.8,-46.8V347.6H418c25.7,0 46.8,-21.1 46.8,-46.8s-21,-46.8 -46.8,-46.8H301.1V136.8c0,-25.8 -21,-46.8 -46.8,-46.8 -25.7,0 -46.8,21.1 -46.8,46.8v117.1H90.8c-25.7,0 -46.8,21.1 -46.8,46.8s21,46.8 46.8,46.8h116.9zM605,605.2c-25.7,0 -46.8,21.1 -46.8,46.8s21,46.8 46.8,46.8h327.3c25.7,0 46.8,-21.1 46.8,-46.8s-21,-46.8 -46.8,-46.8H605zM918.2,104.1c-18.7,-18.7 -46.8,-18.7 -65.4,0l-748,749.3c-18.7,18.7 -18.7,46.8 0,65.6 18.7,18.7 46.8,18.7 65.4,0l748,-749.3c18.7,-18.7 18.7,-49.2 0,-65.6zM932.3,792.5H605c-25.7,0 -46.8,21.1 -46.8,46.8 0,25.8 21,46.8 46.8,46.8h327.3c25.7,0 46.8,-21.1 46.8,-46.8 0,-25.8 -21,-46.8 -46.8,-46.8z"
+        android:fillColor="#000000"/>
+  </group>
+</vector>

+ 18 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <WebView
+        android:id="@+id/webView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 6 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+    <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>

+ 6 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+    <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


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