亢宇翔 3 months ago
parent b83552c46b
commit 28790c9c65

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

@ -0,0 +1,25 @@
# 课程笔记共享
#### 介绍
这是软件工程课程设计项目提供课程笔记的上传、OCR文字识别、自动分类与智能推荐功能。推荐遵循“同专业同年级 > 同专业 > 同年级 > 基础学科(如高数)”的原则;高等数学仅在无其他可推荐内容时作为兜底出现。
#### 软件架构
- 客户端AndroidJava、AppCompat、Material、Gradle
- 分层结构:
- 界面层UI登录注册、首页推荐、搜索、笔记详情、个人中心
- 服务层ServiceOCR识别、分类推荐、用户与积分
- 数据访问层RepositorySQLite数据读写与查询
- 数据库SQLite核心数据表包含 `T_User`、`T_Major`、`T_Subject`、`T_Chapter`、`T_Note`、`T_Comment`、`T_Major_Subject` 等,支持专业-学科-年级的关联与笔记基础操作。
#### 安装教程
1. 安装 Android Studio含 Android SDK与 JDK 11+。
2. 克隆项目源代码:`git clone <your-repo-url>` 并打开项目根目录。
3. 构建与运行:
- 命令行执行:`./gradlew.bat :app:assembleDebug`
- 或在 Android Studio 中直接点击 Run 运行到模拟器/真机。
#### 使用说明
1. 注册或登录后,在“个人中心”完善专业与年级信息,以便获得更精准的推荐。
2. 上传笔记(支持图片/PDF系统进行OCR识别与自动分类若不准确可在界面中手动调整章节。
3. 首页“推荐”区域点击“刷新推荐”,按“同专业同年级 > 同专业 > 同年级”优先显示;无非基础内容时展示“高数”作为兜底。
4. 在笔记详情页进行浏览、点赞、评论与下载;积分机制与下载规则可在后续版本中完善。

@ -0,0 +1,26 @@
package net.micode.myapplication;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("net.micode.myapplication", appContext.getPackageName());
}
}

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Android 13+ 读取媒体图片 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Android 12 及以下的读取外部存储(兼容相册选择) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 9 及以下写入公共下载目录 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<!-- HanLP可能需要的网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name="com.example.myapplication.MyNoteApp"
android:allowBackup="true"
android:label="课程笔记共享系统"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity android:name="com.example.myapplication.ui.LoginUI" />
<activity android:name="com.example.myapplication.ui.RegisterUI" />
<activity android:name="com.example.myapplication.ui.MainUI"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.example.myapplication.ui.SearchUI" />
<activity android:name="com.example.myapplication.ui.UploadUI" />
<activity android:name="com.example.myapplication.ui.NoteDetailUI" />
<activity android:name="com.example.myapplication.ui.UserCenterUI" />
<activity android:name="com.example.myapplication.ui.NoteEditUI" />
</application>
</manifest>

@ -0,0 +1,408 @@
package com.example.myapplication;
import android.app.Application;
import com.example.myapplication.repository.DatabaseManager;
public class MyNoteApp extends Application {
@Override
public void onCreate() {
super.onCreate();
try {
DatabaseManager.init(this);
} catch (Exception e) {
android.util.Log.e("MyNoteApp", "Database init failed", e);
}
try {
android.database.sqlite.SQLiteDatabase db = com.example.myapplication.repository.DatabaseManager.getDb();
if (db != null) {
db.beginTransaction();
try {
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (1, '编译原理', 'CS-301', '编译原理是计算机科学中的重要基础课程,主要介绍编译器的设计与实现技术。')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (1, 1, '第一章 编译器概述', 'CS-301-01', '介绍编译器的基本概念、结构和工作原理', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (2, 1, '第二章 词法分析', 'CS-301-02', '讲解词法分析的基本原理和实现方法', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (3, 1, '第三章 语法分析', 'CS-301-03', '深入探讨语法分析的理论和实践', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (4, 1, '第四章 语法制导翻译', 'CS-301-04', '介绍语法制导翻译的基本概念和方法', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (5, 1, '第五章 类型检查', 'CS-301-05', '讲解类型检查的原理和实现', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (6, 1, '第六章 运行时环境', 'CS-301-06', '分析程序运行时的存储管理机制', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (7, 1, '第七章 中间代码生成', 'CS-301-07', '介绍中间代码的生成方法和技术', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (8, 1, '第八章 代码生成', 'CS-301-08', '讲解目标代码生成的基本原理和算法', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (9, 1, '第九章 代码优化', 'CS-301-09', '深入探讨代码优化的各种技术', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (10, 1, '第十章 数据流分析', 'CS-301-10', '介绍数据流分析的理论基础和算法', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (11, 1, '第十一章 循环优化', 'CS-301-11', '专门讨论循环结构的优化技术', 11)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (12, 1, '第十二章 过程间分析', 'CS-301-12', '探讨跨越过程边界的程序分析技术', 12)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 1, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (2, '数据结构', 'CS-303', '数据结构是计算机科学的核心课程,研究数据的组织、存储和管理方法。')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (13, 2, '第一章 绪论', 'CS-303-01', '介绍数据结构的基本概念和算法分析方法', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (14, 2, '第二章 线性表', 'CS-303-02', '讲解线性表的顺序存储和链式存储实现', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (15, 2, '第三章 栈和队列', 'CS-303-03', '分析栈和队列的特性及其应用', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (16, 2, '第四章 串', 'CS-303-04', '研究字符串的存储结构和模式匹配算法', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (17, 2, '第五章 树与二叉树', 'CS-303-05', '介绍树的基本概念和二叉树的性质与操作', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (18, 2, '第六章 树与二叉树的应用', 'CS-303-06', '探讨树结构在实际问题中的应用', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (19, 2, '第七章 图', 'CS-303-07', '讲解图的基本概念和存储表示', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (20, 2, '第八章 图的应用', 'CS-303-08', '分析图在路径查找和网络优化中的应用', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (21, 2, '第九章 查找', 'CS-303-09', '研究各种查找算法的实现和性能', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (22, 2, '第十章 排序', 'CS-303-10', '讲解各种排序算法的原理和实现', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (23, 2, '第十一章 高级数据结构', 'CS-303-11', '介绍高级数据结构及其应用场景', 11)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (24, 2, '第十二章 算法设计与分析', 'CS-303-12', '探讨算法设计策略和复杂度分析', 12)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 2, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (3, '计算机网络', 'CS-304', '计算机网络是研究计算机之间如何相互连接和通信的学科。')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (25, 3, '第一章 计算机网络概述', 'CS-304-01', '介绍计算机网络的基本概念和发展历程', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (26, 3, '第二章 网络体系结构', 'CS-304-02', '讲解网络分层体系结构和各种模型', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (27, 3, '第三章 物理层', 'CS-304-03', '分析物理层的数据传输原理和技术', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (28, 3, '第四章 数据链路层', 'CS-304-04', '研究数据链路层的帧传输和差错控制', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (29, 3, '第五章 局域网', 'CS-304-05', '介绍局域网技术和以太网协议', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (30, 3, '第六章 网络层', 'CS-304-06', '深入分析网络层的路由和寻址机制', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (31, 3, '第七章 传输层', 'CS-304-07', '研究传输层的端到端通信服务', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (32, 3, '第八章 应用层', 'CS-304-08', '介绍常见的网络应用协议和服务', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (33, 3, '第九章 网络安全', 'CS-304-09', '分析网络安全威胁和防护措施', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (34, 3, '第十章 网络管理', 'CS-304-10', '讨论网络管理的方法和技术', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (35, 3, '第十一章 无线网络', 'CS-304-11', '介绍无线通信技术和移动网络', 11)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (36, 3, '第十二章 网络新技术', 'CS-304-12', '探讨新兴网络技术的发展趋势和应用', 12)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 3, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (4, '操作系统', 'CS-302', '操作系统是计算机系统的核心软件,负责管理硬件资源和提供用户接口。')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (37, 4, '第一章 操作系统概述', 'CS-302-01', '介绍操作系统的基本概念、功能和分类', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (38, 4, '第二章 进程管理', 'CS-302-02', '讲解进程的基本概念和管理机制', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (39, 4, '第三章 线程管理', 'CS-302-03', '深入探讨线程的概念和管理方法', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (40, 4, '第四章 进程同步', 'CS-302-04', '分析进程间的同步与互斥问题', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (41, 4, '第五章 内存管理', 'CS-302-05', '讲解内存管理的基本原理和技术', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (42, 4, '第六章 文件系统', 'CS-302-06', '介绍文件系统的组织和管理', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (43, 4, '第七章 I/O系统', 'CS-302-07', '分析输入输出系统的原理和管理', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (44, 4, '第八章 虚拟内存', 'CS-302-08', '深入探讨虚拟内存的实现机制', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (45, 4, '第九章 系统保护与安全', 'CS-302-09', '讨论操作系统的安全保护和访问控制', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (46, 4, '第十章 分布式系统', 'CS-302-10', '介绍分布式操作系统的基本概念和机制', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (47, 4, '第十一章 实时系统', 'CS-302-11', '分析实时操作系统的特点和要求', 11)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (48, 4, '第十二章 Linux内核分析', 'CS-302-12', '深入分析Linux操作系统的内核实现', 12)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 4, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (5, '机器人学导论第4版', 'RBT-INTRO-4e', '机器人学导论课程,涵盖机器人运动学、动力学、控制等核心内容')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (49, 5, '第1章 概述', 'RBT-1', '机器人学基本概念和机械臂几何结构介绍', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (50, 5, '第2章 空间描述和变换', 'RBT-2', '位置、姿态、坐标系变换和欧拉角等数学基础', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (51, 5, '第3章 操作臂运动学', 'RBT-3', '连杆、关节描述和D-H参数法建立运动学方程', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (52, 5, '第4章 操作臂逆运动学', 'RBT-4', '逆运动学的可解性分析和解法研究', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (53, 5, '第5章 速度和静力', 'RBT-5', '线速度、角速度表示和雅可比矩阵分析', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (54, 5, '第6章 操作臂动力学', 'RBT-6', '机器人动力学建模和牛顿-欧拉、拉格朗日方法', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (55, 5, '第7章 轨迹生成', 'RBT-7', '关节空间和笛卡尔空间的轨迹规划方法', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (56, 5, '第8章 操作臂的机械设计', 'RBT-8', '机器人机械结构设计原理', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (57, 5, '第9章 操作臂的线性控制', 'RBT-9', '机器人线性控制系统设计和PID控制', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (58, 5, '第10章 操作臂的非线性控制', 'RBT-10', '机器人非线性控制方法', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (59, 5, '第11章 操作臂的力控制', 'RBT-11', '机器人柔顺控制和力/位混合控制', 11)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (60, 5, '第12章 机器人编程语言与系统', 'RBT-12', '机器人编程语言和系统介绍', 12)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (61, 5, '第13章 离线编程系统', 'RBT-13', '机器人离线仿真和编程技术', 13)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 5, '大三')");
// 高等数学
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (6, '高等数学', 'MATH-101', '涵盖微积分、线性代数、概率论与数理统计等基础数学内容')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (62, 6, '微积分基础', 'MATH-101-01', '极限、导数与积分的基本概念与运算', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (63, 6, '多元微积分', 'MATH-101-02', '多元函数微分与重积分、方向导数与梯度', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (64, 6, '级数', 'MATH-101-03', '数项级数与函数项级数、收敛判别法', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (65, 6, '向量与空间解析几何', 'MATH-101-04', '向量运算、空间直线与平面、二次曲面', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (66, 6, '线性代数基础', 'MATH-101-05', '线性方程组与向量空间、线性变换', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (67, 6, '行列式与矩阵', 'MATH-101-06', '行列式性质、矩阵运算与初等变换', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (68, 6, '特征值与特征向量', 'MATH-101-07', '特征值问题、相似对角化与主成分', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (69, 6, '概率论', 'MATH-101-08', '概率模型、条件概率与全概率公式', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (70, 6, '随机变量与分布', 'MATH-101-09', '常见分布、期望方差与矩母函数', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (71, 6, '数理统计', 'MATH-101-10', '估计、区间估计与检验的基本方法', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (72, 6, '假设检验与方差分析', 'MATH-101-11', '假设检验框架、t检验与ANOVA', 11)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (73, 6, '数值方法初步', 'MATH-101-12', '插值、数值积分与迭代解方程', 12)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 6, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 6, '大一')");
// 大学英语
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (7, '大学英语', 'ENG-101', '大学英语综合教程,涵盖词汇、语法、阅读、写作、听说等能力提升')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (74, 7, '词汇与短语', 'ENG-101-01', '核心词汇与常用短语积累', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (75, 7, '语法与句法', 'ENG-101-02', '时态、语态、从句与复杂句式', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (76, 7, '阅读理解', 'ENG-101-03', '篇章结构与主旨细节理解', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (77, 7, '写作训练', 'ENG-101-04', '段落与文章写作、论证与修辞', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (78, 7, '听力训练', 'ENG-101-05', '学术与生活场景听力理解', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (79, 7, '口语交流', 'ENG-101-06', '发音与口语表达、讨论与演讲', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (80, 7, '翻译与改写', 'ENG-101-07', '汉英/英汉翻译技巧与改写', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (81, 7, '英美文化', 'ENG-101-08', '跨文化理解与背景知识', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (82, 7, '学术英语', 'ENG-101-09', '学术阅读与写作、术语与体裁', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (83, 7, '综合复习', 'ENG-101-10', '综合能力提升与期末复习', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 7, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 7, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (100, '毛泽东思想和中国特色社会主义理论体系概论', 'POL-001', '系统阐释中国化马克思主义的理论与实践')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (84, 100, '第一章 绪论', 'POL-001-01', '课程导论与学习要求', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (85, 100, '第二章 新民主主义革命理论', 'POL-001-02', '中国革命道路与理论', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (86, 100, '第三章 社会主义改造理论', 'POL-001-03', '社会主义改造的理论与实践', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (87, 100, '第四章 社会主义建设探索', 'POL-001-04', '社会主义建设道路探索', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (88, 100, '第五章 改革开放与中国特色社会主义', 'POL-001-05', '改革开放历史进程与理论成果', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (89, 100, '第六章 总体布局与战略布局', 'POL-001-06', '五位一体与四个全面', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (90, 100, '第七章 党的领导与全面从严治党', 'POL-001-07', '党的建设与治理能力现代化', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (91, 100, '第八章 国家治理现代化', 'POL-001-08', '国家治理体系与治理能力现代化', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (92, 100, '第九章 民生与社会建设', 'POL-001-09', '民生改善与社会建设', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (93, 100, '第十章 青年责任与时代使命', 'POL-001-10', '新时代青年的使命担当', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 100, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (101, '思想道德与法治', 'POL-002', '提升思想道德修养与法治素养')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (94, 101, '第一章 道德与人生', 'POL-002-01', '道德的本质与价值', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (95, 101, '第二章 社会主义核心价值观', 'POL-002-02', '核心价值观的培育与践行', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (96, 101, '第三章 法治与法治思维', 'POL-002-03', '法治理念与法治思维', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (97, 101, '第四章 宪法与宪法权利', 'POL-002-04', '宪法基本原则与权利保障', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (98, 101, '第五章 民法典与民事权利', 'POL-002-05', '民法典体系与权利义务', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (99, 101, '第六章 刑法概述', 'POL-002-06', '刑法基本原理与犯罪构成', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (100, 101, '第七章 行政法与行政救济', 'POL-002-07', '行政行为与救济途径', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (101, 101, '第八章 涉网法治与合规', 'POL-002-08', '网络安全法治与合规要求', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (102, 101, '第九章 大学生法治素养', 'POL-002-09', '校园场景中的法治意识', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (103, 101, '第十章 职业伦理与社会责任', 'POL-002-10', '职业道德与社会责任', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 101, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (102, '马克思主义基本原理', 'POL-003', '马克思主义哲学、政治经济学与科学社会主义基本原理')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (104, 102, '第一章 哲学基本问题', 'POL-003-01', '物质与意识的关系', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (105, 102, '第二章 辩证唯物主义', 'POL-003-02', '世界的物质统一性与发展规律', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (106, 102, '第三章 历史唯物主义', 'POL-003-03', '社会历史发展的普遍规律', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (107, 102, '第四章 政治经济学原理', 'POL-003-04', '剩余价值与资本运行', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (108, 102, '第五章 科学社会主义', 'POL-003-05', '社会主义产生、发展与实践', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (109, 102, '第六章 马克思主义中国化', 'POL-003-06', '中国化时代化的理论成果', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (110, 102, '第七章 价值观与人生观', 'POL-003-07', '价值选择与人生道路', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (111, 102, '第八章 社会发展与现代化', 'POL-003-08', '社会现代化与中国式现代化', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (112, 102, '第九章 实践与认识', 'POL-003-09', '实践与认识的辩证统一', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (113, 102, '第十章 理论与方法论', 'POL-003-10', '基本立场、观点与方法', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 102, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (103, '中国近现代史纲要', 'POL-004', '中国近现代史进程与基本规律')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (114, 103, '第一章 近代中国的沦陷与抗争', 'POL-004-01', '近代中国国情与反侵略斗争', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (115, 103, '第二章 辛亥革命与共和探索', 'POL-004-02', '辛亥革命与民国初期探索', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (116, 103, '第三章 中国共产党成立与新民主主义革命', 'POL-004-03', '新民主主义革命的开展', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (117, 103, '第四章 抗日战争与民族解放', 'POL-004-04', '全民族抗战与胜利', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (118, 103, '第五章 解放战争与新中国成立', 'POL-004-05', '全国解放与新中国成立', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (119, 103, '第六章 社会主义改造与建设', 'POL-004-06', '社会主义改造与早期建设', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (120, 103, '第七章 改革开放与现代化建设', 'POL-004-07', '改革开放与现代化进程', 7)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (121, 103, '第八章 中国特色社会主义新时代', 'POL-004-08', '新时代的历史方位与任务', 8)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (122, 103, '第九章 民族复兴与中国梦', 'POL-004-09', '民族复兴的历史逻辑', 9)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (123, 103, '第十章 历史人物与史料研读', 'POL-004-10', '重要历史人物与史料研读', 10)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 103, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (104, '大学体育', 'PE-101', '增强体质与掌握基本运动技能')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (124, 104, '第一章 体育与健康概论', 'PE-101-01', '体育与健康的关系', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (125, 104, '第二章 田径基础', 'PE-101-02', '跑、跳、投的基础技能', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (126, 104, '第三章 球类运动基础', 'PE-101-03', '篮球、足球、排球基础', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (127, 104, '第四章 健身与体能训练', 'PE-101-04', '力量与心肺耐力训练', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (128, 104, '第五章 安全与运动损伤防护', 'PE-101-05', '运动损伤预防与处理', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (129, 104, '第六章 体质评价与管理', 'PE-101-06', '体测与健康管理', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 104, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (105, '军事理论', 'MIL-101', '国防与安全、军事制度与科技')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (130, 105, '第一章 国防概述', 'MIL-101-01', '国家安全与国防建设', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (131, 105, '第二章 现代战争形态', 'MIL-101-02', '信息化战争与联合作战', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (132, 105, '第三章 我国军事制度', 'MIL-101-03', '兵役制度与国防动员', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (133, 105, '第四章 军事科技与装备', 'MIL-101-04', '军事科技发展与武器装备', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (134, 105, '第五章 国家安全与总体安全观', 'MIL-101-05', '总体国家安全观', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10044, 105, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (106, '微积分', 'MATH-201', '函数、极限、导数与积分的系统学习')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (135, 106, '第一章 函数与极限', 'MATH-201-01', '函数与极限的基础', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (136, 106, '第二章 导数与微分', 'MATH-201-02', '导数与微分运算', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (137, 106, '第三章 不定积分', 'MATH-201-03', '积分基本方法', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (138, 106, '第四章 定积分与应用', 'MATH-201-04', '定积分性质与应用', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (139, 106, '第五章 多元函数微分', 'MATH-201-05', '偏导与梯度', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (140, 106, '第六章 重积分与曲线曲面积分', 'MATH-201-06', '重积分与曲线曲面积分', 6)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 106, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (107, '线性代数', 'MATH-202', '矩阵、向量空间与线性变换')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (141, 107, '第一章 行列式与矩阵', 'MATH-202-01', '行列式与矩阵运算', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (142, 107, '第二章 向量与向量空间', 'MATH-202-02', '向量空间与线性相关', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (143, 107, '第三章 线性方程组', 'MATH-202-03', '线性方程组解法', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (144, 107, '第四章 特征值与对角化', 'MATH-202-04', '特征值与相似对角化', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (145, 107, '第五章 正交与最小二乘', 'MATH-202-05', '内积空间与最小二乘', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 107, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (108, '概率论与数理统计', 'MATH-203', '随机变量与统计推断')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (146, 108, '第一章 概率与条件概率', 'MATH-203-01', '概率基础与条件概率', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (147, 108, '第二章 随机变量与分布', 'MATH-203-02', '常见分布与性质', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (148, 108, '第三章 数字特征与联合分布', 'MATH-203-03', '期望方差协方差', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (149, 108, '第四章 大数定律与中心极限定理', 'MATH-203-04', '极限定理', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (150, 108, '第五章 参数估计与假设检验', 'MATH-203-05', '点估计与检验', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 108, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (109, '程序设计基础', 'CS-101', '编程基础语法与问题求解')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (151, 109, '第一章 计算机与程序', 'CS-101-01', '编程概念与开发环境', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (152, 109, '第二章 基本数据类型与运算', 'CS-101-02', '数据类型与运算', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (153, 109, '第三章 选择与循环', 'CS-101-03', '控制结构', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (154, 109, '第四章 函数与递归', 'CS-101-04', '函数封装与递归', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (155, 109, '第五章 数组与字符串', 'CS-101-05', '顺序数据结构', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 109, '大一')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (110, '面向对象程序设计', 'CS-102', '类、对象与抽象机制')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (156, 110, '第一章 面向对象概述', 'CS-102-01', 'OO思想与基本概念', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (157, 110, '第二章 类与对象', 'CS-102-02', '封装与构造', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (158, 110, '第三章 继承与多态', 'CS-102-03', '继承层次与动态绑定', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (159, 110, '第四章 接口与泛型', 'CS-102-04', '接口抽象与泛型编程', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (160, 110, '第五章 异常与IO', 'CS-102-05', '异常处理与文件IO', 5)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 110, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (111, '数字逻辑电路', 'EE-201', '逻辑代数与组合时序电路')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (161, 111, '第一章 逻辑代数基础', 'EE-201-01', '布尔代数与逻辑化简', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (162, 111, '第二章 组合逻辑电路', 'EE-201-02', '组合电路设计', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (163, 111, '第三章 时序逻辑电路', 'EE-201-03', '触发器与时序分析', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (164, 111, '第四章 可编程逻辑器件', 'EE-201-04', 'PAL、FPGA基础', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 111, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (112, '离散数学', 'CS-201', '集合、图与形式逻辑')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (165, 112, '第一章 集合与关系', 'CS-201-01', '集合、关系与函数', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (166, 112, '第二章 逻辑与证明', 'CS-201-02', '命题逻辑与证明方法', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (167, 112, '第三章 图论基础', 'CS-201-03', '图与树结构', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (168, 112, '第四章 组合数学', 'CS-201-04', '计数原理与递推', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 112, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (113, '计算机组成原理', 'CS-202', '指令系统、存储层次与CPU')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (169, 113, '第一章 计算机系统概述', 'CS-202-01', '计算机系统结构', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (170, 113, '第二章 数据表示与运算', 'CS-202-02', '编码与算术运算', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (171, 113, '第三章 存储系统', 'CS-202-03', '主存与Cache', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (172, 113, '第四章 指令系统', 'CS-202-04', '指令格式与寻址', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 113, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (114, '汇编语言', 'CS-203', '汇编语法与底层编程')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (173, 114, '第一章 机器与汇编', 'CS-203-01', '机器指令与汇编基础', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (174, 114, '第二章 寄存器与指令', 'CS-203-02', '寄存器与常用指令', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (175, 114, '第三章 子程序与调用', 'CS-203-03', '过程调用与栈', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 114, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (115, '数据库', 'CS-305', '关系模型、SQL与事务')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (176, 115, '第一章 关系数据库概述', 'CS-305-01', '数据模型与规范化', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (177, 115, '第二章 SQL语言', 'CS-305-02', '查询、更新与视图', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (178, 115, '第三章 事务与并发控制', 'CS-305-03', '事务隔离与锁', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (179, 115, '第四章 索引与优化', 'CS-305-04', '索引结构与优化', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 115, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (116, '机器学习', 'AI-301', '监督学习、无监督与优化')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (180, 116, '第一章 机器学习概述', 'AI-301-01', '基本概念与术语', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (181, 116, '第二章 线性模型', 'AI-301-02', '回归与分类', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (182, 116, '第三章 树模型与集成', 'AI-301-03', '决策树与Boosting', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (183, 116, '第四章 聚类与降维', 'AI-301-04', 'KMeans与PCA', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 116, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (117, '深度学习', 'AI-302', '神经网络、卷积与序列模型')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (184, 117, '第一章 神经网络基础', 'AI-302-01', '前馈网络与反向传播', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (185, 117, '第二章 卷积神经网络', 'AI-302-02', 'CNN结构与应用', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (186, 117, '第三章 循环与序列模型', 'AI-302-03', 'RNN、LSTM、Transformer', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (187, 117, '第四章 训练与正则化', 'AI-302-04', '优化与正则化策略', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 117, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (118, '自然语言处理', 'AI-303', '文本表示、序列建模与生成')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (188, 118, '第一章 文本预处理与表示', 'AI-303-01', '分词、词向量与编码', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (189, 118, '第二章 语言模型', 'AI-303-02', '统计与神经语言模型', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (190, 118, '第三章 序列标注', 'AI-303-03', '标注与序列任务', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (191, 118, '第四章 文本生成与理解', 'AI-303-04', '生成模型与理解', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 118, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (119, '计算机视觉', 'AI-304', '图像处理、特征与检测识别')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (192, 119, '第一章 图像基础', 'AI-304-01', '图像采集与颜色空间', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (193, 119, '第二章 图像滤波与增强', 'AI-304-02', '空间与频域处理', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (194, 119, '第三章 特征提取', 'AI-304-03', '边缘、角点与局部特征', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (195, 119, '第四章 目标检测与识别', 'AI-304-04', '检测识别方法', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 119, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (120, '网络安全', 'SECU-301', '攻击与防御、漏洞与加固')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (196, 120, '第一章 安全概述', 'SECU-301-01', '威胁模型与安全目标', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (197, 120, '第二章 密码学基础', 'SECU-301-02', '对称、非对称与哈希', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (198, 120, '第三章 漏洞与利用', 'SECU-301-03', '常见漏洞类型与利用', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (199, 120, '第四章 网络攻防', 'SECU-301-04', '渗透与防御技术', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 120, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (121, '密码学', 'CRYPTO-301', '现代密码体制与协议')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (200, 121, '第一章 古典密码与现代密码', 'CRYPTO-301-01', '密码发展与概述', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (201, 121, '第二章 对称加密', 'CRYPTO-301-02', '分组与流密码', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (202, 121, '第三章 公钥密码', 'CRYPTO-301-03', 'RSA、ECC等', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (203, 121, '第四章 消息认证与签名', 'CRYPTO-301-04', 'MAC与签名', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 121, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (122, '软件工程', 'SE-301', '需求、设计、实现与维护')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (204, 122, '第一章 软件工程概述', 'SE-301-01', '软件过程与方法', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (205, 122, '第二章 需求工程', 'SE-301-02', '需求获取与分析', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (206, 122, '第三章 架构设计', 'SE-301-03', '架构风格与模式', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (207, 122, '第四章 测试与质量保证', 'SE-301-04', '测试策略与质量', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 122, '大二')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (123, '计算机图形学', 'CG-301', '几何、渲染与图形系统')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (208, 123, '第一章 图形学概述', 'CG-301-01', '渲染管线与基础', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (209, 123, '第二章 几何与变换', 'CG-301-02', '矩阵变换与投影', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (210, 123, '第三章 光照与着色', 'CG-301-03', '光照模型与着色', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (211, 123, '第四章 曲线与曲面', 'CG-301-04', 'Bezier与B样条', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 123, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (124, '数字图像处理', 'DIP-301', '图像采集、增强与分析')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (212, 124, '第一章 图像采集与表示', 'DIP-301-01', '采集与表示基础', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (213, 124, '第二章 图像增强', 'DIP-301-02', '直方图与滤波增强', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (214, 124, '第三章 图像分割', 'DIP-301-03', '阈值、边缘与区域方法', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (215, 124, '第四章 图像特征与理解', 'DIP-301-04', '特征提取与理解', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 124, '大三')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id, name, code, description) VALUES (125, '嵌入式系统', 'EE-301', '嵌入式硬件、RTOS与外设')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (216, 125, '第一章 嵌入式概述', 'EE-301-01', '嵌入式系统组成', 1)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (217, 125, '第二章 处理器与存储', 'EE-301-02', '处理器架构与存储', 2)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (218, 125, '第三章 RTOS基础', 'EE-301-03', '任务、调度与同步', 3)");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id, subject_id, name, code, description, order_num) VALUES (219, 125, '第四章 外设与驱动', 'EE-301-04', 'GPIO、串口与驱动', 4)");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id, subject_id, grade) VALUES (10001, 125, '大三')");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
} catch (Exception e) {
android.util.Log.e("MyNoteApp", "Seed data insert failed", e);
}
// 统一重复课程:将旧科目(2:数据结构,3:计算机网络,4:操作系统,6:高等数学)映射到新科目(20002,20004,20003,20013),并删除旧记录
try {
android.database.sqlite.SQLiteDatabase db = com.example.myapplication.repository.DatabaseManager.getDb();
if (db != null) {
db.beginTransaction();
try {
db.execSQL("UPDATE T_Major_Subject SET subject_id=20002 WHERE subject_id=2");
db.execSQL("UPDATE T_Major_Subject SET subject_id=20004 WHERE subject_id=3");
db.execSQL("UPDATE T_Major_Subject SET subject_id=20003 WHERE subject_id=4");
db.execSQL("UPDATE T_Major_Subject SET subject_id=20013 WHERE subject_id=6");
db.execSQL("DELETE FROM T_Chapter WHERE subject_id IN (2,3,4,6)");
db.execSQL("DELETE FROM T_Subject WHERE id IN (2,3,4,6)");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
} catch (Exception e) {
android.util.Log.e("MyNoteApp", "Dedup subjects failed", e);
}
// 移除清理白名单,允许数据库长期存储更多课程与章节
try {
android.database.sqlite.SQLiteDatabase db = com.example.myapplication.repository.DatabaseManager.getDb();
if (db != null) {
db.beginTransaction();
try {
db.execSQL("UPDATE T_Chapter SET keywords='栈,队列,入栈,出栈,链表', aliases='stack,queue' WHERE id=15");
db.execSQL("UPDATE T_Chapter SET keywords='树,二叉树,遍历,BST', aliases='tree,binary tree' WHERE id=17");
db.execSQL("UPDATE T_Chapter SET keywords='内存,分页,分段,虚拟内存', aliases='memory,vm' WHERE id=41");
db.execSQL("UPDATE T_Chapter SET keywords='IO,设备,驱动,缓冲', aliases='io,i/o' WHERE id=43");
db.execSQL("UPDATE T_Chapter SET keywords='TCP,UDP,端口,连接', aliases='transport layer' WHERE id=31");
db.execSQL("UPDATE T_Chapter SET keywords='HTTP,DNS,SMTP,FTP', aliases='application layer' WHERE id=32");
db.execSQL("UPDATE T_Chapter SET keywords='D-H参数,关节,连杆,运动学', aliases='kinematics' WHERE id=51");
db.execSQL("UPDATE T_Chapter SET keywords='动力学,拉格朗日,牛顿-欧拉', aliases='dynamics' WHERE id=54");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
} catch (Exception e) {
android.util.Log.e("MyNoteApp", "Seed keywords update failed", e);
}
}
}

@ -0,0 +1,16 @@
package com.example.myapplication;
import android.content.Context;
import android.content.SharedPreferences;
import android.widget.Toast;
public class Utils {
public static void toast(Context ctx, String msg) {
Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show();
}
public static String getUser(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
return prefs.getString("currentUser", "张三");
}
}

@ -0,0 +1,31 @@
package com.example.myapplication.domain;
public class Chapter {
private long id;
private String name;
private int orderIndex;
private int courseId;
private String keywords;
private String aliases;
public Chapter() {}
public Chapter(long id, String name, int orderIndex) {
this.id = id;
this.name = name;
this.orderIndex = orderIndex;
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getOrderIndex() { return orderIndex; }
public void setOrderIndex(int orderIndex) { this.orderIndex = orderIndex; }
public int getCourseId() { return courseId; }
public void setCourseId(int courseId) { this.courseId = courseId; }
public String getKeywords() { return keywords; }
public void setKeywords(String keywords) { this.keywords = keywords; }
public String getAliases() { return aliases; }
public void setAliases(String aliases) { this.aliases = aliases; }
}

@ -0,0 +1,30 @@
package com.example.myapplication.domain;
public class Comment {
private long id;
private String uploaderId;
private long noteId;
private String content;
private String createdAt; // ISO string
public Comment() {}
public Comment(long id, String uploaderId, long noteId, String content, String createdAt) {
this.id = id;
this.uploaderId = uploaderId;
this.noteId = noteId;
this.content = content;
this.createdAt = createdAt;
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getUploaderId() { return uploaderId; }
public void setUploaderId(String uploaderId) { this.uploaderId = uploaderId; }
public long getNoteId() { return noteId; }
public void setNoteId(long noteId) { this.noteId = noteId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getCreatedAt() { return createdAt; }
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
}

@ -0,0 +1,26 @@
package com.example.myapplication.domain;
public class Course {
private long id;
private String description;
private String name;
private String code;
public Course() {}
public Course(long id, String description, String name, String code) {
this.id = id;
this.description = description;
this.name = name;
this.code = code;
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
}

@ -0,0 +1,22 @@
package com.example.myapplication.domain;
public class Like {
private String userId;
private long noteId;
private String createdAt;
public Like() {}
public Like(String userId, long noteId, String createdAt) {
this.userId = userId;
this.noteId = noteId;
this.createdAt = createdAt;
}
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public long getNoteId() { return noteId; }
public void setNoteId(long noteId) { this.noteId = noteId; }
public String getCreatedAt() { return createdAt; }
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
}

@ -0,0 +1,26 @@
package com.example.myapplication.domain;
public class Major {
private long id;
private String name;
private String code;
private String description;
public Major() {}
public Major(long id, String name, String code, String description) {
this.id = id;
this.name = name;
this.code = code;
this.description = description;
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}

@ -0,0 +1,76 @@
package com.example.myapplication.domain;
public class Note {
private long id;
private String uploaderId;
private long courseId;
private String content;
private int downloadCount;
private int likeCount;
private String created;
private String title;
private String filePath;
private Integer publisherGrade; // 发布者年级
private String publisherMajor; // 发布者专业
// 额外字段用于 NoteUploadActivity
private String description; // 描述
private String ocrText; // OCR识别文本
private long uploadTime; // 上传时间
private long updateTime; // 更新时间
private long chapterId; // 章节ID
public Note() {}
public Note(long id, String uploaderId, long courseId, String content,
int downloadCount, int likeCount, String created, String title, String filePath,
Integer publisherGrade, String publisherMajor) {
this.id = id;
this.uploaderId = uploaderId;
this.courseId = courseId;
this.content = content;
this.downloadCount = downloadCount;
this.likeCount = likeCount;
this.created = created;
this.title = title;
this.filePath = filePath;
this.publisherGrade = publisherGrade;
this.publisherMajor = publisherMajor;
}
public int getLikeCount() { return likeCount; }
public void setLikeCount(int likeCount) { this.likeCount = likeCount; }
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getUploaderId() { return uploaderId; }
public void setUploaderId(String uploaderId) { this.uploaderId = uploaderId; }
public long getCourseId() { return courseId; }
public void setCourseId(long courseId) { this.courseId = courseId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public int getDownloadCount() { return downloadCount; }
public void setDownloadCount(int downloadCount) { this.downloadCount = downloadCount; }
public String getCreated() { return created; }
public void setCreated(String created) { this.created = created; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public Integer getPublisherGrade() { return publisherGrade; }
public void setPublisherGrade(Integer publisherGrade) { this.publisherGrade = publisherGrade; }
public String getPublisherMajor() { return publisherMajor; }
public void setPublisherMajor(String publisherMajor) { this.publisherMajor = publisherMajor; }
// 新增方法用于 NoteUploadActivity
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getOcrText() { return ocrText; }
public void setOcrText(String ocrText) { this.ocrText = ocrText; }
public long getUploadTime() { return uploadTime; }
public void setUploadTime(long uploadTime) { this.uploadTime = uploadTime; }
public long getUpdateTime() { return updateTime; }
public void setUpdateTime(long updateTime) { this.updateTime = updateTime; }
public long getChapterId() { return chapterId; }
public void setChapterId(long chapterId) { this.chapterId = chapterId; }
}

@ -0,0 +1,66 @@
package com.example.myapplication.domain;
public class User {
private String account;
private String name;
private String password;
private int type;
private String mobile;
private int points;
private Integer grade; // 年级(大几)
private Long majorId; // 专业ID
public User() {}
public User(String account, String name, String password, int type, String mobile) {
this.account = account;
this.name = name;
this.password = password;
this.type = type;
this.mobile = mobile;
this.points = 0;
this.grade = null;
this.majorId = null;
}
// 带积分的构造
public User(String account, String name, String password, int type, String mobile, int points) {
this.account = account;
this.name = name;
this.password = password;
this.type = type;
this.mobile = mobile;
this.points = points;
this.grade = null;
this.majorId = null;
}
// 带年级与专业的构造
public User(String account, String name, String password, int type, String mobile, int points, Integer grade, Long majorId) {
this.account = account;
this.name = name;
this.password = password;
this.type = type;
this.mobile = mobile;
this.points = points;
this.grade = grade;
this.majorId = majorId;
}
public String getAccount() { return account; }
public void setAccount(String account) { this.account = account; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getType() { return type; }
public void setType(int type) { this.type = type; }
public String getMobile() { return mobile; }
public void setMobile(String mobile) { this.mobile = mobile; }
public int getPoints() { return points; }
public void setPoints(int points) { this.points = points; }
public Integer getGrade() { return grade; }
public void setGrade(Integer grade) { this.grade = grade; }
public Long getMajorId() { return majorId; }
public void setMajorId(Long majorId) { this.majorId = majorId; }
}

@ -0,0 +1,66 @@
package com.example.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.myapplication.domain.Comment;
import java.util.ArrayList;
import java.util.List;
public class CommentRepository {
private final Context context;
private DatabaseHelper helper;
private SQLiteDatabase db;
public CommentRepository(Context context) { this.context = context.getApplicationContext(); }
public void openDatabase() {
if (helper == null) helper = new DatabaseHelper(context);
if (db == null || !db.isOpen()) db = helper.getWritableDatabase();
}
public void closeDatabase() { if (db != null && db.isOpen()) db.close(); db = null; }
public void close() { closeDatabase(); }
private Comment map(Cursor c) {
Comment cm = new Comment();
cm.setId(c.getLong(c.getColumnIndexOrThrow("id")));
cm.setUploaderId(c.getString(c.getColumnIndexOrThrow("user_id")));
cm.setNoteId(c.getLong(c.getColumnIndexOrThrow("note_id")));
cm.setContent(c.getString(c.getColumnIndexOrThrow("content")));
cm.setCreatedAt(c.getString(c.getColumnIndexOrThrow("created_at")));
return cm;
}
public List<Comment> listByNoteId(long noteId) {
openDatabase();
List<Comment> list = new ArrayList<>();
try (Cursor c = db.query("T_Comment", null, "note_id=?", new String[]{String.valueOf(noteId)}, null, null, "created_at DESC")) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public long add(String userId, long noteId, String content) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("user_id", userId);
cv.put("note_id", noteId);
cv.put("content", content);
long r = db.insert("T_Comment", null, cv);
closeDatabase();
return r;
}
public boolean delete(long id, String userId) {
openDatabase();
int rows = db.delete("T_Comment", "id=? AND user_id=?", new String[]{String.valueOf(id), userId});
closeDatabase();
return rows > 0;
}
}

@ -0,0 +1,211 @@
package com.example.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.myapplication.domain.Chapter;
import com.example.myapplication.domain.Course;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CourseRepository {
private final Context context;
private DatabaseHelper helper;
private SQLiteDatabase db;
public CourseRepository(Context context) { this.context = context.getApplicationContext(); }
public void openDatabase() {
if (helper == null) helper = new DatabaseHelper(context);
if (db == null || !db.isOpen()) db = helper.getWritableDatabase();
}
public void closeDatabase() { if (db != null && db.isOpen()) db.close(); db = null; }
public void close() { closeDatabase(); }
public List<Course> getAllCourses() {
openDatabase();
List<Course> list = new ArrayList<>();
try (Cursor c = db.query("T_Subject", null, null, null, null, null, "name ASC")) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public List<Course> getCoursesByMajorAndGrade(Long majorId, String gradeText) {
openDatabase();
List<Course> list = new ArrayList<>();
if (majorId == null || gradeText == null || gradeText.isEmpty()) {
closeDatabase();
return list;
}
String sql = "SELECT s.* FROM T_Subject s INNER JOIN T_Major_Subject ms ON ms.subject_id = s.id WHERE ms.major_id = ? AND ms.grade = ? ORDER BY s.name ASC";
try (Cursor c = db.rawQuery(sql, new String[]{String.valueOf(majorId), gradeText})) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public List<Course> getCoursesByMajor(Long majorId) {
openDatabase();
List<Course> list = new ArrayList<>();
if (majorId == null) { closeDatabase(); return list; }
String sql = "SELECT DISTINCT s.* FROM T_Subject s INNER JOIN T_Major_Subject ms ON ms.subject_id = s.id WHERE ms.major_id = ? ORDER BY s.name ASC";
try (Cursor c = db.rawQuery(sql, new String[]{String.valueOf(majorId)})) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public Course getCourseById(long courseId) {
openDatabase();
Course course = null;
try (Cursor c = db.query("T_Subject", null, "id=?", new String[]{String.valueOf(courseId)}, null, null, null)) {
if (c != null && c.moveToFirst()) course = map(c);
}
closeDatabase();
return course;
}
public List<Course> findSimilarCourses(String keywords) {
openDatabase();
List<Course> list = new ArrayList<>();
String like = "%" + (keywords == null ? "" : keywords) + "%";
try (Cursor c = db.query("T_Subject", null, "name LIKE ? OR description LIKE ? OR code LIKE ?",
new String[]{like, like, like}, null, null, "name ASC")) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public Map<Course, List<Chapter>> getCourseCatalog() {
Map<Course, List<Chapter>> map = new HashMap<>();
List<Course> courses = getAllCourses();
for (Course c : courses) {
List<Chapter> chapters = getChaptersBySubjectId(c.getId());
map.put(c, chapters);
}
return map;
}
// Optional: helper to insert/update courses
public long upsertCourse(String name, String code, String description) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("name", name);
cv.put("code", code);
cv.put("description", description);
long id = db.insert("T_Subject", null, cv);
closeDatabase();
return id;
}
// 新增方法:插入课程及其章节
public void insertCourseWithChapters(Course course, List<Chapter> chapters) {
openDatabase();
try {
db.beginTransaction();
// 插入课程
ContentValues courseValues = new ContentValues();
courseValues.put("id", course.getId());
courseValues.put("name", course.getName());
courseValues.put("code", course.getCode());
courseValues.put("description", course.getDescription());
db.insert("T_Subject", null, courseValues);
// 插入章节
for (Chapter chapter : chapters) {
ContentValues chapterValues = new ContentValues();
chapterValues.put("id", chapter.getId());
chapterValues.put("name", chapter.getName());
chapterValues.put("subject_id", chapter.getCourseId());
chapterValues.put("order_num", chapter.getOrderIndex());
if (chapter.getKeywords() != null) chapterValues.put("keywords", chapter.getKeywords());
if (chapter.getAliases() != null) chapterValues.put("aliases", chapter.getAliases());
db.insert("T_Chapter", null, chapterValues);
}
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.endTransaction();
closeDatabase();
}
}
private Course map(Cursor c) {
return new Course(
c.getLong(c.getColumnIndexOrThrow("id")),
c.getString(c.getColumnIndexOrThrow("description")),
c.getString(c.getColumnIndexOrThrow("name")),
c.getString(c.getColumnIndexOrThrow("code"))
);
}
public List<Chapter> getChaptersBySubjectId(long subjectId) {
openDatabase();
List<Chapter> list = new ArrayList<>();
try (Cursor c = db.query("T_Chapter", null, "subject_id=?", new String[]{String.valueOf(subjectId)}, null, null, "order_num ASC")) {
while (c != null && c.moveToNext()) {
Chapter ch = new Chapter();
ch.setId(c.getLong(c.getColumnIndexOrThrow("id")));
ch.setName(c.getString(c.getColumnIndexOrThrow("name")));
int oi = 0; try { oi = c.getInt(c.getColumnIndexOrThrow("order_num")); } catch (Exception ignored) {}
ch.setOrderIndex(oi);
ch.setCourseId((int) subjectId);
try {
int kwi = c.getColumnIndex("keywords");
if (kwi >= 0 && !c.isNull(kwi)) ch.setKeywords(c.getString(kwi));
} catch (Exception ignored) {}
try {
int ali = c.getColumnIndex("aliases");
if (ali >= 0 && !c.isNull(ali)) ch.setAliases(c.getString(ali));
} catch (Exception ignored) {}
list.add(ch);
}
}
closeDatabase();
return list;
}
public List<Chapter> findChaptersByNameLike(String q, int limit) {
openDatabase();
List<Chapter> list = new ArrayList<>();
String like = "%" + (q == null ? "" : q) + "%";
String lim = String.valueOf(Math.max(1, limit));
try (Cursor c = db.query("T_Chapter", null, "name LIKE ? OR code LIKE ?", new String[]{like, like}, null, null, "order_num ASC", lim)) {
while (c != null && c.moveToNext()) {
Chapter ch = new Chapter();
ch.setId(c.getLong(c.getColumnIndexOrThrow("id")));
ch.setName(c.getString(c.getColumnIndexOrThrow("name")));
int oi = 0; try { oi = c.getInt(c.getColumnIndexOrThrow("order_num")); } catch (Exception ignored) {}
int sid = 0; try { sid = c.getInt(c.getColumnIndexOrThrow("subject_id")); } catch (Exception ignored) {}
ch.setOrderIndex(oi);
ch.setCourseId(sid);
try {
int kwi = c.getColumnIndex("keywords");
if (kwi >= 0 && !c.isNull(kwi)) ch.setKeywords(c.getString(kwi));
} catch (Exception ignored) {}
try {
int ali = c.getColumnIndex("aliases");
if (ali >= 0 && !c.isNull(ali)) ch.setAliases(c.getString(ali));
} catch (Exception ignored) {}
list.add(ch);
}
}
closeDatabase();
return list;
}
}

@ -0,0 +1,368 @@
package com.example.myapplication.repository;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Central SQLite helper creating tables for T_User, T_Note, T_Comment, T_Like, T_Course.
*/
public class DatabaseHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "mynote.db";
private static final int DB_VERSION = 10;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onConfigure(SQLiteDatabase db) {
db.setForeignKeyConstraintsEnabled(true);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS T_User (" +
"account TEXT PRIMARY KEY," +
"name TEXT," +
"password TEXT," +
"type INTEGER," +
"grade TEXT," +
"major_id INTEGER," +
"point INTEGER," +
"FOREIGN KEY(major_id) REFERENCES T_Major(id) ON DELETE SET NULL" +
")");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Major (" +
"id INTEGER PRIMARY KEY," +
"name TEXT," +
"code TEXT," +
"description TEXT" +
")");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10001,'计算机科学与技术','CS','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10002,'软件工程','SE','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10003,'电子信息工程','EE','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10004,'电气工程及其自动化','EEA','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10005,'机械工程','ME','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10006,'土木工程','CE','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10007,'建筑学','ARCH','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10008,'材料科学与工程','MSE','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10009,'数学与应用数学','MATH','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10010,'物理学','PHYS','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10011,'化学','CHEM','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10012,'生物科学','BIO','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10013,'医学','MED','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10014,'经济学','ECON','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10015,'金融学','FIN','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10016,'会计学','ACC','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10017,'工商管理','MBA','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10018,'法学','LAW','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10019,'教育学','EDU','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10020,'外国语言文学','LANG','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10021,'思想政治教育','IDEO','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10022,'信息安全','INFOSEC','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10023,'安全工程','SAFEENG','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10024,'应用心理学','APPSY','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10025,'物流管理','LOGMGT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10026,'飞行器质量与可靠性','AEROQUAL','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10027,'无人驾驶航空器系统工程','UAVSYS','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10028,'飞行器运维工程','AEROMAINT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10029,'交通工程','TRAENG','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10030,'智慧交通','SMARTTRA','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10031,'交通管理','TRAMGT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10032,'交通运输','TRANSPORT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10033,'应用气象学','APPMETEOR','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10034,'飞行技术','FLYTECH','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10035,'机械电子工程','MECHATRON','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10036,'飞行器动力工程','AEROPROP','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10037,'材料科学与工程','MSE2','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10038,'飞行器制造工程','AEROMFG','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10039,'飞行器设计与工程','AERODESIGN','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10040,'油气储运工程','OILGASTR','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10041,'通信工程','COMM','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10042,'物联网工程','IOT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10043,'自动化','AUTO','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10044,'人工智能','AI','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10045,'信息与计算科学','INFOCS','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10046,'统计学','STAT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10047,'材料物理','MATERIALPHY','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10048,'材料化学','MATERIALCHEM','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10049,'财务管理','FINMGT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10050,'公共事业管理','PUBMGT','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10051,'经济与金融','ECONFIN','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10052,'工商管理','BUSADM','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10053,'翻译','TRANS','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10054,'英语','ENGLISH','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10055,'空中乘务','CABIN','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10056,'航空服务艺术与管理','AIRSRV','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10057,'民航空中安全保卫','AIRSEC','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10058,'航空安防管理','AIRSAFE','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10059,'民航安全技术管理','AVSAFETECH','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10060,'飞机电子设备维修','AVIONICSR','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10061,'飞机电设备维修','AIRPOWER','')");
db.execSQL("INSERT OR IGNORE INTO T_Major (id,name,code,description) VALUES (10062,'民航运输服务','AIRTRANS','')");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Subject (" +
"id INTEGER PRIMARY KEY," +
"name TEXT," +
"code TEXT," +
"description TEXT" +
")");
// 课程种子(按常见课程与贵校专业扩展)
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20001,'人工智能','AI','机器学习、深度学习、强化学习')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20002,'数据结构与算法','DSA','线性表、树、图、排序与查找')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20003,'操作系统','OS','进程与线程、调度、内存管理、文件系统')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20004,'计算机网络','NET','TCP/IP、路由、交换、应用层协议')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20005,'数据库系统','DB','SQL、事务、索引、范式与设计')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20006,'信号与系统','SIGSYS','时域与频域、卷积、傅里叶、拉普拉斯')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20007,'数字通信','DIGCOM','调制解调、编码、容量、误码率')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20008,'交通工程导论','TRAENG','道路、交通流、信控、设施设计')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20009,'飞行原理','FLYPRIN','空气动力、升力阻力、机翼特性')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20010,'材料科学基础','MSEBAS','晶体结构、相图、力学与热学性能')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20011,'安全工程导论','SAFEENG','风险评估、隐患排查、应急管理')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20012,'应用气象学','APPMET','环流、边界层、观测与预报')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20013,'高等数学','ADVMath','微积分、线性代数、概率统计')");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Major_Subject (" +
"major_id INTEGER," +
"subject_id INTEGER," +
"grade TEXT NOT NULL DEFAULT ''," +
"PRIMARY KEY(major_id, subject_id, grade)," +
"FOREIGN KEY(major_id) REFERENCES T_Major(id) ON DELETE CASCADE," +
"FOREIGN KEY(subject_id) REFERENCES T_Subject(id) ON DELETE CASCADE" +
")");
// 计算机学院专业-课程映射(按年级限定)
String[] grades = new String[]{"大一","大二","大三","大四"};
for (String g : grades) {
// 计算机科学与技术10001→ 数据结构、操作系统、计算机网络、数据库系统
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20002,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20003,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20004,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20005,'" + g + "')");
// 人工智能10044→ 人工智能、以及通用基础课
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20001,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20002,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20003,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20004,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20005,'" + g + "')");
// 公共数学基础
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20013,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20013,'" + g + "')");
}
db.execSQL("CREATE TABLE IF NOT EXISTS T_Chapter (" +
"id INTEGER PRIMARY KEY," +
"subject_id INTEGER," +
"name TEXT," +
"code TEXT," +
"description TEXT," +
"keywords TEXT," +
"aliases TEXT," +
"order_num INTEGER," +
"FOREIGN KEY(subject_id) REFERENCES T_Subject(id) ON DELETE CASCADE" +
")");
db.execSQL("CREATE INDEX IF NOT EXISTS idx_chapter_subject_order ON T_Chapter(subject_id, order_num)");
// 章节种子(含关键词与别名,便于自动分类)
// 人工智能
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30001,20001,'机器学习基础',1,'监督学习,非监督学习,回归,分类,交叉验证','machine learning,ml,supervised,unsupervised')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30002,20001,'深度学习',2,'神经网络,卷积,反向传播,激活函数,梯度下降','deep learning,cnn,rnn,bp,activation')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30003,20001,'强化学习',3,'马尔可夫决策,策略,价值函数,探索利用','reinforcement learning,mdp,policy,value,exploration')");
// 数据结构与算法
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30011,20002,'线性结构',1,'数组,链表,栈,队列','array,list,stack,queue')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30012,20002,'树与图',2,'二叉树,平衡树,堆,图遍历,最短路径','tree,graph,dfs,bfs,heap,dijkstra')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30013,20002,'排序与查找',3,'快速排序,归并排序,哈希,二分查找','quicksort,mergesort,hash,binary search')");
// 操作系统
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30021,20003,'进程与线程',1,'调度,同步,死锁,临界区','process,thread,schedule,lock')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30022,20003,'内存管理',2,'分页,分段,虚拟内存,置换算法','memory,virtual,paging,mmu')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30023,20003,'文件系统',3,'索引,日志,磁盘,目录结构','filesystem,inode,journal,disk')");
// 计算机网络
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30031,20004,'网络层',1,'路由,IP,子网,拥塞','ip,routing,subnet,congestion')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30032,20004,'传输层',2,'TCP,UDP,流控,拥塞控制','tcp,udp,flow')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30033,20004,'应用层',3,'HTTP,DNS,SMTP,FTP','http,dns,email,ftp')");
// 数据库系统
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30041,20005,'关系模型与SQL',1,'范式,主键,外键,查询,连接','sql,normal form,join')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30042,20005,'事务与并发',2,'ACID,锁,隔离级别,日志','transaction,acid,lock,isolation')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30043,20005,'索引与优化',3,'B树,哈希索引,执行计划,优化器','index,b-tree,optimizer')");
// 信号与系统 / 数字通信
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30051,20006,'时域与频域',1,'卷积,傅里叶变换,频谱','convolution,fourier,spectrum')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30061,20007,'调制与编码',1,'ASK,PSK,QAM,信道编码','modulation,qam,channel coding')");
// 交通工程 / 飞行原理 / 材料科学基础 / 安全工程 / 应用气象学
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30071,20008,'信号控制',1,'交叉口,配时,路口控制','signal control,intersection')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30081,20009,'空气动力学基础',1,'升力,阻力,机翼,攻角','aerodynamics,lift,drag,airfoil')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30091,20010,'晶体与相图',1,'晶体结构,相变,相图','crystal,phase diagram')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30101,20011,'风险评估',1,'危险源,隐患,评估方法,应急','risk,hazard,assessment,emergency')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30111,20012,'天气系统',1,'环流,锋面,降水,预报','circulation,front,precipitation,forecast')");
// 高等数学
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30121,20013,'微积分',1,'极限,导数,积分,泰勒展开','calculus,derivative,integral,taylor')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30122,20013,'线性代数',2,'矩阵,向量,特征值,特征向量,秩','linear algebra,matrix,eigenvalue,eigenvector,rank')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30123,20013,'概率论与数理统计',3,'概率分布,方差,期望,假设检验,样本','probability,distribution,variance,expectation,hypothesis test')");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Note (" +
"id INTEGER PRIMARY KEY," +
"title TEXT," +
"content TEXT," +
"file_path TEXT," +
"uploader_id TEXT," +
"chapter_id INTEGER," +
"download_count INTEGER DEFAULT 0," +
"like_count INTEGER DEFAULT 0," +
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP," +
"FOREIGN KEY(uploader_id) REFERENCES T_User(account) ON DELETE SET NULL," +
"FOREIGN KEY(chapter_id) REFERENCES T_Chapter(id) ON DELETE SET NULL" +
")");
db.execSQL("CREATE INDEX IF NOT EXISTS idx_user_grade ON T_User(grade)");
db.execSQL("CREATE INDEX IF NOT EXISTS idx_user_major_id ON T_User(major_id)");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Comment (" +
"id INTEGER PRIMARY KEY," +
"user_id TEXT," +
"note_id INTEGER," +
"content TEXT," +
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP," +
"FOREIGN KEY(user_id) REFERENCES T_User(account) ON DELETE SET NULL," +
"FOREIGN KEY(note_id) REFERENCES T_Note(id) ON DELETE CASCADE" +
")");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Like (" +
"user_id TEXT," +
"note_id INTEGER," +
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP," +
"PRIMARY KEY(user_id, note_id)," +
"FOREIGN KEY(user_id) REFERENCES T_User(account) ON DELETE CASCADE," +
"FOREIGN KEY(note_id) REFERENCES T_Note(id) ON DELETE CASCADE" +
")");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
if (oldVersion < 2) {
db.execSQL("CREATE TABLE IF NOT EXISTS T_Note_new (id INTEGER PRIMARY KEY AUTOINCREMENT,uploader_id TEXT,course_id INTEGER,content TEXT,download_count INTEGER DEFAULT 0,created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,title TEXT,file_path TEXT,FOREIGN KEY(uploader_id) REFERENCES T_User(account) ON DELETE SET NULL)");
db.execSQL("INSERT INTO T_Note_new (id, uploader_id, course_id, content, download_count, created, title, file_path) SELECT id, uploader_id, course_id, COALESCE(ocr_text, description), download_count, created, title, file_path FROM T_Note");
db.execSQL("DROP TABLE IF EXISTS T_Note");
db.execSQL("ALTER TABLE T_Note_new RENAME TO T_Note");
}
if (oldVersion < 3) {
db.execSQL("ALTER TABLE T_User ADD COLUMN points INTEGER DEFAULT 10");
}
if (oldVersion < 5) {
db.execSQL("CREATE TABLE IF NOT EXISTS T_User_new (account TEXT PRIMARY KEY,name TEXT,password TEXT,type INTEGER,mobile TEXT,grade TEXT,major TEXT,point INTEGER)");
db.execSQL("INSERT INTO T_User_new (account,name,password,type,mobile,grade,major,point) SELECT account,name,password,type,mobile,CASE WHEN grade IS NULL THEN NULL ELSE CAST(grade AS TEXT) END,major,COALESCE(points,0) FROM T_User");
db.execSQL("DROP TABLE IF EXISTS T_User");
db.execSQL("ALTER TABLE T_User_new RENAME TO T_User");
onCreate(db);
db.execSQL("CREATE TABLE IF NOT EXISTS T_Note_new (id INTEGER PRIMARY KEY,title TEXT,description TEXT,file_path TEXT,uploader_id TEXT,chapter_id INTEGER,ocr_text TEXT,ocr_status TEXT,download_count INTEGER DEFAULT 0,like_count INTEGER DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY(uploader_id) REFERENCES T_User(account) ON DELETE SET NULL,FOREIGN KEY(chapter_id) REFERENCES T_Chapter(id) ON DELETE SET NULL)");
db.execSQL("INSERT INTO T_Note_new (id,title,description,file_path,uploader_id,chapter_id,ocr_text,ocr_status,download_count,like_count,created_at) SELECT n.id,n.title,COALESCE(n.content,''),n.file_path,n.uploader_id,NULL,NULL,NULL,COALESCE(n.download_count,0),(SELECT COUNT(*) FROM T_Like l WHERE l.note_id = n.id),COALESCE(n.created,CURRENT_TIMESTAMP) FROM T_Note n");
db.execSQL("DROP TABLE IF EXISTS T_Note");
db.execSQL("ALTER TABLE T_Note_new RENAME TO T_Note");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Comment_new (id INTEGER PRIMARY KEY,user_id TEXT,note_id INTEGER,content TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY(user_id) REFERENCES T_User(account) ON DELETE SET NULL,FOREIGN KEY(note_id) REFERENCES T_Note(id) ON DELETE CASCADE)");
db.execSQL("INSERT INTO T_Comment_new (id,user_id,note_id,content,created_at) SELECT id,uploader_id,note_id,content,created_at FROM T_Comment");
db.execSQL("DROP TABLE IF EXISTS T_Comment");
db.execSQL("ALTER TABLE T_Comment_new RENAME TO T_Comment");
}
if (oldVersion < 6) {
db.execSQL("CREATE TABLE IF NOT EXISTS T_User_new (account TEXT PRIMARY KEY,name TEXT,password TEXT,type INTEGER,mobile TEXT,grade TEXT,major_id INTEGER,point INTEGER,FOREIGN KEY(major_id) REFERENCES T_Major(id) ON DELETE SET NULL)");
db.execSQL("INSERT INTO T_User_new (account,name,password,type,mobile,grade,major_id,point) SELECT account,name,password,type,mobile,grade,(SELECT id FROM T_Major m WHERE m.name LIKE '%'||COALESCE(T_User.major,'')||'%' OR m.code LIKE '%'||COALESCE(T_User.major,'')||'%' LIMIT 1),COALESCE(point,0) FROM T_User");
db.execSQL("DROP TABLE IF EXISTS T_User");
db.execSQL("ALTER TABLE T_User_new RENAME TO T_User");
}
if (oldVersion < 7) {
db.execSQL("CREATE TABLE IF NOT EXISTS T_Note_new (id INTEGER PRIMARY KEY,title TEXT,content TEXT,file_path TEXT,uploader_id TEXT,chapter_id INTEGER,download_count INTEGER DEFAULT 0,like_count INTEGER DEFAULT 0,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY(uploader_id) REFERENCES T_User(account) ON DELETE SET NULL,FOREIGN KEY(chapter_id) REFERENCES T_Chapter(id) ON DELETE SET NULL)");
db.execSQL("INSERT INTO T_Note_new (id,title,content,file_path,uploader_id,chapter_id,download_count,like_count,created_at) SELECT n.id,n.title,COALESCE(n.description,''),n.file_path,n.uploader_id,n.chapter_id,COALESCE(n.download_count,0),COALESCE(n.like_count,(SELECT COUNT(*) FROM T_Like l WHERE l.note_id = n.id)),COALESCE(n.created_at,CURRENT_TIMESTAMP) FROM T_Note n");
db.execSQL("DROP TABLE IF EXISTS T_Note");
db.execSQL("ALTER TABLE T_Note_new RENAME TO T_Note");
}
if (oldVersion < 8) {
db.execSQL("CREATE TABLE IF NOT EXISTS T_User_new (account TEXT PRIMARY KEY,name TEXT,password TEXT,type INTEGER,grade TEXT,major_id INTEGER,point INTEGER,FOREIGN KEY(major_id) REFERENCES T_Major(id) ON DELETE SET NULL)");
db.execSQL("INSERT INTO T_User_new (account,name,password,type,grade,major_id,point) SELECT account,name,password,type,grade,major_id,point FROM T_User");
db.execSQL("DROP TABLE IF EXISTS T_User");
db.execSQL("ALTER TABLE T_User_new RENAME TO T_User");
db.execSQL("CREATE TABLE IF NOT EXISTS T_Major_Subject_new (major_id INTEGER,subject_id INTEGER,grade TEXT NOT NULL DEFAULT '',PRIMARY KEY(major_id, subject_id, grade),FOREIGN KEY(major_id) REFERENCES T_Major(id) ON DELETE CASCADE,FOREIGN KEY(subject_id) REFERENCES T_Subject(id) ON DELETE CASCADE)");
db.execSQL("INSERT INTO T_Major_Subject_new (major_id,subject_id,grade) SELECT major_id,subject_id,'' FROM T_Major_Subject");
db.execSQL("DROP TABLE IF EXISTS T_Major_Subject");
db.execSQL("ALTER TABLE T_Major_Subject_new RENAME TO T_Major_Subject");
}
if (oldVersion < 9) {
try { db.execSQL("ALTER TABLE T_Chapter ADD COLUMN keywords TEXT"); } catch (Exception ignored) {}
try { db.execSQL("ALTER TABLE T_Chapter ADD COLUMN aliases TEXT"); } catch (Exception ignored) {}
}
if (oldVersion < 10) {
String[] grades2 = new String[]{"大一","大二","大三","大四"};
for (String g : grades2) {
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20013,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20013,'" + g + "')");
}
}
if (oldVersion < 10) {
// 补种课程与章节(老安装无需清数据即可获得新课程/章节)
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20001,'人工智能','AI','机器学习、深度学习、强化学习')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20002,'数据结构与算法','DSA','线性表、树、图、排序与查找')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20003,'操作系统','OS','进程与线程、调度、内存管理、文件系统')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20004,'计算机网络','NET','TCP/IP、路由、交换、应用层协议')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20005,'数据库系统','DB','SQL、事务、索引、范式与设计')");
db.execSQL("INSERT OR IGNORE INTO T_Subject (id,name,code,description) VALUES (20013,'高等数学','ADVMath','微积分、线性代数、概率统计')");
// 补种关键章节与关键词/别名
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30001,20001,'机器学习基础',1,'监督学习,非监督学习,回归,分类,交叉验证','machine learning,ml,supervised,unsupervised')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30002,20001,'深度学习',2,'神经网络,卷积,反向传播,激活函数,梯度下降','deep learning,cnn,rnn,bp,activation')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30003,20001,'强化学习',3,'马尔可夫决策,策略,价值函数,探索利用','reinforcement learning,mdp,policy,value,exploration')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30011,20002,'线性结构',1,'数组,链表,栈,队列','array,list,stack,queue')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30012,20002,'树与图',2,'二叉树,平衡树,堆,图遍历,最短路径','tree,graph,dfs,bfs,heap,dijkstra')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30013,20002,'排序与查找',3,'快速排序,归并排序,哈希,二分查找','quicksort,mergesort,hash,binary search')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30021,20003,'进程与线程',1,'调度,同步,死锁,临界区','process,thread,schedule,lock')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30022,20003,'内存管理',2,'分页,分段,虚拟内存,置换算法','memory,virtual,paging,mmu')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30023,20003,'文件系统',3,'索引,日志,磁盘,目录结构','filesystem,inode,journal,disk')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30031,20004,'网络层',1,'路由,IP,子网,拥塞','ip,routing,subnet,congestion')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30032,20004,'传输层',2,'TCP,UDP,流控,拥塞控制','tcp,udp,flow')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30033,20004,'应用层',3,'HTTP,DNS,SMTP,FTP','http,dns,email,ftp')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30041,20005,'关系模型与SQL',1,'范式,主键,外键,查询,连接','sql,normal form,join')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30042,20005,'事务与并发',2,'ACID,锁,隔离级别,日志','transaction,acid,lock,isolation')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30043,20005,'索引与优化',3,'B树,哈希索引,执行计划,优化器','index,b-tree,optimizer')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30121,20013,'微积分',1,'极限,导数,积分,泰勒展开','calculus,derivative,integral,taylor')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30122,20013,'线性代数',2,'矩阵,向量,特征值,特征向量,秩','linear algebra,matrix,eigenvalue,eigenvector,rank')");
db.execSQL("INSERT OR IGNORE INTO T_Chapter (id,subject_id,name,order_num,keywords,aliases) VALUES (30123,20013,'概率论与数理统计',3,'概率分布,方差,期望,假设检验,样本','probability,distribution,variance,expectation,hypothesis test')");
// 计算机学院两专业课程映射(含公共数学)
String[] grades2 = new String[]{"大一","大二","大三","大四"};
for (String g : grades2) {
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20002,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20003,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20004,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20005,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20001,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20002,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20003,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20004,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20005,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10001,20013,'" + g + "')");
db.execSQL("INSERT OR IGNORE INTO T_Major_Subject (major_id,subject_id,grade) VALUES (10044,20013,'" + g + "')");
}
}
} catch (Exception e) {
db.execSQL("DROP TABLE IF EXISTS T_Comment");
db.execSQL("DROP TABLE IF EXISTS T_Like");
db.execSQL("DROP TABLE IF EXISTS T_Note");
db.execSQL("DROP TABLE IF EXISTS T_Chapter");
db.execSQL("DROP TABLE IF EXISTS T_Major_Subject");
db.execSQL("DROP TABLE IF EXISTS T_Subject");
db.execSQL("DROP TABLE IF EXISTS T_Major");
db.execSQL("DROP TABLE IF EXISTS T_User");
onCreate(db);
}
}
}

@ -0,0 +1,33 @@
package com.example.myapplication.repository;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
public final class DatabaseManager {
private static DatabaseHelper helper;
private static SQLiteDatabase db;
private DatabaseManager() {}
public static synchronized void init(Context ctx) {
if (helper == null) {
helper = new DatabaseHelper(ctx.getApplicationContext());
db = helper.getWritableDatabase(); // 打开并保持
}
}
public static synchronized SQLiteDatabase getDb() {
if (db == null && helper != null) {
db = helper.getWritableDatabase();
}
return db;
}
public static synchronized void close() {
if (helper != null) {
helper.close();
db = null;
helper = null;
}
}
}

@ -0,0 +1,81 @@
package com.example.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class LikeRepository {
private final Context context;
private DatabaseHelper helper;
private SQLiteDatabase db;
public LikeRepository(Context context) { this.context = context.getApplicationContext(); }
public void openDatabase() {
if (helper == null) helper = new DatabaseHelper(context);
if (db == null || !db.isOpen()) db = helper.getWritableDatabase();
}
public void closeDatabase() { if (db != null && db.isOpen()) db.close(); db = null; }
public boolean addLike(String userId, long noteId) {
openDatabase();
boolean ok = false;
db.beginTransaction();
try {
ContentValues cv = new ContentValues();
cv.put("user_id", userId);
cv.put("note_id", noteId);
long r = db.insertWithOnConflict("T_Like", null, cv, SQLiteDatabase.CONFLICT_IGNORE);
if (r != -1) {
db.execSQL("UPDATE T_Note SET like_count = COALESCE(like_count,0) + 1 WHERE id=?", new Object[]{noteId});
ok = true;
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
closeDatabase();
return ok;
}
public boolean removeLike(String userId, long noteId) {
openDatabase();
boolean ok = false;
db.beginTransaction();
try {
int rows = db.delete("T_Like", "user_id=? AND note_id=?", new String[]{userId, String.valueOf(noteId)});
if (rows > 0) {
db.execSQL("UPDATE T_Note SET like_count = CASE WHEN COALESCE(like_count,0) > 0 THEN like_count - 1 ELSE 0 END WHERE id=?", new Object[]{noteId});
ok = true;
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
closeDatabase();
return ok;
}
public boolean checkUserLikeStatus(String userId, long noteId) {
openDatabase();
boolean exists = false;
try (Cursor c = db.query("T_Like", new String[]{"user_id"}, "user_id=? AND note_id=?",
new String[]{userId, String.valueOf(noteId)}, null, null, null)) {
exists = c != null && c.moveToFirst();
}
closeDatabase();
return exists;
}
public int getLikeCount(long noteId) {
openDatabase();
int count = 0;
try (Cursor c = db.rawQuery("SELECT COUNT(*) FROM T_Like WHERE note_id=?", new String[]{String.valueOf(noteId)})) {
if (c != null && c.moveToFirst()) count = c.getInt(0);
}
closeDatabase();
return count;
}
}

@ -0,0 +1,56 @@
package com.example.myapplication.repository;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.myapplication.domain.Major;
import java.util.ArrayList;
import java.util.List;
public class MajorRepository {
private final Context context;
private DatabaseHelper helper;
private SQLiteDatabase db;
public MajorRepository(Context context) { this.context = context.getApplicationContext(); }
public void openDatabase() {
if (helper == null) helper = new DatabaseHelper(context);
if (db == null || !db.isOpen()) db = helper.getWritableDatabase();
}
public void closeDatabase() { if (db != null && db.isOpen()) db.close(); db = null; }
public List<Major> findByNameLike(String keywords, int limit) {
openDatabase();
List<Major> list = new ArrayList<>();
String like = "%" + (keywords == null ? "" : keywords.trim()) + "%";
try (Cursor c = db.query("T_Major", null, "name LIKE ? OR code LIKE ?",
new String[]{like, like}, null, null, "name ASC", String.valueOf(Math.max(1, limit)) )) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public Major getById(long id) {
openDatabase();
Major m = null;
try (Cursor c = db.query("T_Major", null, "id=?", new String[]{String.valueOf(id)}, null, null, null)) {
if (c != null && c.moveToFirst()) m = map(c);
}
closeDatabase();
return m;
}
private Major map(Cursor c) {
return new Major(
c.getLong(c.getColumnIndexOrThrow("id")),
c.getString(c.getColumnIndexOrThrow("name")),
c.getString(c.getColumnIndexOrThrow("code")),
c.getString(c.getColumnIndexOrThrow("description"))
);
}
}

@ -0,0 +1,417 @@
package com.example.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.myapplication.domain.Note;
import java.util.ArrayList;
import java.util.List;
public class NoteRepository {
private final Context context;
private DatabaseHelper helper;
private SQLiteDatabase db;
private static final java.util.Map<String, java.util.List<com.example.myapplication.domain.Note>> recommendedCache = new java.util.LinkedHashMap<String, java.util.List<com.example.myapplication.domain.Note>>() {
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<String, java.util.List<com.example.myapplication.domain.Note>> eldest) {
return size() > 20; // 最多缓存20个键
}
};
public static void clearRecommendedCacheFor(Long userMajorId, String userGradeText) {
String majorKey = userMajorId == null ? "" : String.valueOf(userMajorId);
String gradeArg = userGradeText == null ? "" : userGradeText;
String cacheKey = majorKey + "|" + gradeArg;
recommendedCache.remove(cacheKey);
}
public NoteRepository(Context context) { this.context = context.getApplicationContext(); }
public void openDatabase() {
if (helper == null) helper = new DatabaseHelper(context);
if (db == null || !db.isOpen()) db = helper.getWritableDatabase();
}
public void closeDatabase() { if (db != null && db.isOpen()) db.close(); db = null; }
public void close() { closeDatabase(); }
/**
* NoteUploadActivity
* @param note
* @return ID-1
*/
public long insertNote(Note note) {
if (note == null) return -1;
openDatabase();
try {
ContentValues cv = new ContentValues();
cv.put("title", note.getTitle());
cv.put("content", note.getDescription());
cv.put("file_path", note.getFilePath());
cv.put("chapter_id", note.getChapterId());
cv.put("uploader_id", note.getUploaderId());
// 处理新的字段
if (note.getOcrText() != null) {
cv.put("ocr_text", note.getOcrText());
}
if (note.getUploadTime() > 0) {
cv.put("upload_time", note.getUploadTime());
}
if (note.getUpdateTime() > 0) {
cv.put("update_time", note.getUpdateTime());
}
long result = db.insert("T_Note", null, cv);
return result;
} catch (Exception e) {
e.printStackTrace();
return -1;
} finally {
closeDatabase();
}
}
// 保存long 版本)
public boolean saveNote(String title, String content, String filePath, long courseId, String uploaderId) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("title", title);
cv.put("content", content);
cv.put("file_path", filePath);
if (courseId <= 0) { cv.putNull("chapter_id"); } else { cv.put("chapter_id", courseId); }
if (uploaderId == null || uploaderId.isEmpty()) { cv.putNull("uploader_id"); } else { cv.put("uploader_id", uploaderId); }
long r = db.insert("T_Note", null, cv);
closeDatabase();
return r != -1;
}
// 保存Long 版本)
public boolean saveNote(String title, String content, String filePath, Long courseId, String uploaderId) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("title", title);
cv.put("content", content);
cv.put("file_path", filePath);
if (courseId == null) { cv.putNull("chapter_id"); } else { cv.put("chapter_id", courseId); }
if (uploaderId == null || uploaderId.isEmpty()) { cv.putNull("uploader_id"); } else { cv.put("uploader_id", uploaderId); }
long r = db.insert("T_Note", null, cv);
closeDatabase();
return r != -1;
}
// 新增重载:支持可空的 courseId / uploaderId
public boolean saveNoteWithSentinel(String title, String description, String filePath, long courseIdOrMinus1, String uploaderId) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("title", title);
cv.put("content", description);
cv.put("file_path", filePath);
if (courseIdOrMinus1 == -1L) {
cv.putNull("chapter_id");
} else {
cv.put("chapter_id", courseIdOrMinus1);
}
if (uploaderId == null || uploaderId.isEmpty()) {
cv.putNull("uploader_id");
} else {
cv.put("uploader_id", uploaderId);
}
long r = db.insert("T_Note", null, cv);
closeDatabase();
return r != -1;
}
public boolean deleteNote(long noteId) {
com.example.myapplication.domain.Note n = getNoteById(noteId);
String currentUser = com.example.myapplication.service.AuthService.getCurrentUser(context);
if (n == null || n.getUploaderId() == null || currentUser == null || !n.getUploaderId().equals(currentUser)) {
return false;
}
openDatabase();
int rows = db.delete("T_Note", "id=?", new String[]{String.valueOf(noteId)});
closeDatabase();
return rows > 0;
}
public boolean updateNote(long noteId, ContentValues updateData) {
boolean needsOwner = false;
if (updateData != null) {
needsOwner = updateData.containsKey("title")
|| updateData.containsKey("content")
|| updateData.containsKey("file_path")
|| updateData.containsKey("chapter_id");
}
if (needsOwner) {
com.example.myapplication.domain.Note n = getNoteById(noteId);
String currentUser = com.example.myapplication.service.AuthService.getCurrentUser(context);
if (n == null || n.getUploaderId() == null || currentUser == null || !n.getUploaderId().equals(currentUser)) {
return false;
}
}
openDatabase();
int rows = db.update("T_Note", updateData, "id=?", new String[]{String.valueOf(noteId)});
closeDatabase();
return rows > 0;
}
public Note getNoteById(long noteId) {
openDatabase();
Note note = null;
try (Cursor c = db.query("T_Note", null, "id=?", new String[]{String.valueOf(noteId)}, null, null, null)) {
if (c != null && c.moveToFirst()) {
note = map(c);
}
}
closeDatabase();
return note;
}
public List<Note> getNotesByUser(String userId) {
openDatabase();
List<Note> list = new ArrayList<>();
try (Cursor c = db.query("T_Note", null, "uploader_id=?", new String[]{userId}, null, null, "created_at DESC")) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public List<Note> findNotesByKeywords(String keywords, String filters) {
openDatabase();
List<Note> list = new ArrayList<>();
String q = keywords == null ? "" : keywords.trim();
String like = "%" + q + "%";
// 1) 章节匹配
List<Long> chapterIds = new ArrayList<>();
try (Cursor c1 = db.rawQuery("SELECT id FROM T_Chapter WHERE name LIKE ? OR code LIKE ? ORDER BY order_num ASC", new String[]{like, like})) {
while (c1 != null && c1.moveToNext()) chapterIds.add(c1.getLong(0));
}
if (!chapterIds.isEmpty()) {
StringBuilder in = new StringBuilder();
for (int i = 0; i < chapterIds.size(); i++) { if (i > 0) in.append(","); in.append("?"); }
String sql = "SELECT * FROM T_Note WHERE chapter_id IN (" + in + ") ORDER BY created_at DESC";
String[] args = new String[chapterIds.size()];
for (int i = 0; i < chapterIds.size(); i++) args[i] = String.valueOf(chapterIds.get(i));
try (Cursor c = db.rawQuery(sql, args)) { while (c != null && c.moveToNext()) list.add(map(c)); }
if (!list.isEmpty()) { closeDatabase(); return list; }
}
// 2) 科目匹配(包含描述)
List<Long> subjectIds = new ArrayList<>();
try (Cursor c2 = db.rawQuery("SELECT id FROM T_Subject WHERE name LIKE ? OR code LIKE ? OR description LIKE ? ORDER BY name ASC", new String[]{like, like, like})) {
while (c2 != null && c2.moveToNext()) subjectIds.add(c2.getLong(0));
}
if (!subjectIds.isEmpty()) {
StringBuilder in = new StringBuilder();
for (int i = 0; i < subjectIds.size(); i++) { if (i > 0) in.append(","); in.append("?"); }
String sql = "SELECT n.* FROM T_Note n WHERE n.chapter_id IN (SELECT ch.id FROM T_Chapter ch WHERE ch.subject_id IN (" + in + ")) ORDER BY n.created_at DESC";
String[] args = new String[subjectIds.size()];
for (int i = 0; i < subjectIds.size(); i++) args[i] = String.valueOf(subjectIds.get(i));
try (Cursor c = db.rawQuery(sql, args)) { while (c != null && c.moveToNext()) list.add(map(c)); }
if (!list.isEmpty()) { closeDatabase(); return list; }
}
// 3) 专业匹配
List<Long> majorIds = new ArrayList<>();
try (Cursor c3 = db.rawQuery("SELECT id FROM T_Major WHERE name LIKE ? OR code LIKE ? ORDER BY name ASC", new String[]{like, like})) {
while (c3 != null && c3.moveToNext()) majorIds.add(c3.getLong(0));
}
if (!majorIds.isEmpty()) {
StringBuilder in = new StringBuilder();
for (int i = 0; i < majorIds.size(); i++) { if (i > 0) in.append(","); in.append("?"); }
String sql = "SELECT n.* FROM T_Note n WHERE n.chapter_id IN (SELECT ch.id FROM T_Chapter ch WHERE ch.subject_id IN (SELECT ms.subject_id FROM T_Major_Subject ms WHERE ms.major_id IN (" + in + "))) ORDER BY n.created_at DESC";
String[] args = new String[majorIds.size()];
for (int i = 0; i < majorIds.size(); i++) args[i] = String.valueOf(majorIds.get(i));
try (Cursor c = db.rawQuery(sql, args)) { while (c != null && c.moveToNext()) list.add(map(c)); }
if (!list.isEmpty()) { closeDatabase(); return list; }
}
// 4) 标题/内容模糊
String selection = "title LIKE ? OR content LIKE ?";
String[] args = new String[]{like, like};
try (Cursor c = db.query("T_Note", null, selection, args, null, null, "created_at DESC")) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
// 新增:按标题模糊查询,仅匹配 title
public java.util.List<com.example.myapplication.domain.Note> findNotesByTitleLike(String keywords) {
openDatabase();
java.util.List<com.example.myapplication.domain.Note> list = new java.util.ArrayList<>();
String like = "%" + (keywords == null ? "" : keywords) + "%";
try (android.database.Cursor c = db.query(
"T_Note",
null,
"title LIKE ?",
new String[]{like},
null,
null,
"created_at DESC"
)) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
public java.util.List<com.example.myapplication.domain.Note> getRecentNotes(int limit) {
openDatabase();
java.util.List<com.example.myapplication.domain.Note> list = new java.util.ArrayList<>();
String orderBy = "created_at DESC";
String limitStr = String.valueOf(Math.max(1, limit));
try (android.database.Cursor c = db.query("T_Note", null, null, null, null, null, orderBy, limitStr)) {
while (c != null && c.moveToNext()) list.add(map(c));
}
closeDatabase();
return list;
}
private Note map(Cursor c) {
Integer pg = null;
try {
int gi = c.getColumnIndex("publisher_grade");
if (gi >= 0 && !c.isNull(gi)) pg = c.getInt(gi);
} catch (Exception ignored) {}
String pm = null;
try { int mi = c.getColumnIndex("publisher_major"); if (mi >= 0) pm = c.getString(mi); } catch (Exception ignored) {}
long chapterId = 0;
try { chapterId = c.getLong(c.getColumnIndexOrThrow("chapter_id")); } catch (Exception ignored) {}
String created = null;
try { created = c.getString(c.getColumnIndexOrThrow("created_at")); } catch (Exception ignored) {}
int likeCount = 0;
try { likeCount = c.getInt(c.getColumnIndexOrThrow("like_count")); } catch (Exception ignored) {}
String content = null;
try { content = c.getString(c.getColumnIndexOrThrow("content")); } catch (Exception ignored) {}
return new Note(
c.getLong(c.getColumnIndexOrThrow("id")),
c.getString(c.getColumnIndexOrThrow("uploader_id")),
chapterId,
content,
c.getInt(c.getColumnIndexOrThrow("download_count")),
likeCount,
created,
c.getString(c.getColumnIndexOrThrow("title")),
c.getString(c.getColumnIndexOrThrow("file_path")),
pg,
pm
);
}
public int getTotalLikesByUser(String userId) {
openDatabase();
int total = 0;
try (android.database.Cursor c = db.rawQuery("SELECT COALESCE(SUM(like_count),0) FROM T_Note WHERE uploader_id=?", new String[]{userId})) {
if (c != null && c.moveToFirst()) total = c.getInt(0);
}
closeDatabase();
return total;
}
// 推荐查询:按优先级排序(同专业同年级 > 同专业 > 同年级 > 基础学科),支持分页
public List<Note> getRecommendedNotes(Long userMajorId, String userGradeText, int limit, int offset) {
openDatabase();
List<Note> list = new ArrayList<>();
String gradeArg = userGradeText == null ? "" : userGradeText;
String majorKey = userMajorId == null ? "" : String.valueOf(userMajorId);
String cacheKey = majorKey + "|" + gradeArg;
if (offset == 0) {
java.util.List<com.example.myapplication.domain.Note> cached = recommendedCache.get(cacheKey);
if (cached != null) {
for (int i = 0; i < Math.min(limit, cached.size()); i++) list.add(cached.get(i));
closeDatabase();
return list;
}
}
String sqlPrimary =
"SELECT n.*, " +
" m.name AS publisher_major_name, " +
" u.grade AS publisher_grade_text, " +
" CASE " +
" WHEN u.major_id = ? AND u.grade = ? THEN 1000 " +
" WHEN u.major_id = ? THEN 800 " +
" WHEN u.grade = ? THEN 600 " +
" ELSE 0 " +
" END AS relevance " +
"FROM T_Note n " +
"LEFT JOIN T_User u ON u.account = n.uploader_id " +
"LEFT JOIN T_Major m ON m.id = u.major_id " +
"LEFT JOIN T_Chapter ch ON ch.id = n.chapter_id " +
"LEFT JOIN T_Subject s ON s.id = ch.subject_id " +
"WHERE NOT (s.id = 20013 OR s.name LIKE '%高等数学%' OR s.code = 'ADVMath') " +
"ORDER BY relevance DESC, n.like_count DESC, n.created_at DESC " +
"LIMIT ? OFFSET ?";
String[] argsPrimary = new String[]{
userMajorId == null ? "-1" : String.valueOf(userMajorId),
gradeArg,
userMajorId == null ? "-1" : String.valueOf(userMajorId),
gradeArg,
String.valueOf(limit),
String.valueOf(offset)
};
try (Cursor c = db.rawQuery(sqlPrimary, argsPrimary)) {
while (c != null && c.moveToNext()) {
Note n = map(c);
try {
int mi = c.getColumnIndex("publisher_major_name");
if (mi >= 0 && !c.isNull(mi)) n.setPublisherMajor(c.getString(mi));
} catch (Exception ignored) {}
list.add(n);
}
}
if (list.isEmpty()) {
String sqlFallback =
"SELECT n.*, " +
" m.name AS publisher_major_name, " +
" u.grade AS publisher_grade_text " +
"FROM T_Note n " +
"LEFT JOIN T_User u ON u.account = n.uploader_id " +
"LEFT JOIN T_Major m ON m.id = u.major_id " +
"LEFT JOIN T_Chapter ch ON ch.id = n.chapter_id " +
"LEFT JOIN T_Subject s ON s.id = ch.subject_id " +
"WHERE (s.id = 20013 OR s.name LIKE '%高等数学%' OR s.code = 'ADVMath') " +
"ORDER BY n.like_count DESC, n.created_at DESC " +
"LIMIT ? OFFSET ?";
String[] argsFallback = new String[]{
String.valueOf(limit),
String.valueOf(offset)
};
try (Cursor c2 = db.rawQuery(sqlFallback, argsFallback)) {
while (c2 != null && c2.moveToNext()) {
Note n = map(c2);
try {
int mi = c2.getColumnIndex("publisher_major_name");
if (mi >= 0 && !c2.isNull(mi)) n.setPublisherMajor(c2.getString(mi));
} catch (Exception ignored) {}
list.add(n);
}
}
}
if (offset == 0) {
recommendedCache.put(cacheKey, new java.util.ArrayList<>(list));
}
closeDatabase();
return list;
}
}

@ -0,0 +1,154 @@
package com.example.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.example.myapplication.domain.User;
public class UserRepository {
private final Context context;
private DatabaseHelper helper;
private SQLiteDatabase db;
public UserRepository(Context context) {
this.context = context.getApplicationContext();
}
public void openDatabase() {
if (helper == null) helper = new DatabaseHelper(context);
if (db == null || !db.isOpen()) db = helper.getWritableDatabase();
}
public void closeDatabase() {
if (db != null && db.isOpen()) db.close();
db = null;
}
public boolean insertUser(User user) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("account", user.getAccount());
cv.put("name", user.getName());
cv.put("password", user.getPassword());
cv.put("type", user.getType());
cv.put("point", user.getPoints());
if (user.getGrade() != null) cv.put("grade", String.valueOf(user.getGrade())); else cv.putNull("grade");
if (user.getMajorId() != null) cv.put("major_id", user.getMajorId()); else cv.putNull("major_id");
long r = db.insertWithOnConflict("T_User", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
closeDatabase();
return r != -1;
}
public boolean insertUserWithGradeText(User user, String gradeText) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("account", user.getAccount());
cv.put("name", user.getName());
cv.put("password", user.getPassword());
cv.put("type", user.getType());
cv.put("point", user.getPoints());
if (gradeText != null && !gradeText.isEmpty()) cv.put("grade", gradeText); else cv.putNull("grade");
if (user.getMajorId() != null) cv.put("major_id", user.getMajorId()); else cv.putNull("major_id");
long r = db.insertWithOnConflict("T_User", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
closeDatabase();
return r != -1;
}
public boolean deleteUser(User user) {
openDatabase();
int rows = db.delete("T_User", "account=?", new String[]{user.getAccount()});
closeDatabase();
return rows > 0;
}
public boolean updateUser(User user) {
openDatabase();
ContentValues cv = new ContentValues();
cv.put("name", user.getName());
cv.put("password", user.getPassword());
cv.put("type", user.getType());
if (user.getPoints() >= 0) cv.put("point", user.getPoints());
if (user.getGrade() != null) cv.put("grade", String.valueOf(user.getGrade()));
if (user.getMajorId() != null) cv.put("major_id", user.getMajorId());
int rows = db.update("T_User", cv, "account=?", new String[]{user.getAccount()});
closeDatabase();
return rows > 0;
}
public User getUserByAccount(String account) {
openDatabase();
User user = null;
try (Cursor c = db.query("T_User", null, "account=?", new String[]{account}, null, null, null)) {
if (c != null && c.moveToFirst()) {
Integer grade = null;
try {
int gi = c.getColumnIndex("grade");
if (gi >= 0 && !c.isNull(gi)) {
String gs = c.getString(gi);
try { grade = gs == null ? null : Integer.parseInt(gs.trim()); } catch (Exception e) { grade = null; }
}
} catch (Exception ignored) {}
Long majorId = null;
try { int mi = c.getColumnIndex("major_id"); if (mi >= 0 && !c.isNull(mi)) majorId = c.getLong(mi); } catch (Exception ignored) {}
user = new User(
c.getString(c.getColumnIndexOrThrow("account")),
c.getString(c.getColumnIndexOrThrow("name")),
c.getString(c.getColumnIndexOrThrow("password")),
c.getInt(c.getColumnIndexOrThrow("type")),
null,
c.getInt(c.getColumnIndexOrThrow("point")),
grade,
majorId
);
}
}
closeDatabase();
return user;
}
public boolean verifyUserValidity(String account, String password) {
openDatabase();
boolean ok = false;
try (Cursor c = db.query("T_User", new String[]{"account"}, "account=? AND password=?",
new String[]{account, password}, null, null, null)) {
ok = c != null && c.moveToFirst();
}
closeDatabase();
return ok;
}
public int getPoints(String account) {
openDatabase();
int pts = 0;
try (Cursor c = db.query("T_User", new String[]{"point"}, "account=?",
new String[]{account}, null, null, null)) {
if (c != null && c.moveToFirst()) pts = c.getInt(0);
}
closeDatabase();
return pts;
}
public String getGradeText(String account) {
openDatabase();
String grade = null;
try (Cursor c = db.query("T_User", new String[]{"grade"}, "account=?",
new String[]{account}, null, null, null)) {
if (c != null && c.moveToFirst()) {
int idx = c.getColumnIndex("grade");
if (idx >= 0 && !c.isNull(idx)) grade = c.getString(idx);
}
}
closeDatabase();
return grade;
}
public boolean addPoints(String account, int delta) {
openDatabase();
db.execSQL("UPDATE T_User SET point = COALESCE(point,0) + ? WHERE account=?",
new Object[]{delta, account});
closeDatabase();
return true;
}
}

@ -0,0 +1,75 @@
package com.example.myapplication.service;
import android.content.Context;
import android.content.SharedPreferences;
public class AuthService {
public static boolean login(Context ctx, String username, String password) {
if (username == null || password == null) return false;
username = username.trim();
password = password.trim();
if (username.isEmpty() || password.isEmpty()) return false;
// verify against repository
com.example.myapplication.repository.UserRepository repo = new com.example.myapplication.repository.UserRepository(ctx);
boolean valid = repo.verifyUserValidity(username, password);
if (!valid) return false;
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
prefs.edit()
.putString("currentUser", username)
.putBoolean("isLoggedIn", true)
.apply();
return true;
}
public static void logout(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
prefs.edit()
.remove("isLoggedIn")
.remove("currentUser")
.remove("searchKeyword")
.remove("filterSubject")
.remove("currentNoteId")
.apply();
}
public static boolean isLoggedIn(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
return prefs.getBoolean("isLoggedIn", false);
}
public static String getCurrentUser(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
return prefs.getString("currentUser", "张三");
}
public static String validateRegistration(String username, String password, String confirm, String gradeName, Long majorId) {
if (username == null || password == null || confirm == null || gradeName == null || majorId == null) return "请输入完整信息";
username = username.trim();
password = password.trim();
confirm = confirm.trim();
if (username.length() < 3 || username.length() > 20) {
return "用户名长度应为3-20个字符";
}
if (password.length() < 6) {
return "密码长度不能少于6位";
}
if (!password.equals(confirm)) {
return "两次输入的密码不一致";
}
java.util.List<String> allowed = java.util.Arrays.asList("大一","大二","大三","大四");
if (!allowed.contains(gradeName)) {
return "请选择正确的年级";
}
return null; // null 表示校验通过
}
// Persist user to repository after validation
public static boolean register(Context ctx, String username, String password, String gradeName, Long majorId) {
com.example.myapplication.repository.UserRepository repo = new com.example.myapplication.repository.UserRepository(ctx);
com.example.myapplication.domain.User u =
new com.example.myapplication.domain.User(username, username, password, 0, "", 10, null, majorId);
return repo.insertUserWithGradeText(u, gradeName);
}
}

@ -0,0 +1,168 @@
package com.example.myapplication.service;
import android.content.Context;
import android.text.TextUtils;
import com.example.myapplication.repository.CourseRepository;
import com.example.myapplication.domain.Course;
import com.example.myapplication.domain.Chapter;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class ClassificationService {
public static class Result {
public String category = "";
public String chapter = "";
public String bestId = "";
public String bestTitle = "";
public double confidence = 0.0;
public boolean isSegmented = false;
public java.util.List<Candidate> topCandidates = new java.util.ArrayList<>();
}
public static class Candidate {
public long chapterId;
public long subjectId;
public String title;
public double score;
}
private final Context context;
private final CourseRepository courseRepository;
private boolean hanLPInitialized = false;
public ClassificationService(Context ctx) {
this.context = ctx == null ? null : ctx.getApplicationContext();
this.courseRepository = new CourseRepository(this.context);
try {
List<Term> testSegment = HanLP.segment("测试HanLP");
hanLPInitialized = testSegment != null;
} catch (Throwable e) {
hanLPInitialized = false;
}
}
public Result classifyWithSegmentation(String text) {
Result out = new Result();
if (text == null || text.trim().isEmpty()) return out;
Set<String> noteTokens = extractKeywords(text);
List<Course> courses = courseRepository.getAllCourses();
double bestScore = 0.0;
Course bestCourse = null;
Chapter bestChapter = null;
java.util.List<Candidate> all = new java.util.ArrayList<>();
for (Course course : courses) {
List<Chapter> chapters = courseRepository.getChaptersBySubjectId(course.getId());
for (Chapter ch : chapters) {
double score = calculateMatchScore(noteTokens, course, ch);
if (score > 0) {
if (score > bestScore) {
bestScore = score;
bestCourse = course;
bestChapter = ch;
}
Candidate cand = new Candidate();
cand.chapterId = ch.getId();
cand.subjectId = course.getId();
cand.title = safe(ch.getName());
cand.score = score;
all.add(cand);
}
}
}
if (bestCourse != null && bestChapter != null) {
out.category = safe(courseName(bestCourse));
out.chapter = safe(bestChapter.getName());
out.bestId = String.valueOf(bestChapter.getId());
out.bestTitle = out.chapter;
out.confidence = Math.max(0.0, Math.min(1.0, bestScore));
}
if (!all.isEmpty()) {
java.util.Collections.sort(all, (a,b) -> Double.compare(b.score, a.score));
int k = Math.min(3, all.size());
for (int i = 0; i < k; i++) out.topCandidates.add(all.get(i));
}
return out;
}
private String courseName(Course c) {
String n = c.getName();
if (TextUtils.isEmpty(n)) n = c.getDescription();
return n == null ? "" : n;
}
private String safe(String s) { return s == null ? "" : s; }
private Set<String> extractKeywords(String text) {
Set<String> tokens = new HashSet<>();
if (hanLPInitialized) {
try {
List<Term> terms = HanLP.segment(text);
for (Term t : terms) {
String w = t.word;
if (w != null && w.length() > 1) tokens.add(w.toLowerCase(Locale.ROOT));
}
return tokens;
} catch (Throwable ignored) {}
}
String[] parts = text.toLowerCase(Locale.ROOT).split("[^\u4e00-\u9fa5a-zA-Z0-9]+");
for (String p : parts) if (p.length() > 1) tokens.add(p);
return tokens;
}
private double calculateMatchScore(Set<String> noteTokens, Course course, Chapter ch) {
double total = 0.0;
List<String> titleTokens = tokenize(ch.getName());
List<String> keywordTokens = splitComma(ch.getKeywords());
List<String> aliasTokens = splitComma(ch.getAliases());
total += overlapScore(noteTokens, keywordTokens) * 0.5;
total += overlapScore(noteTokens, titleTokens) * 0.3;
total += overlapScore(noteTokens, aliasTokens) * 0.2;
if (total < 0.2) {
List<String> courseTokens = tokenize(courseName(course));
total = Math.max(total, overlapScore(noteTokens, courseTokens) * 0.2);
}
return total;
}
private List<String> tokenize(String text) {
List<String> out = new ArrayList<>();
if (text == null || text.isEmpty()) return out;
if (hanLPInitialized) {
try {
List<Term> terms = HanLP.segment(text);
for (Term t : terms) {
String w = t.word;
if (w != null && w.length() > 1) out.add(w.toLowerCase(Locale.ROOT));
}
return out;
} catch (Throwable ignored) {}
}
String[] parts = text.toLowerCase(Locale.ROOT).split("[^\u4e00-\u9fa5a-zA-Z0-9]+");
for (String p : parts) if (p.length() > 1) out.add(p);
return out;
}
private List<String> splitComma(String s) {
List<String> out = new ArrayList<>();
if (s == null || s.trim().isEmpty()) return out;
String[] arr = s.split(",");
for (String x : arr) {
String v = x.trim();
if (!v.isEmpty()) out.add(v.toLowerCase(Locale.ROOT));
}
return out;
}
private double overlapScore(Set<String> noteTokens, List<String> targetTokens) {
if (noteTokens.isEmpty() || targetTokens == null || targetTokens.isEmpty()) return 0.0;
int hit = 0;
for (String t : targetTokens) if (noteTokens.contains(t)) hit++;
double base = (double) hit / (double) Math.max(1, targetTokens.size());
return Math.max(0.0, Math.min(1.0, base));
}
}

@ -0,0 +1,37 @@
package com.example.myapplication.service;
import android.content.Context;
import com.example.myapplication.domain.Comment;
import java.util.List;
public class CommentService {
public static List<Comment> listComments(Context ctx, long noteId) {
com.example.myapplication.repository.CommentRepository repo = new com.example.myapplication.repository.CommentRepository(ctx);
return repo.listByNoteId(noteId);
}
public static String validateContent(String content) {
if (content == null) return "内容不能为空";
String t = content.trim();
if (t.isEmpty()) return "内容不能为空";
if (t.length() > 500) return "内容过长";
return null;
}
public static boolean addComment(Context ctx, String userId, long noteId, String content) {
String err = validateContent(content);
if (err != null) return false;
if (userId == null || userId.trim().isEmpty()) return false;
com.example.myapplication.repository.CommentRepository repo = new com.example.myapplication.repository.CommentRepository(ctx);
long r = repo.add(userId, noteId, content.trim());
return r != -1;
}
public static boolean deleteOwn(Context ctx, long id) {
String userId = com.example.myapplication.service.AuthService.getCurrentUser(ctx);
com.example.myapplication.repository.CommentRepository repo = new com.example.myapplication.repository.CommentRepository(ctx);
return repo.delete(id, userId);
}
}

@ -0,0 +1,160 @@
package com.example.myapplication.service;
import android.content.Context;
import android.util.Log;
import com.example.myapplication.domain.Chapter;
import com.example.myapplication.domain.Course;
import com.example.myapplication.repository.CourseRepository;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* -
*/
public class DataInitializer {
private static final String TAG = "DataInitializer";
private final Context context;
private final CourseRepository courseRepository;
// 课程大纲文件列表
private static final String[] COURSE_OUTLINE_FILES = {};
public DataInitializer(Context context) {
this.context = context.getApplicationContext();
this.courseRepository = new CourseRepository(context);
}
/**
*
*/
public void initializeAllCourses() {
Log.i(TAG, "开始初始化课程数据...");
try {
courseRepository.openDatabase();
// 检查是否已有课程数据
List<Course> existingCourses = courseRepository.getAllCourses();
if (!existingCourses.isEmpty()) {
Log.i(TAG, "数据库中已有课程数据,跳过初始化");
return;
}
// 兼容现有数据库不再从assets加载直接返回
Log.i(TAG, "课程数据初始化完成");
} catch (Exception e) {
Log.e(TAG, "初始化课程数据失败", e);
} finally {
courseRepository.closeDatabase();
}
}
/**
* assets
*/
private void loadCourseFromAssets(String fileName) throws Exception { }
/**
*
*/
private Course parseCourse(JSONObject jsonObject) throws Exception {
String courseId = jsonObject.optString("id");
String courseTitle = jsonObject.optString("title");
if (courseId.isEmpty() || courseTitle.isEmpty()) {
// 尝试其他字段名
courseId = jsonObject.optString("courseId");
courseTitle = jsonObject.optString("courseTitle");
}
if (courseId.isEmpty() || courseTitle.isEmpty()) {
return null;
}
Course course = new Course();
course.setId(Integer.parseInt(courseId.replaceAll("\\D", "")));
course.setName(courseTitle);
course.setCode(courseId);
course.setDescription(jsonObject.optString("description", ""));
return course;
}
/**
*
*/
private List<Chapter> parseChapters(JSONObject jsonObject, int courseId) throws Exception {
List<Chapter> chapters = new ArrayList<>();
JSONArray nodesArray = jsonObject.optJSONArray("nodes");
if (nodesArray == null) {
nodesArray = jsonObject.optJSONArray("children");
}
if (nodesArray != null) {
for (int i = 0; i < nodesArray.length(); i++) {
JSONObject node = nodesArray.getJSONObject(i);
parseChapterNode(node, courseId, chapters, 0);
}
}
return chapters;
}
/**
*
*/
private void parseChapterNode(JSONObject node, int courseId, List<Chapter> chapters, int level) throws Exception {
String chapterId = node.optString("id");
String title = node.optString("title");
if (!chapterId.isEmpty() && !title.isEmpty()) {
Chapter chapter = new Chapter();
chapter.setId(Integer.parseInt(chapterId.replaceAll("\\D", "")));
chapter.setName(title);
chapter.setCourseId(courseId);
chapter.setOrderIndex(chapters.size() + 1);
// 设置关键词
JSONArray keywordsArray = node.optJSONArray("keywords");
if (keywordsArray != null && keywordsArray.length() > 0) {
StringBuilder keywords = new StringBuilder();
for (int i = 0; i < keywordsArray.length(); i++) {
if (i > 0) keywords.append(",");
keywords.append(keywordsArray.getString(i));
}
chapter.setKeywords(keywords.toString());
}
// 设置别名
JSONArray aliasesArray = node.optJSONArray("aliases");
if (aliasesArray != null && aliasesArray.length() > 0) {
StringBuilder aliases = new StringBuilder();
for (int i = 0; i < aliasesArray.length(); i++) {
if (i > 0) aliases.append(",");
aliases.append(aliasesArray.getString(i));
}
chapter.setAliases(aliases.toString());
}
chapters.add(chapter);
}
// 递归处理子节点
JSONArray childrenArray = node.optJSONArray("children");
if (childrenArray != null) {
for (int i = 0; i < childrenArray.length(); i++) {
JSONObject childNode = childrenArray.getJSONObject(i);
parseChapterNode(childNode, courseId, chapters, level + 1);
}
}
}
}

@ -0,0 +1,118 @@
package com.example.myapplication.service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class DownloadService {
public static boolean saveToDownloads(Context ctx, String srcPath, String displayName, String mime) {
if (srcPath == null || srcPath.isEmpty()) return false;
File src = new File(srcPath);
if (!src.exists()) return false;
if (displayName == null || displayName.trim().isEmpty()) {
displayName = src.getName();
}
if (mime == null || mime.isEmpty()) mime = guessMime(displayName);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = ctx.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, displayName);
values.put(MediaStore.Downloads.MIME_TYPE, mime);
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
if (uri == null) return false;
try (FileInputStream in = new FileInputStream(src);
OutputStream out = resolver.openOutputStream(uri)) {
if (out == null) return false;
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
out.flush();
}
return true;
} else {
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
if (dir != null && !dir.exists()) dir.mkdirs();
File dst = new File(dir, displayName);
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
out.flush();
}
return true;
}
} catch (Exception e) {
android.util.Log.e("DownloadService", "saveToDownloads failed", e);
return false;
}
}
public static String guessMime(String name) {
String lower = name.toLowerCase();
if (lower.endsWith(".pdf")) return "application/pdf";
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
if (lower.endsWith(".png")) return "image/png";
if (lower.endsWith(".txt")) return "text/plain";
if (lower.endsWith(".doc")) return "application/msword";
if (lower.endsWith(".docx")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
return "application/octet-stream";
}
public static boolean saveTextToDownloads(Context ctx, String displayName, String mime, String content) {
android.net.Uri uri = saveTextToDownloadsReturnUri(ctx, displayName, mime, content);
return uri != null;
}
public static android.net.Uri saveTextToDownloadsReturnUri(Context ctx, String displayName, String mime, String content) {
if (displayName == null || displayName.trim().isEmpty()) displayName = System.currentTimeMillis() + ".txt";
if (mime == null || mime.isEmpty()) mime = "text/plain";
if (content == null) content = "";
try {
byte[] data = content.getBytes("UTF-8");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
android.content.ContentResolver resolver = ctx.getContentResolver();
android.content.ContentValues values = new android.content.ContentValues();
values.put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, displayName);
values.put(android.provider.MediaStore.MediaColumns.MIME_TYPE, mime);
values.put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, android.os.Environment.DIRECTORY_DOWNLOADS);
values.put(android.provider.MediaStore.MediaColumns.IS_PENDING, 1);
android.net.Uri uri = resolver.insert(android.provider.MediaStore.Files.getContentUri("external"), values);
if (uri == null) return null;
try (java.io.OutputStream out = resolver.openOutputStream(uri)) {
if (out == null) return null;
out.write(data);
out.flush();
}
android.content.ContentValues done = new android.content.ContentValues();
done.put(android.provider.MediaStore.MediaColumns.IS_PENDING, 0);
resolver.update(uri, done, null, null);
return uri;
} else {
java.io.File dir = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS);
if (dir != null && !dir.exists()) dir.mkdirs();
java.io.File dst = new java.io.File(dir, displayName);
try (java.io.FileOutputStream out = new java.io.FileOutputStream(dst)) {
out.write(data);
out.flush();
}
return android.net.Uri.fromFile(dst);
}
} catch (Exception e) {
android.util.Log.e("DownloadService", "saveTextToDownloadsReturnUri failed", e);
return null;
}
}
}

@ -0,0 +1,136 @@
package com.example.myapplication.service;
import android.content.Context;
import android.util.Log;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary;
import java.util.ArrayList;
import java.util.List;
public class EnhancedKeywordClassifier {
private final Context context;
private boolean hanLPInitialized = false;
public static class Result {
public String bestId = "";
public String bestTitle = "";
public double confidence = 0.0;
public boolean isSegmented = false;
public List<Object> topCandidates = new ArrayList<>();
}
public static class ClassificationResult {
public String category = "";
public String chapter = "";
public String bestId = "";
public String bestTitle = "";
public double confidence = 0.0;
public boolean isSegmented = false;
public List<Object> topCandidates = new ArrayList<>();
}
public EnhancedKeywordClassifier(Context ctx) {
this.context = ctx == null ? null : ctx.getApplicationContext();
try {
List<Term> testSegment = HanLP.segment("测试HanLP");
hanLPInitialized = true;
Log.i("HanLP", "HanLP初始化成功分词测试" + testSegment.toString());
} catch (Throwable e) {
Log.w("HanLP", "HanLP初始化失败将使用基础分类: " + String.valueOf(e));
hanLPInitialized = false;
}
}
public EnhancedKeywordClassifier() {
this(null);
}
public ClassificationResult classifyWithSegmentation(String text) {
Result base = performBasicClassification(text);
ClassificationResult resultOut = new ClassificationResult();
resultOut.bestId = base.bestId;
resultOut.bestTitle = base.bestTitle;
resultOut.category = base.bestTitle;
resultOut.chapter = base.bestTitle;
resultOut.confidence = base.confidence;
resultOut.isSegmented = false;
resultOut.topCandidates = base.topCandidates;
if (text == null || text.trim().isEmpty()) {
resultOut.bestTitle = "默认分类";
resultOut.category = resultOut.bestTitle;
resultOut.chapter = resultOut.bestTitle;
resultOut.confidence = 0.0;
return resultOut;
}
if (hanLPInitialized && base.confidence < 0.7) {
try {
List<Term> terms = HanLP.segment(text);
StringBuilder segmentedText = new StringBuilder();
StringBuilder keywordsText = new StringBuilder();
for (Term term : terms) {
String word = term.word;
String nature = term.nature != null ? term.nature.toString() : "";
if (word.length() > 1 && !CoreStopWordDictionary.contains(word)) {
segmentedText.append(word).append(" ");
if (nature.startsWith("n") || nature.startsWith("v") || nature.startsWith("a")) {
keywordsText.append(word).append(" ");
}
}
}
String enhancedText = segmentedText.toString() + " " + keywordsText.toString();
Result segmentedBase = performBasicClassification(enhancedText);
ClassificationResult segmentedOut = new ClassificationResult();
segmentedOut.bestId = segmentedBase.bestId;
segmentedOut.bestTitle = segmentedBase.bestTitle;
segmentedOut.category = segmentedBase.bestTitle;
segmentedOut.chapter = segmentedBase.bestTitle;
segmentedOut.confidence = segmentedBase.confidence;
segmentedOut.isSegmented = true;
segmentedOut.topCandidates = segmentedBase.topCandidates;
if (segmentedOut.confidence > resultOut.confidence) {
segmentedOut.confidence = Math.min(0.95, segmentedOut.confidence * 1.2);
return segmentedOut;
}
} catch (Throwable e) {
Log.w("HanLP", "HanLP分词失败使用基础分类: " + String.valueOf(e));
}
}
return resultOut;
}
private Result performBasicClassification(String text) {
Result result = new Result();
if (text == null) return result;
String lowerText = text.toLowerCase();
if (lowerText.contains("程序") || lowerText.contains("java") || lowerText.contains("方法") || lowerText.contains("类")) {
result.bestTitle = "程序设计";
result.confidence = 0.8;
} else if (lowerText.contains("数据结构") || lowerText.contains("数组") || lowerText.contains("链表") || lowerText.contains("栈") || lowerText.contains("队列")) {
result.bestTitle = "数据结构";
result.confidence = 0.75;
} else if (lowerText.contains("高等数学") || lowerText.contains("微积分") || lowerText.contains("导数") || lowerText.contains("积分") || lowerText.contains("极限")) {
result.bestTitle = "高等数学";
result.confidence = 0.7;
} else if (lowerText.contains("人工智能") || lowerText.contains("ai") || lowerText.contains("机器学习") || lowerText.contains("深度学习")) {
result.bestTitle = "人工智能";
result.confidence = 0.8;
} else if (lowerText.contains("物联网") || lowerText.contains("iot") || lowerText.contains("传感器") || lowerText.contains("rfid")) {
result.bestTitle = "物联网";
result.confidence = 0.75;
} else if (lowerText.contains("英语") || lowerText.contains("english")) {
result.bestTitle = "大学英语";
result.confidence = 0.6;
} else if (lowerText.contains("数据库") || lowerText.contains("sql") || lowerText.contains("关系模型")) {
result.bestTitle = "数据库系统";
result.confidence = 0.75;
} else if (lowerText.contains("机器人") || lowerText.contains("robot") || lowerText.contains("robotics")) {
result.bestTitle = "第1章 机器人学概述";
result.confidence = 0.7;
} else {
result.bestTitle = "程序设计";
result.confidence = 0.5;
}
return result;
}
}

@ -0,0 +1,28 @@
package com.example.myapplication.service;
public class LikeService {
public static int increment(int current) {
return current + 1;
}
// 新增:使用点赞仓库
public static boolean addLike(android.content.Context ctx, String userId, long noteId) {
com.example.myapplication.repository.LikeRepository repo = new com.example.myapplication.repository.LikeRepository(ctx);
return repo.addLike(userId, noteId);
}
public static boolean removeLike(android.content.Context ctx, String userId, long noteId) {
com.example.myapplication.repository.LikeRepository repo = new com.example.myapplication.repository.LikeRepository(ctx);
return repo.removeLike(userId, noteId);
}
public static boolean hasLiked(android.content.Context ctx, String userId, long noteId) {
com.example.myapplication.repository.LikeRepository repo = new com.example.myapplication.repository.LikeRepository(ctx);
return repo.checkUserLikeStatus(userId, noteId);
}
public static int likeCount(android.content.Context ctx, long noteId) {
com.example.myapplication.repository.LikeRepository repo = new com.example.myapplication.repository.LikeRepository(ctx);
return repo.getLikeCount(noteId);
}
}

@ -0,0 +1,20 @@
package com.example.myapplication.service;
import android.content.Context;
import android.content.SharedPreferences;
public class NoteService {
public static void setCurrentNoteId(Context ctx, int id) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
prefs.edit().putInt("currentNoteId", id).apply();
}
public static int getCurrentNoteId(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
return prefs.getInt("currentNoteId", -1);
}
public static int incrementLikeCount(int current) {
return current + 1;
}
}

@ -0,0 +1,313 @@
package com.example.myapplication.service;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.util.Log;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;
// 拉丁识别器(如依赖不可用,仍保留中文识别路径)
// import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.text.PDFTextStripper;
public class OCRService {
public interface Callback {
void onSuccess(String text);
void onFailure(Exception e);
}
public static void recognize(Context ctx, InputImage image, Callback cb) {
com.google.mlkit.vision.text.TextRecognizer recognizer =
TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());
recognizer.process(image)
.addOnSuccessListener(visionText -> {
if (cb != null) cb.onSuccess(visionText.getText());
})
.addOnFailureListener(e -> {
if (cb != null) cb.onFailure(e);
});
}
/**
* 便
* @param filePath
* @return
*/
public static String extractTextFromFile(Context ctx, String filePath) {
if (filePath == null || filePath.isEmpty()) {
return "";
}
try {
File file = new File(filePath);
if (!file.exists()) {
Log.e("OCRService", "文件不存在: " + filePath);
return "";
}
// 根据文件扩展名判断处理方式
String extension = getFileExtension(filePath);
if (isImageFile(extension)) {
// 首次:标准预处理
String txt = extractTextFromImage(ctx, filePath, false);
if (txt != null && !txt.trim().isEmpty()) return txt;
// 兜底:高对比度预处理重试一次
return extractTextFromImage(ctx, filePath, true);
} else if (isPdfFile(extension)) {
return extractTextFromPdf(ctx, filePath);
} else {
Log.w("OCRService", "不支持的文件类型: " + extension);
return "";
}
} catch (Exception e) {
Log.e("OCRService", "处理文件失败: " + filePath, e);
return "";
}
}
private static String extractTextFromImage(Context ctx, String imagePath, boolean highContrast) {
try {
Bitmap bitmap = loadAndPreprocessImage(imagePath, 2500, highContrast);
if (bitmap == null) {
Log.e("OCRService", "无法解码图片: " + imagePath);
return "";
}
Bitmap bin = adaptiveBinarize(bitmap);
Bitmap cropped = cropContent(bin);
java.util.List<Bitmap> lines = segmentLines(cropped);
StringBuilder sb = new StringBuilder();
for (Bitmap ln : lines) {
String t = com.example.myapplication.service.TesseractService.recognizeBitmap(ctx, ln, "chi_sim", 7, 1);
if (t != null && !t.trim().isEmpty()) {
if (sb.length() > 0) sb.append('\n');
sb.append(t.trim());
}
ln.recycle();
}
if (sb.length() > 0) {
if (bitmap != null && !bitmap.isRecycled()) bitmap.recycle();
if (bin != null && !bin.isRecycled()) bin.recycle();
if (cropped != null && !cropped.isRecycled()) cropped.recycle();
return sb.toString();
}
InputImage image = InputImage.fromBitmap(bitmap, 0);
com.google.mlkit.vision.text.TextRecognizer recognizer =
TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());
final String[] result = {""};
final Exception[] exception = {null};
CountDownLatch latch = new CountDownLatch(1);
recognizer.process(image)
.addOnSuccessListener(visionText -> { result[0] = visionText.getText(); latch.countDown(); })
.addOnFailureListener(e -> { exception[0] = e; latch.countDown(); });
try { latch.await(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.e("OCRService", "OCR处理被中断", e); }
if (bitmap != null && !bitmap.isRecycled()) bitmap.recycle();
if (bin != null && !bin.isRecycled()) bin.recycle();
if (cropped != null && !cropped.isRecycled()) cropped.recycle();
if (exception[0] != null) return "";
return result[0] == null ? "" : result[0];
} catch (Exception e) {
Log.e("OCRService", "图片OCR处理失败: " + imagePath, e);
return "";
}
}
private static Bitmap loadAndPreprocessImage(String path, int maxDim, boolean highContrast) {
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap src = BitmapFactory.decodeFile(path, opts);
if (src == null) return null;
int w = src.getWidth();
int h = src.getHeight();
int max = Math.max(w, h);
float scale = maxDim > 0 && max > maxDim ? (maxDim * 1f / max) : 1f;
Bitmap scaled = src;
if (scale < 1f) {
int nw = Math.max(1, (int) (w * scale));
int nh = Math.max(1, (int) (h * scale));
scaled = Bitmap.createScaledBitmap(src, nw, nh, true);
if (scaled != src) src.recycle();
}
if (!highContrast) return scaled;
Bitmap out = Bitmap.createBitmap(scaled.getWidth(), scaled.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(out);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
ColorMatrix cm = new ColorMatrix();
float c = 1.35f; // 对比度提升
cm.set(new float[]{
c, 0, 0, 0, 0,
0, c, 0, 0, 0,
0, 0, c, 0, 0,
0, 0, 0, 1, 0
});
paint.setColorFilter(new ColorMatrixColorFilter(cm));
canvas.drawBitmap(scaled, 0, 0, paint);
if (scaled != out) scaled.recycle();
return out;
} catch (Exception e) {
Log.e("OCRService", "预处理失败: " + path, e);
return BitmapFactory.decodeFile(path);
}
}
private static Bitmap adaptiveBinarize(Bitmap src) {
int w = src.getWidth();
int h = src.getHeight();
int[] pix = new int[w * h];
src.getPixels(pix, 0, w, 0, 0, w, h);
int[] gray = new int[w * h];
for (int i = 0; i < pix.length; i++) {
int p = pix[i];
int r = (p >> 16) & 0xFF;
int g = (p >> 8) & 0xFF;
int b = p & 0xFF;
gray[i] = (int)(0.299 * r + 0.587 * g + 0.114 * b);
}
int win = Math.max(15, Math.min(w, 25));
int hw = win / 2;
long[] sum = new long[w * h];
for (int y = 0; y < h; y++) {
long row = 0;
for (int x = 0; x < w; x++) {
row += gray[y * w + x];
sum[y * w + x] = row + (y > 0 ? sum[(y - 1) * w + x] : 0);
}
}
int[] out = new int[w * h];
int c = 10;
for (int y = 0; y < h; y++) {
int y0 = Math.max(0, y - hw);
int y1 = Math.min(h - 1, y + hw);
for (int x = 0; x < w; x++) {
int x0 = Math.max(0, x - hw);
int x1 = Math.min(w - 1, x + hw);
int area = (x1 - x0 + 1) * (y1 - y0 + 1);
long s = sum[y1 * w + x1] - (x0 > 0 ? sum[y1 * w + (x0 - 1)] : 0) - (y0 > 0 ? sum[(y0 - 1) * w + x1] : 0) + (y0 > 0 && x0 > 0 ? sum[(y0 - 1) * w + (x0 - 1)] : 0);
int mean = (int)(s / area);
int g = gray[y * w + x];
int v = g < mean - c ? 0 : 255;
out[y * w + x] = 0xFF000000 | (v << 16) | (v << 8) | v;
}
}
Bitmap bin = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bin.setPixels(out, 0, w, 0, 0, w, h);
return bin;
}
private static Bitmap cropContent(Bitmap src) {
int w = src.getWidth();
int h = src.getHeight();
int[] pix = new int[w * h];
src.getPixels(pix, 0, w, 0, 0, w, h);
int top = 0, bottom = h - 1, left = 0, right = w - 1;
int thr = Math.max(10, w / 50);
for (int y = 0; y < h; y++) {
int cnt = 0;
for (int x = 0; x < w; x++) { if ((pix[y * w + x] & 0xFF) < 128) cnt++; }
if (cnt > thr) { top = y; break; }
}
for (int y = h - 1; y >= 0; y--) {
int cnt = 0;
for (int x = 0; x < w; x++) { if ((pix[y * w + x] & 0xFF) < 128) cnt++; }
if (cnt > thr) { bottom = y; break; }
}
for (int x = 0; x < w; x++) {
int cnt = 0;
for (int y = 0; y < h; y++) { if ((pix[y * w + x] & 0xFF) < 128) cnt++; }
if (cnt > Math.max(5, h / 80)) { left = x; break; }
}
for (int x = w - 1; x >= 0; x--) {
int cnt = 0;
for (int y = 0; y < h; y++) { if ((pix[y * w + x] & 0xFF) < 128) cnt++; }
if (cnt > Math.max(5, h / 80)) { right = x; break; }
}
int cw = Math.max(1, right - left + 1);
int ch = Math.max(1, bottom - top + 1);
Bitmap out = Bitmap.createBitmap(src, left, top, cw, ch);
return out;
}
private static java.util.List<Bitmap> segmentLines(Bitmap src) {
int w = src.getWidth();
int h = src.getHeight();
int[] pix = new int[w * h];
src.getPixels(pix, 0, w, 0, 0, w, h);
int[] proj = new int[h];
for (int y = 0; y < h; y++) {
int cnt = 0;
for (int x = 0; x < w; x++) { if ((pix[y * w + x] & 0xFF) < 128) cnt++; }
proj[y] = cnt;
}
java.util.List<Bitmap> lines = new java.util.ArrayList<>();
int minRow = Math.max(8, w / 80);
int y = 0;
while (y < h) {
while (y < h && proj[y] <= minRow) y++;
if (y >= h) break;
int start = y;
while (y < h && proj[y] > minRow) y++;
int end = y - 1;
int lh = Math.max(1, end - start + 1);
Bitmap ln = Bitmap.createBitmap(src, 0, start, w, lh);
lines.add(ln);
}
return lines;
}
private static String extractTextFromPdf(Context ctx, String filePath) {
try {
if (ctx != null) {
try { PDFBoxResourceLoader.init(ctx.getApplicationContext()); } catch (Exception ignored) {}
}
PDDocument doc = PDDocument.load(new File(filePath));
try {
PDFTextStripper stripper = new PDFTextStripper();
try { stripper.setSortByPosition(true); } catch (Exception ignored) {}
String text = stripper.getText(doc);
if (text != null && !text.trim().isEmpty() && text.trim().length() >= 500) return text;
} finally {
doc.close();
}
} catch (Exception e) {
Log.w("OCRService", "PDF文本直读失败路径: " + filePath, e);
}
PdfOcrService.Config cfg = new PdfOcrService.Config();
String ocrText = PdfOcrService.recognizePdf(ctx, filePath, cfg);
return ocrText == null ? "" : ocrText;
}
private static String getFileExtension(String filePath) {
int lastDot = filePath.lastIndexOf('.');
if (lastDot > 0 && lastDot < filePath.length() - 1) {
return filePath.substring(lastDot + 1).toLowerCase();
}
return "";
}
private static boolean isImageFile(String extension) {
return extension.equals("jpg") || extension.equals("jpeg") ||
extension.equals("png") || extension.equals("bmp") ||
extension.equals("webp");
}
private static boolean isPdfFile(String extension) {
return extension.equals("pdf");
}
}

@ -0,0 +1,170 @@
package com.example.myapplication.service;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.rendering.PDFRenderer;
import java.io.File;
public class PdfOcrService {
private static final String TAG = "PdfOcrService";
public static class Config {
public int maxPages = -1;
public float dpiPrimary = 192f;
public float dpiFallback = 300f;
public int maxSidePx = 3500;
public String lang = "chi_sim+eng";
public int psmPrimary = 4; // 单列
public int psmAlt = 6; // 整页
}
public static String recognizePdf(Context ctx, String filePath, Config cfg) {
if (cfg == null) cfg = new Config();
StringBuilder all = new StringBuilder();
PDDocument doc = null;
try {
try { PDFBoxResourceLoader.init(ctx.getApplicationContext()); } catch (Exception ignored) {}
doc = PDDocument.load(new File(filePath));
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
int limit = cfg.maxPages <= 0 ? pageCount : Math.min(pageCount, cfg.maxPages);
for (int i = 0; i < limit; i++) {
Bitmap bmp = null;
try {
bmp = renderer.renderImageWithDPI(i, cfg.dpiPrimary);
// 限制长边
int w = bmp.getWidth();
int h = bmp.getHeight();
int max = Math.max(w, h);
if (max > cfg.maxSidePx) {
float scale = cfg.maxSidePx * 1f / max;
int nw = Math.max(1, (int)(w * scale));
int nh = Math.max(1, (int)(h * scale));
bmp = Bitmap.createScaledBitmap(bmp, nw, nh, true);
}
java.util.List<Bitmap> cols = segmentColumns(bmp);
String pageText;
if (cols != null && cols.size() > 1) {
StringBuilder sb = new StringBuilder();
for (Bitmap colBmp : cols) {
String ca = TesseractService.recognizeBitmap(ctx, colBmp, cfg.lang, cfg.psmPrimary, 1);
String cb = TesseractService.recognizeBitmap(ctx, colBmp, cfg.lang, cfg.psmAlt, 1);
String ct = (cb != null && (ca == null || cb.length() > ca.length())) ? cb : ca;
if (ct != null && !ct.trim().isEmpty()) {
if (sb.length() > 0) sb.append('\n');
sb.append(ct.trim());
}
try { if (colBmp != null && !colBmp.isRecycled()) colBmp.recycle(); } catch (Exception ignored) {}
}
pageText = sb.toString();
} else {
String a = TesseractService.recognizeBitmap(ctx, bmp, cfg.lang, cfg.psmPrimary, 1);
String b = TesseractService.recognizeBitmap(ctx, bmp, cfg.lang, cfg.psmAlt, 1);
pageText = (b != null && (a == null || b.length() > a.length())) ? b : a;
}
// 若文本过短提升DPI重渲染重试
if (pageText == null || pageText.trim().length() < 200) {
try { if (bmp != null && !bmp.isRecycled()) bmp.recycle(); } catch (Exception ignored) {}
bmp = renderer.renderImageWithDPI(i, cfg.dpiFallback);
int w2 = bmp.getWidth(); int h2 = bmp.getHeight(); int max2 = Math.max(w2, h2);
if (max2 > cfg.maxSidePx) {
float scale2 = cfg.maxSidePx * 1f / max2;
int nw2 = Math.max(1, (int)(w2 * scale2));
int nh2 = Math.max(1, (int)(h2 * scale2));
bmp = Bitmap.createScaledBitmap(bmp, nw2, nh2, true);
}
String a2 = TesseractService.recognizeBitmap(ctx, bmp, cfg.lang, cfg.psmPrimary, 1);
String b2 = TesseractService.recognizeBitmap(ctx, bmp, cfg.lang, cfg.psmAlt, 1);
pageText = (b2 != null && (a2 == null || b2.length() > a2.length())) ? b2 : a2;
}
// 若仍短,再做高对比度二值化并用稀疏文本模式捕捉残缺部分
if (pageText == null || pageText.trim().length() < 200) {
Bitmap hc = toHighContrast(bmp);
String s11 = TesseractService.recognizeBitmap(ctx, hc, cfg.lang, 11, 1); // Sparse text
String s06 = TesseractService.recognizeBitmap(ctx, hc, cfg.lang, cfg.psmAlt, 1);
String best = (s06 != null && (s11 == null || s06.length() > s11.length())) ? s06 : s11;
if (best != null && !best.trim().isEmpty()) {
pageText = (pageText == null ? "" : pageText);
if (!pageText.isEmpty()) pageText += "\n";
pageText += best.trim();
}
try { if (hc != null && !hc.isRecycled()) hc.recycle(); } catch (Exception ignored) {}
}
if (pageText != null && !pageText.trim().isEmpty()) {
if (all.length() > 0) all.append('\n');
all.append(pageText.trim());
}
} catch (Exception e) {
Log.w(TAG, "渲染或识别第" + (i+1) + "页失败", e);
} finally {
try { if (bmp != null && !bmp.isRecycled()) bmp.recycle(); } catch (Exception ignored) {}
}
}
} catch (Exception e) {
Log.e(TAG, "PDF识别失败: " + filePath, e);
} finally {
try { if (doc != null) doc.close(); } catch (Exception ignored) {}
}
return all.toString();
}
private static java.util.List<Bitmap> segmentColumns(Bitmap src) {
int w = src.getWidth();
int h = src.getHeight();
int[] pix = new int[w * h];
src.getPixels(pix, 0, w, 0, 0, w, h);
int[] projX = new int[w];
for (int x = 0; x < w; x++) {
int cnt = 0;
for (int y = 0; y < h; y++) {
int p = pix[y * w + x] & 0xFF;
if (p < 160) cnt++;
}
projX[x] = cnt;
}
java.util.List<int[]> ranges = new java.util.ArrayList<>();
int minCol = Math.max(h / 60, 10);
int x = 0;
while (x < w) {
while (x < w && projX[x] <= minCol) x++;
if (x >= w) break;
int start = x;
while (x < w && projX[x] > minCol) x++;
int end = x - 1;
if (end - start + 1 >= w / 10) ranges.add(new int[]{start, end});
}
if (ranges.size() <= 1) return java.util.Collections.emptyList();
java.util.List<Bitmap> cols = new java.util.ArrayList<>();
for (int[] r : ranges) {
int cw = Math.max(1, r[1] - r[0] + 1);
Bitmap col = Bitmap.createBitmap(src, r[0], 0, cw, h);
cols.add(col);
}
return cols;
}
private static Bitmap toHighContrast(Bitmap src) {
try {
Bitmap out = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
android.graphics.Canvas canvas = new android.graphics.Canvas(out);
android.graphics.Paint paint = new android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG);
android.graphics.ColorMatrix cm = new android.graphics.ColorMatrix();
float c = 1.4f;
cm.set(new float[]{
c, 0, 0, 0, 0,
0, c, 0, 0, 0,
0, 0, c, 0, 0,
0, 0, 0, 1, 0
});
paint.setColorFilter(new android.graphics.ColorMatrixColorFilter(cm));
canvas.drawBitmap(src, 0, 0, paint);
return out;
} catch (Exception e) {
return src;
}
}
}

@ -0,0 +1,20 @@
package com.example.myapplication.service;
public class RegistrationService {
public static String validateRegistration(String username, String password, String confirm) {
if (username == null || password == null || confirm == null) return "请输入完整信息";
username = username.trim();
password = password.trim();
confirm = confirm.trim();
if (username.length() < 3 || username.length() > 20) {
return "用户名长度应为3-20个字符";
}
if (password.length() < 6) {
return "密码长度不能少于6位";
}
if (!password.equals(confirm)) {
return "两次输入的密码不一致";
}
return null; // null 表示校验通过
}
}

@ -0,0 +1,16 @@
package com.example.myapplication.service;
import android.content.Context;
import android.content.SharedPreferences;
public class SearchService {
public static void saveKeyword(Context ctx, String keyword) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
prefs.edit().putString("searchKeyword", keyword).apply();
}
public static java.util.List<com.example.myapplication.domain.Note> search(Context ctx, String query) {
com.example.myapplication.repository.NoteRepository repo = new com.example.myapplication.repository.NoteRepository(ctx);
return repo.findNotesByKeywords(query, null);
}
}

@ -0,0 +1,69 @@
package com.example.myapplication.service;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
public class TesseractService {
private static final String TAG = "TesseractService";
private static final String TESSDATA_DIR = "tessdata";
private static final String[] REQUIRED_LANGS = new String[]{"chi_sim.traineddata", "eng.traineddata"};
public static boolean ensureTessDataInstalled(Context ctx) {
try {
File tessDir = new File(ctx.getFilesDir(), TESSDATA_DIR);
if (!tessDir.exists()) tessDir.mkdirs();
// 拷贝 assets/tessdata 下的模型到 files/tessdata
for (String lang : REQUIRED_LANGS) {
File out = new File(tessDir, lang);
if (out.exists() && out.length() > 0) continue;
try (InputStream is = ctx.getAssets().open(TESSDATA_DIR + "/" + lang)) {
try (FileOutputStream fos = new FileOutputStream(out)) {
byte[] buf = new byte[8192];
int r;
while ((r = is.read(buf)) > 0) fos.write(buf, 0, r);
}
} catch (Exception e) {
Log.w(TAG, "缺少模型文件: " + lang + ",请将其放入 assets/tessdata/", e);
return false;
}
}
return true;
} catch (Exception e) {
Log.e(TAG, "安装tessdata失败", e);
return false;
}
}
public static String recognizeBitmap(Context ctx, Bitmap bmp, String lang, int psm, int oem) {
if (bmp == null) return "";
boolean ok = ensureTessDataInstalled(ctx);
if (!ok) return "";
String dataPath = ctx.getFilesDir().getAbsolutePath();
com.googlecode.tesseract.android.TessBaseAPI api = new com.googlecode.tesseract.android.TessBaseAPI();
try {
if (!api.init(dataPath, lang)) {
Log.e(TAG, "Tesseract初始化失败");
return "";
}
try {
api.setPageSegMode(psm);
} catch (Throwable ignored) {}
try {
api.setVariable("user_defined_dpi", "300");
} catch (Throwable ignored) {}
api.setImage(bmp);
String text = api.getUTF8Text();
return text == null ? "" : text;
} catch (Exception e) {
Log.e(TAG, "Tesseract识别失败", e);
return "";
} finally {
try { api.end(); } catch (Throwable ignored) {}
}
}
}

@ -0,0 +1,94 @@
package com.example.myapplication.service;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.provider.OpenableColumns;
import android.util.Log;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;
// Keep only PDFBox and I/O for text extraction
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.text.PDFTextStripper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class UploadService {
public interface ProgressListener {
void onProgress(int progress);
void onCompleted();
}
public static void simulateUpload(Handler handler, ProgressListener listener) {
for (int i = 1; i <= 10; i++) {
int progress = i * 10;
handler.postDelayed(() -> {
if (listener != null) listener.onProgress(progress);
if (progress == 100 && listener != null) listener.onCompleted();
}, i * 200);
}
}
public static String getMimeType(Context ctx, Uri uri) {
ContentResolver cr = ctx.getContentResolver();
return cr.getType(uri);
}
public static String getDisplayName(Context ctx, Uri uri) {
String name = null;
try (Cursor cursor = ctx.getContentResolver()
.query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
name = cursor.getString(0);
}
} catch (Exception ignored) {}
return name;
}
// Removed: OcrCallback interface and any runTextRecognition code.
// OCR must be performed via OCRService.recognize(...)
public static String extractPdfText(Context ctx, Uri uri) throws IOException {
InputStream inputStream = ctx.getContentResolver().openInputStream(uri);
if (inputStream == null) return "";
java.io.File tmp = java.io.File.createTempFile("ocr_pdf_", ".pdf", ctx.getCacheDir());
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(tmp)) {
byte[] buf = new byte[8192];
int len;
while ((len = inputStream.read(buf)) != -1) fos.write(buf, 0, len);
} finally { inputStream.close(); }
String full = com.example.myapplication.service.OCRService.extractTextFromFile(ctx, tmp.getAbsolutePath());
try { tmp.delete(); } catch (Exception ignored) {}
return full == null ? "" : full;
}
public static String extractPlainText(Context ctx, Uri uri) throws IOException {
InputStream inputStream = ctx.getContentResolver().openInputStream(uri);
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
inputStream.close();
return result.toString("UTF-8");
}
public static String guessSubject(String lower, String[] dictMath, String[] dictPhysics, String[] dictChem, String[] dictEnglish, String[] dictCS, String[] dictRobotics) {
for (String k : dictRobotics) { if (lower.contains(k.toLowerCase())) return "机器人学导论"; }
for (String k : dictCS) { if (lower.contains(k.toLowerCase())) return "计算机基础"; }
for (String k : dictMath) { if (lower.contains(k.toLowerCase())) return "数学"; }
for (String k : dictPhysics) { if (lower.contains(k.toLowerCase())) return "物理"; }
for (String k : dictChem) { if (lower.contains(k.toLowerCase())) return "化学"; }
for (String k : dictEnglish) { if (lower.contains(k.toLowerCase())) return "英语"; }
return null;
}
}

@ -0,0 +1,16 @@
package com.example.myapplication.service;
import android.content.Context;
import android.content.SharedPreferences;
public class UserService {
public static String getUser(Context ctx) {
SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
String account = prefs.getString("currentUser", null);
if (account == null) return "张三";
com.example.myapplication.repository.UserRepository repo = new com.example.myapplication.repository.UserRepository(ctx);
com.example.myapplication.domain.User u = repo.getUserByAccount(account);
return u != null ? u.getName() : account;
}
}

@ -0,0 +1,44 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import com.example.myapplication.Utils;
import com.example.myapplication.service.AuthService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class LoginUI extends AppCompatActivity {
private SharedPreferences prefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
EditText usernameEt = findViewById(R.id.usernameInput);
EditText passwordEt = findViewById(R.id.passwordInput);
Button loginBtn = findViewById(R.id.loginBtn);
TextView registerLink = findViewById(R.id.registerLink);
loginBtn.setOnClickListener(v -> {
String username = usernameEt.getText().toString().trim();
String password = passwordEt.getText().toString().trim();
if (!AuthService.login(this, username, password)) {
Utils.toast(this, "请输入用户名和密码");
return;
}
Utils.toast(this, "登录成功!即将跳转到主界面");
startActivity(new Intent(this, MainUI.class));
finish();
});
registerLink.setOnClickListener(v ->
startActivity(new Intent(this, RegisterUI.class)));
}
}

@ -0,0 +1,302 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import com.example.myapplication.Utils;
import com.example.myapplication.service.AuthService;
import com.example.myapplication.service.NoteService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.text.TextUtils;
import androidx.appcompat.app.AppCompatActivity;
public class MainUI extends AppCompatActivity {
private SharedPreferences prefs;
private int recommendedOffset = 0;
private final int recommendedPageSize = 10;
private int recommendedFilterMode = 0; // 0:全部 1:同专业 2:同年级
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 全局初始化数据库连接(可选,但推荐)
try {
com.example.myapplication.repository.DatabaseManager.init(getApplicationContext());
} catch (Exception e) {
android.util.Log.e("MainUI", "Database init failed", e);
}
prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
boolean isLoggedIn = AuthService.isLoggedIn(this);
if (!isLoggedIn) {
Utils.toast(this, "请先登录");
startActivity(new Intent(this, LoginUI.class));
finish();
return;
}
String currentUser = AuthService.getCurrentUser(this);
TextView welcomeTv = findViewById(R.id.welcomeText);
String majorName = null;
String gradeText = null;
com.example.myapplication.repository.UserRepository ur0 = new com.example.myapplication.repository.UserRepository(this);
com.example.myapplication.domain.User u0 = ur0.getUserByAccount(currentUser);
gradeText = ur0.getGradeText(currentUser);
if (u0 != null && u0.getMajorId() != null) {
com.example.myapplication.repository.MajorRepository mr0 = new com.example.myapplication.repository.MajorRepository(this);
com.example.myapplication.domain.Major m0 = mr0.getById(u0.getMajorId());
if (m0 != null) majorName = m0.getName();
}
String extra = "";
if (majorName != null) extra += "(专业:" + majorName + ")";
if (gradeText != null && !gradeText.isEmpty()) extra += (extra.isEmpty() ? "" : " ") + "年级:" + gradeText + (extra.contains("") ? "" : ")");
welcomeTv.setText("欢迎," + currentUser + (extra.isEmpty() ? "" : " " + extra));
Button logoutBtn = findViewById(R.id.logoutBtn);
Button searchBtn = findViewById(R.id.searchBtn);
Button uploadBtn = findViewById(R.id.uploadBtn);
Button userCenterBtn = findViewById(R.id.userCenterBtn);
searchBtn.setOnClickListener(v -> startActivity(new Intent(this, SearchUI.class)));
uploadBtn.setOnClickListener(v -> startActivity(new Intent(this, UploadUI.class)));
userCenterBtn.setOnClickListener(v -> startActivity(new Intent(this, UserCenterUI.class)));
logoutBtn.setOnClickListener(v -> {
AuthService.logout(this);
Utils.toast(this, "已退出登录");
startActivity(new Intent(this, LoginUI.class));
finish();
});
// 动态渲染
renderRecommendedNotes(true);
renderLatestNotes();
}
@Override
protected void onResume() {
super.onResume();
renderRecommendedNotes(false);
renderLatestNotes();
}
private void renderLatestNotes() {
android.widget.LinearLayout container = findViewById(R.id.latestNotesContainer);
if (container == null) return;
container.removeAllViews();
com.example.myapplication.repository.NoteRepository repo =
new com.example.myapplication.repository.NoteRepository(this);
java.util.List<com.example.myapplication.domain.Note> notes = repo.getRecentNotes(20);
if (notes == null || notes.isEmpty()) {
android.widget.TextView empty = new android.widget.TextView(this);
empty.setText("暂无笔记,点击“上传笔记”添加吧~");
empty.setTextColor(android.graphics.Color.parseColor("#666666"));
container.addView(empty);
return;
}
for (com.example.myapplication.domain.Note n : notes) {
android.widget.LinearLayout item = new android.widget.LinearLayout(this);
item.setOrientation(android.widget.LinearLayout.VERTICAL);
item.setBackgroundResource(R.drawable.card_background);
int pad = (int) (20 * getResources().getDisplayMetrics().density);
item.setPadding(pad, pad, pad, pad);
android.widget.LinearLayout.LayoutParams lp =
new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
lp.topMargin = (int) (12 * getResources().getDisplayMetrics().density);
item.setLayoutParams(lp);
item.setElevation(4f);
// 标题
android.widget.TextView title = new android.widget.TextView(this);
title.setText(n.getTitle() != null ? n.getTitle() : "(无标题)");
title.setTextColor(getResources().getColor(R.color.text_primary));
title.setTextSize(18);
title.setTypeface(android.graphics.Typeface.DEFAULT_BOLD);
title.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
// 元信息(上传者、时间、下载数/点赞数占位)
android.widget.TextView meta = new android.widget.TextView(this);
String uploader = n.getUploaderId() != null ? n.getUploaderId() : "未知";
String created = n.getCreated() != null ? n.getCreated() : "";
int likes = n.getLikeCount();
meta.setText("上传者:" + uploader + (TextUtils.isEmpty(created) ? "" : " | " + created) + " | 👍 " + likes);
meta.setTextColor(getResources().getColor(R.color.text_secondary));
meta.setTextSize(14);
meta.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
// 描述摘要
android.widget.TextView desc = new android.widget.TextView(this);
String d = n.getContent();
if (d == null) d = "";
if (d.length() > 80) d = d.substring(0, 80) + "...";
desc.setText(TextUtils.isEmpty(d) ? "暂无内容" : d);
desc.setTextColor(getResources().getColor(R.color.text_secondary));
desc.setTextSize(15);
desc.setLineSpacing(0, 1.4f);
desc.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
item.addView(title);
item.addView(meta);
item.addView(desc);
item.setOnClickListener(v -> openNote((int) n.getId()));
container.addView(item);
}
}
private void openNote(int id) {
NoteService.setCurrentNoteId(this, id);
Intent intent = new Intent(this, NoteDetailUI.class);
intent.putExtra("noteId", id);
startActivity(intent);
}
private void renderRecommendedNotes(boolean reset) {
android.widget.LinearLayout container = findViewById(R.id.recommendedNotesContainer);
android.widget.Button refreshBtn = findViewById(R.id.recommendedRefreshBtn);
if (container == null) return;
if (refreshBtn != null) refreshBtn.setOnClickListener(v -> renderRecommendedNotes(true));
if (reset) {
recommendedOffset = 0;
container.removeAllViews();
}
String currentUserId = AuthService.getCurrentUser(this);
com.example.myapplication.repository.UserRepository ur = new com.example.myapplication.repository.UserRepository(this);
com.example.myapplication.domain.User u = ur.getUserByAccount(currentUserId);
Long majorId2 = u != null ? u.getMajorId() : null;
String gradeText2 = ur.getGradeText(currentUserId);
String majorName2 = null;
if (majorId2 != null) {
com.example.myapplication.repository.MajorRepository mr2 = new com.example.myapplication.repository.MajorRepository(this);
com.example.myapplication.domain.Major m2 = mr2.getById(majorId2);
if (m2 != null) majorName2 = m2.getName();
}
if (reset) {
com.example.myapplication.repository.NoteRepository.clearRecommendedCacheFor(majorId2, gradeText2);
}
com.example.myapplication.repository.NoteRepository repo = new com.example.myapplication.repository.NoteRepository(this);
java.util.List<com.example.myapplication.domain.Note> notes = repo.getRecommendedNotes(majorId2, gradeText2, recommendedPageSize, recommendedOffset);
if (notes == null || notes.isEmpty()) {
if (recommendedOffset == 0) {
android.widget.TextView empty = new android.widget.TextView(this);
empty.setText("暂无推荐笔记,完善个人信息或上传吧~");
empty.setTextColor(android.graphics.Color.parseColor("#666666"));
container.addView(empty);
}
return;
}
for (com.example.myapplication.domain.Note n : notes) {
String pubMajorName = null;
String pubGradeText = null;
if (n.getUploaderId() != null) {
com.example.myapplication.repository.UserRepository urp = new com.example.myapplication.repository.UserRepository(this);
com.example.myapplication.domain.User pu = urp.getUserByAccount(n.getUploaderId());
if (pu != null && pu.getMajorId() != null) {
com.example.myapplication.repository.MajorRepository mrp = new com.example.myapplication.repository.MajorRepository(this);
com.example.myapplication.domain.Major mp = mrp.getById(pu.getMajorId());
if (mp != null) pubMajorName = mp.getName();
}
pubGradeText = urp.getGradeText(n.getUploaderId());
}
String relTag = buildRelevanceTagText(majorName2, gradeText2, pubMajorName, pubGradeText);
if (relTag == null) relTag = "";
// 客户端过滤
if (recommendedFilterMode == 1 && (relTag == null || !relTag.contains("同专业"))) continue;
if (recommendedFilterMode == 2 && (relTag == null || !relTag.contains("同年级"))) continue;
android.widget.LinearLayout item = new android.widget.LinearLayout(this);
item.setOrientation(android.widget.LinearLayout.VERTICAL);
item.setBackgroundResource(R.drawable.card_background);
int pad = (int) (20 * getResources().getDisplayMetrics().density);
item.setPadding(pad, pad, pad, pad);
android.widget.LinearLayout.LayoutParams lp =
new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
lp.topMargin = (int) (12 * getResources().getDisplayMetrics().density);
item.setLayoutParams(lp);
item.setElevation(4f);
// 标题
android.widget.TextView title = new android.widget.TextView(this);
title.setText(n.getTitle() != null ? n.getTitle() : "(无标题)");
title.setTextColor(getResources().getColor(R.color.text_primary));
title.setTextSize(18);
title.setTypeface(android.graphics.Typeface.DEFAULT_BOLD);
title.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
// 元信息(相关性标签)
android.widget.TextView meta = new android.widget.TextView(this);
String created = n.getCreated() != null ? n.getCreated() : "";
int likes2 = n.getLikeCount();
meta.setText((TextUtils.isEmpty(relTag) ? "" : (relTag + " | ")) + (TextUtils.isEmpty(created) ? "" : (created)) + " | 👍 " + likes2);
meta.setTextColor(getResources().getColor(R.color.text_secondary));
meta.setTextSize(14);
meta.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
// 描述摘要
android.widget.TextView desc = new android.widget.TextView(this);
String d = n.getContent();
if (d == null) d = "";
if (d.length() > 80) d = d.substring(0, 80) + "...";
desc.setText(TextUtils.isEmpty(d) ? "暂无内容" : d);
desc.setTextColor(getResources().getColor(R.color.text_secondary));
desc.setTextSize(15);
desc.setLineSpacing(0, 1.4f);
desc.setLayoutParams(new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT));
item.addView(title);
item.addView(meta);
item.addView(desc);
item.setOnClickListener(v -> openNote((int) n.getId()));
container.addView(item);
}
recommendedOffset += notes.size();
}
private String buildRelevanceTagText(String userMajor, String userGradeText, String pubMajor, String pubGradeText) {
java.util.List<String> tags = new java.util.ArrayList<>();
if (userMajor != null && pubMajor != null && userMajor.equals(pubMajor)) tags.add("同专业");
if (userGradeText != null && pubGradeText != null && userGradeText.equals(pubGradeText)) tags.add("同年级");
if (tags == null || tags.isEmpty()) return "";
StringBuilder sb = new StringBuilder();
for (String t : tags) {
if (sb.length() > 0) sb.append("、");
sb.append(t);
}
return sb.toString();
}
}

@ -0,0 +1,213 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import com.example.myapplication.Utils;
import com.example.myapplication.service.NoteService;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.text.TextUtils;
import androidx.appcompat.app.AppCompatActivity;
public class NoteDetailUI extends androidx.appcompat.app.AppCompatActivity {
private int likeCount = 18; // 示例初始点赞数
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_detail);
int noteId = getIntent().getIntExtra("noteId", 1);
TextView titleTv = findViewById(R.id.noteTitle);
TextView metaTv = findViewById(R.id.noteMeta);
TextView bodyTv = findViewById(R.id.noteBody);
android.widget.EditText editTitleEt = findViewById(R.id.editNoteTitle);
android.widget.EditText editBodyEt = findViewById(R.id.editNoteBody);
android.widget.LinearLayout editActions = findViewById(R.id.editActions);
android.widget.Button startEditBtn = findViewById(R.id.startEditBtn);
android.widget.Button saveEditBtn = findViewById(R.id.saveEditBtn);
android.widget.Button deleteNoteBtn = findViewById(R.id.deleteNoteBtn);
TextView likeTv = findViewById(R.id.likeCount);
Button backBtn = findViewById(R.id.backBtn);
Button likeBtn = findViewById(R.id.likeBtn);
Button downloadBtn = findViewById(R.id.downloadBtn);
android.widget.LinearLayout commentList = findViewById(R.id.commentListContainer);
android.widget.EditText commentInput = findViewById(R.id.commentInput);
android.widget.Button commentSubmit = findViewById(R.id.commentSubmitBtn);
// 读取数据库中的真实笔记
com.example.myapplication.repository.NoteRepository repo =
new com.example.myapplication.repository.NoteRepository(this);
com.example.myapplication.domain.Note note = repo.getNoteById(noteId);
if (note == null) {
com.example.myapplication.Utils.toast(this, "未找到该笔记");
finish();
return;
}
titleTv.setText(note.getTitle() != null ? note.getTitle() : "(无标题)");
String uploader = note.getUploaderId() != null ? note.getUploaderId() : "未知";
String created = note.getCreated() != null ? note.getCreated() : "";
String fileSummary = summarizeFile(note.getFilePath());
metaTv.setText("👤 " + uploader +
(TextUtils.isEmpty(created) ? "" : " 📅 " + created) +
(TextUtils.isEmpty(fileSummary) ? "" : " 💾 " + fileSummary));
// 修复:正文统一使用 content不再重复声明 bodyTv也不访问 description/ocrText
String content = note.getContent();
bodyTv.setText(content != null && !content.trim().isEmpty() ? content : "暂无内容");
String currentUser = com.example.myapplication.service.AuthService.getCurrentUser(this);
boolean isOwner = note.getUploaderId() != null && note.getUploaderId().equals(currentUser);
editActions.setVisibility(android.view.View.GONE);
// 数据库读取真实点赞数与用户点赞状态
int dbLikeCount = note.getLikeCount();
boolean[] liked = new boolean[] {
com.example.myapplication.service.LikeService.hasLiked(this, currentUser, noteId)
};
likeTv.setText("👍 " + dbLikeCount);
likeBtn.setText(liked[0] ? "取消点赞" : "点赞");
backBtn.setOnClickListener(v -> finish());
likeBtn.setOnClickListener(v -> {
if (!com.example.myapplication.service.AuthService.isLoggedIn(this)) {
com.example.myapplication.Utils.toast(this, "请先登录");
return;
}
boolean ok;
if (liked[0]) {
ok = com.example.myapplication.service.LikeService.removeLike(this, currentUser, noteId);
if (ok) liked[0] = false;
} else {
ok = com.example.myapplication.service.LikeService.addLike(this, currentUser, noteId);
if (ok) liked[0] = true;
}
com.example.myapplication.domain.Note refreshed = repo.getNoteById(noteId);
int newCount = refreshed != null ? refreshed.getLikeCount() : 0;
likeTv.setText("👍 " + newCount);
likeBtn.setText(liked[0] ? "取消点赞" : "点赞");
});
downloadBtn.setOnClickListener(v -> {
String title = note.getTitle() != null ? note.getTitle() : "未命名笔记";
String body = note.getContent() != null ? note.getContent() : "";
StringBuilder sb = new StringBuilder();
sb.append(title).append("\n");
if (created != null && !created.isEmpty()) sb.append("时间: ").append(created).append("\n");
sb.append("作者: ").append(uploader).append("\n\n");
sb.append(body);
String filename = sanitizeFileName(title) + ".txt";
android.net.Uri saved = com.example.myapplication.service.DownloadService.saveTextToDownloadsReturnUri(this, filename, "text/plain", sb.toString());
if (saved != null) {
com.example.myapplication.Utils.toast(this, "已保存笔记内容到下载");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
try {
android.content.Intent view = new android.content.Intent(android.content.Intent.ACTION_VIEW);
view.setDataAndType(saved, "text/plain");
view.addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(view);
} catch (Exception ignored) {}
}
android.content.ContentValues cv = new android.content.ContentValues();
cv.put("download_count", note.getDownloadCount() + 1);
new com.example.myapplication.repository.NoteRepository(this).updateNote(noteId, cv);
} else {
com.example.myapplication.Utils.toast(this, "保存失败,请检查权限或存储空间");
}
});
java.util.List<com.example.myapplication.domain.Comment> cs = com.example.myapplication.service.CommentService.listComments(this, noteId);
commentList.removeAllViews();
if (cs != null && !cs.isEmpty()) {
for (com.example.myapplication.domain.Comment cm : cs) {
android.widget.LinearLayout item = new android.widget.LinearLayout(this);
item.setOrientation(android.widget.LinearLayout.VERTICAL);
android.widget.TextView meta = new android.widget.TextView(this);
meta.setText((cm.getUploaderId() == null ? "匿名" : cm.getUploaderId()) + " " + (cm.getCreatedAt() == null ? "" : cm.getCreatedAt()));
meta.setTextColor(android.graphics.Color.parseColor("#666666"));
android.widget.TextView body = new android.widget.TextView(this);
body.setText(cm.getContent() == null ? "" : cm.getContent());
body.setTextColor(android.graphics.Color.parseColor("#333333"));
item.addView(meta);
item.addView(body);
item.setPadding(0, 12, 0, 12);
commentList.addView(item);
}
} else {
android.widget.TextView empty = new android.widget.TextView(this);
empty.setText("暂无评论,来说两句吧~");
empty.setTextColor(android.graphics.Color.parseColor("#999999"));
commentList.addView(empty);
}
if (commentSubmit != null) {
commentSubmit.setOnClickListener(v -> {
if (!com.example.myapplication.service.AuthService.isLoggedIn(this)) {
com.example.myapplication.Utils.toast(this, "请先登录");
return;
}
String userId2 = com.example.myapplication.service.AuthService.getCurrentUser(this);
String txt = commentInput != null ? commentInput.getText().toString() : "";
boolean ok = com.example.myapplication.service.CommentService.addComment(this, userId2, noteId, txt);
if (!ok) {
com.example.myapplication.Utils.toast(this, "评论失败,检查内容长度");
return;
}
com.example.myapplication.Utils.toast(this, "发布成功");
if (commentInput != null) commentInput.setText("");
java.util.List<com.example.myapplication.domain.Comment> cs2 = com.example.myapplication.service.CommentService.listComments(this, noteId);
commentList.removeAllViews();
if (cs2 != null && !cs2.isEmpty()) {
for (com.example.myapplication.domain.Comment cm : cs2) {
android.widget.LinearLayout item = new android.widget.LinearLayout(this);
item.setOrientation(android.widget.LinearLayout.VERTICAL);
android.widget.TextView meta = new android.widget.TextView(this);
meta.setText((cm.getUploaderId() == null ? "匿名" : cm.getUploaderId()) + " " + (cm.getCreatedAt() == null ? "" : cm.getCreatedAt()));
meta.setTextColor(android.graphics.Color.parseColor("#666666"));
android.widget.TextView body = new android.widget.TextView(this);
body.setText(cm.getContent() == null ? "" : cm.getContent());
body.setTextColor(android.graphics.Color.parseColor("#333333"));
item.addView(meta);
item.addView(body);
item.setPadding(0, 12, 0, 12);
commentList.addView(item);
}
}
});
}
}
// 简单的文件信息摘要:扩展名 + 大小(若可读)
private String summarizeFile(String path) {
if (path == null || path.isEmpty()) return "";
String ext = "";
int dot = path.lastIndexOf('.');
if (dot >= 0 && dot < path.length() - 1) {
ext = path.substring(dot + 1).toUpperCase();
}
java.io.File f = new java.io.File(path);
String size = "";
if (f.exists()) {
long bytes = f.length();
if (bytes > 1024 * 1024) {
size = String.format(java.util.Locale.getDefault(), "%.1fMB", bytes / 1024f / 1024f);
} else if (bytes > 1024) {
size = String.format(java.util.Locale.getDefault(), "%.0fKB", bytes / 1024f);
} else {
size = bytes + "B";
}
}
if (!ext.isEmpty() && !size.isEmpty()) return ext + ", " + size;
if (!ext.isEmpty()) return ext;
return size;
}
private String sanitizeFileName(String name) {
String n = name == null ? "note" : name.trim();
n = n.replaceAll("[\\/:*?\"<>|]", "_");
if (n.isEmpty()) n = "note";
return n;
}
}

@ -0,0 +1,78 @@
package com.example.myapplication.ui;
import androidx.appcompat.app.AppCompatActivity;
public class NoteEditUI extends AppCompatActivity {
private long noteId;
@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(com.example.myapplication.R.layout.activity_note_edit);
noteId = getIntent().getLongExtra("noteId", -1);
if (noteId <= 0) {
int nid = com.example.myapplication.service.NoteService.getCurrentNoteId(this);
noteId = nid;
}
android.widget.EditText titleEt = findViewById(com.example.myapplication.R.id.editNoteTitle);
android.widget.EditText contentEt = findViewById(com.example.myapplication.R.id.editNoteContent);
android.widget.Button saveBtn = findViewById(com.example.myapplication.R.id.saveNoteBtn);
android.widget.Button cancelBtn = findViewById(com.example.myapplication.R.id.cancelEditBtn);
android.widget.Button deleteBtn = findViewById(com.example.myapplication.R.id.deleteNoteBtn);
if (noteId > 0) {
com.example.myapplication.repository.NoteRepository repo = new com.example.myapplication.repository.NoteRepository(this);
com.example.myapplication.domain.Note note = repo.getNoteById(noteId);
if (note != null) {
if (titleEt != null) titleEt.setText(note.getTitle() != null ? note.getTitle() : "");
if (contentEt != null) contentEt.setText(note.getContent() != null ? note.getContent() : "");
}
}
saveBtn.setOnClickListener(v -> {
String title = titleEt.getText().toString().trim();
String content = contentEt.getText().toString().trim();
if (title.isEmpty()) {
com.example.myapplication.Utils.toast(this, "标题不能为空");
return;
}
// 仅允许上传者编辑
com.example.myapplication.repository.NoteRepository repo0 = new com.example.myapplication.repository.NoteRepository(this);
com.example.myapplication.domain.Note n0 = repo0.getNoteById(noteId);
String currentUser = com.example.myapplication.service.AuthService.getCurrentUser(this);
if (n0 != null && n0.getUploaderId() != null && !n0.getUploaderId().equals(currentUser)) {
com.example.myapplication.Utils.toast(this, "仅作者可编辑");
return;
}
android.content.ContentValues cv = new android.content.ContentValues();
cv.put("title", title);
cv.put("content", content);
boolean ok = new com.example.myapplication.repository.NoteRepository(this).updateNote(noteId, cv);
com.example.myapplication.Utils.toast(this, ok ? "保存成功" : "保存失败");
if (ok) finish();
});
cancelBtn.setOnClickListener(v -> finish());
deleteBtn.setOnClickListener(v -> {
android.app.AlertDialog.Builder b = new android.app.AlertDialog.Builder(this);
b.setTitle("删除笔记");
b.setMessage("确定删除这条笔记吗?");
b.setPositiveButton("删除", (d, which) -> {
com.example.myapplication.repository.NoteRepository repo0 = new com.example.myapplication.repository.NoteRepository(this);
com.example.myapplication.domain.Note n0 = repo0.getNoteById(noteId);
String currentUser = com.example.myapplication.service.AuthService.getCurrentUser(this);
if (n0 != null && n0.getUploaderId() != null && !n0.getUploaderId().equals(currentUser)) {
com.example.myapplication.Utils.toast(this, "仅作者可删除");
return;
}
boolean ok = new com.example.myapplication.repository.NoteRepository(this).deleteNote(noteId);
com.example.myapplication.Utils.toast(this, ok ? "已删除" : "删除失败");
if (ok) finish();
});
b.setNegativeButton("取消", (d, which) -> {});
b.show();
});
}
}

@ -0,0 +1,477 @@
package com.example.myapplication;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.textfield.TextInputEditText;
import com.example.myapplication.domain.Chapter;
// import com.example.myapplication.domain.ClassificationResult; // 使用ClassificationService.Result替代
import com.example.myapplication.domain.Course;
import com.example.myapplication.domain.Note;
import com.example.myapplication.repository.CourseRepository;
import com.example.myapplication.repository.NoteRepository;
import com.example.myapplication.service.ClassificationService;
import com.example.myapplication.service.OCRService;
import com.example.myapplication.service.ClassificationService.Result;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class NoteUploadActivity extends AppCompatActivity {
private static final String TAG = "NoteUploadActivity";
private static final int PICK_FILE_REQUEST = 1;
// UI组件
private Button btnSelectFile, btnUpload, btnCancel, btnAcceptClassification, btnRejectClassification;
private TextInputEditText etNoteTitle, etNoteDescription;
private TextView tvSelectedFile, tvClassificationResult, tvClassificationConfidence;
private MaterialCardView cardClassification, cardManualSelection;
private Spinner spinnerCourse, spinnerChapter;
private ProgressBar progressBar;
// 数据存储
private CourseRepository courseRepository;
private NoteRepository noteRepository;
private ClassificationService classificationService;
// 状态变量
private Uri selectedFileUri;
private String savedFilePath;
private ClassificationService.Result currentClassification;
private List<Course> availableCourses;
private List<Chapter> availableChapters;
// Activity结果启动器
private ActivityResultLauncher<Intent> filePickerLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_upload);
initViews();
initRepositories();
setupListeners();
setupFilePicker();
loadAvailableCourses();
}
private void initViews() {
btnSelectFile = findViewById(R.id.btn_select_file);
btnUpload = findViewById(R.id.btn_upload);
btnCancel = findViewById(R.id.btn_cancel);
btnAcceptClassification = findViewById(R.id.btn_accept_classification);
btnRejectClassification = findViewById(R.id.btn_reject_classification);
etNoteTitle = findViewById(R.id.et_note_title);
etNoteDescription = findViewById(R.id.et_note_description);
tvSelectedFile = findViewById(R.id.tv_selected_file);
tvClassificationResult = findViewById(R.id.tv_classification_result);
tvClassificationConfidence = findViewById(R.id.tv_classification_confidence);
cardClassification = findViewById(R.id.card_classification);
cardManualSelection = findViewById(R.id.card_manual_selection);
spinnerCourse = findViewById(R.id.spinner_course);
spinnerChapter = findViewById(R.id.spinner_chapter);
progressBar = findViewById(R.id.progress_bar);
}
private void initRepositories() {
courseRepository = new CourseRepository(this);
noteRepository = new NoteRepository(this);
classificationService = new ClassificationService(this);
// OCRService是静态工具类不需要实例化
}
private void setupListeners() {
btnSelectFile.setOnClickListener(v -> openFilePicker());
btnUpload.setOnClickListener(v -> uploadNote());
btnCancel.setOnClickListener(v -> finish());
btnAcceptClassification.setOnClickListener(v -> acceptClassification());
btnRejectClassification.setOnClickListener(v -> showManualSelection());
spinnerCourse.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position >= 0 && position < availableCourses.size()) {
Course selectedCourse = availableCourses.get(position);
loadChaptersForCourse(String.valueOf(selectedCourse.getId()));
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing
}
});
}
private void setupFilePicker() {
filePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
Uri uri = result.getData().getData();
if (uri != null) {
handleFileSelection(uri);
}
}
}
);
}
private void openFilePicker() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
String[] mimeTypes = {"image/*", "application/pdf"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
intent.addCategory(Intent.CATEGORY_OPENABLE);
Intent chooser = Intent.createChooser(intent, "选择文件");
filePickerLauncher.launch(chooser);
}
private void handleFileSelection(Uri uri) {
selectedFileUri = uri;
String fileName = getFileName(uri);
tvSelectedFile.setText("已选择: " + fileName);
// 保存文件到应用目录
savedFilePath = saveFileToAppDirectory(uri);
if (savedFilePath == null) {
Toast.makeText(this, "文件保存失败", Toast.LENGTH_SHORT).show();
}
}
private String getFileName(Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (nameIndex >= 0) {
result = cursor.getString(nameIndex);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
private String saveFileToAppDirectory(Uri uri) {
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
if (inputStream == null) return null;
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String fileName = "note_" + timeStamp + getFileExtension(uri);
File outputFile = new File(getFilesDir(), fileName);
OutputStream outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[4096];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
return outputFile.getAbsolutePath();
} catch (IOException e) {
Log.e(TAG, "保存文件失败", e);
return null;
}
}
private String getFileExtension(Uri uri) {
String fileName = getFileName(uri);
int lastDotIndex = fileName.lastIndexOf('.');
return lastDotIndex >= 0 ? fileName.substring(lastDotIndex) : "";
}
private void loadAvailableCourses() {
String account = com.example.myapplication.service.AuthService.getCurrentUser(this);
com.example.myapplication.repository.UserRepository ur = new com.example.myapplication.repository.UserRepository(this);
com.example.myapplication.domain.User u = ur.getUserByAccount(account);
Long majorId = u != null ? u.getMajorId() : null;
String gradeText = ur.getGradeText(account);
availableCourses = courseRepository.getCoursesByMajorAndGrade(majorId, gradeText);
if (availableCourses == null || availableCourses.isEmpty()) {
availableCourses = courseRepository.getAllCourses();
}
if (availableCourses == null || availableCourses.isEmpty()) {
Toast.makeText(this, "暂无课程数据,请稍后重试", Toast.LENGTH_LONG).show();
} else {
setupCourseSpinner();
}
}
private void setupCourseSpinner() {
List<String> courseNames = new ArrayList<>();
for (Course course : availableCourses) {
courseNames.add(course.getName());
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, courseNames);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCourse.setAdapter(adapter);
}
private void loadChaptersForCourse(String courseId) {
long sid = 0L;
try { sid = Long.parseLong(courseId); } catch (Exception ignored) {}
availableChapters = courseRepository.getChaptersBySubjectId(sid);
List<String> chapterTitles = new ArrayList<>();
for (Chapter ch : availableChapters) chapterTitles.add(ch.getName());
if (chapterTitles.isEmpty()) chapterTitles.add("暂无章节");
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, chapterTitles);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerChapter.setAdapter(adapter);
}
private void uploadNote() {
String title = etNoteTitle.getText().toString().trim();
String description = etNoteDescription.getText().toString().trim();
if (title.isEmpty()) {
Toast.makeText(this, "请输入笔记标题", Toast.LENGTH_SHORT).show();
return;
}
if (selectedFileUri == null) {
Toast.makeText(this, "请选择文件", Toast.LENGTH_SHORT).show();
return;
}
showProgress(true);
// 在后台线程中处理
new Thread(() -> {
try {
// 1. 执行OCR提取文本
String ocrText = performOCR();
// 2. 创建笔记对象
Note note = createNote(title, description, ocrText);
// 3. 进行自动分类
if (note.getOcrText() != null && !note.getOcrText().isEmpty()) {
performClassification(note);
} else {
runOnUiThread(() -> {
showProgress(false);
showManualSelection();
});
return;
}
runOnUiThread(() -> {
showProgress(false);
showClassificationResult();
});
} catch (Exception e) {
Log.e(TAG, "处理笔记失败", e);
runOnUiThread(() -> {
showProgress(false);
Toast.makeText(this, "处理失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
}).start();
}
private String performOCR() {
if (savedFilePath == null) return "";
try {
String text = com.example.myapplication.service.OCRService.extractTextFromFile(this, savedFilePath);
if (text == null || text.trim().isEmpty()) {
Toast.makeText(this, "识别暂不可用,您可手动填写内容", Toast.LENGTH_SHORT).show();
return "";
}
return text;
} catch (Exception e) {
Log.e(TAG, "OCR处理失败", e);
Toast.makeText(this, "识别失败,已保留上传与编辑", Toast.LENGTH_SHORT).show();
return "";
}
}
private Note createNote(String title, String description, String ocrText) {
Note note = new Note();
note.setTitle(title);
note.setDescription(description);
note.setOcrText(ocrText);
note.setFilePath(savedFilePath);
long currentTime = System.currentTimeMillis();
note.setUploadTime(currentTime);
note.setUpdateTime(currentTime);
return note;
}
private void performClassification(Note note) {
currentClassification = classificationService.classifyWithSegmentation(note.getOcrText());
}
private void showClassificationResult() {
if (currentClassification == null) {
Toast.makeText(this, "分类失败", Toast.LENGTH_SHORT).show();
return;
}
if (currentClassification.category.isEmpty()) {
tvClassificationResult.setText("无法自动分类,请手动选择");
tvClassificationConfidence.setText("");
showManualSelection();
} else {
String result = String.format("分类结果: %s - %s", currentClassification.category, currentClassification.chapter);
tvClassificationResult.setText(result);
String confidence = String.format("置信度: %.2f%%", currentClassification.confidence * 100);
tvClassificationConfidence.setText(confidence);
if (currentClassification.confidence >= 0.6) {
preselectByClassification();
cardClassification.setVisibility(View.VISIBLE);
cardManualSelection.setVisibility(View.GONE);
} else {
cardClassification.setVisibility(View.VISIBLE);
cardManualSelection.setVisibility(View.VISIBLE);
Toast.makeText(this, "置信度较低,请确认或手动选择", Toast.LENGTH_SHORT).show();
}
}
}
private void preselectByClassification() {
if (availableCourses != null && !availableCourses.isEmpty()) {
int courseIndex = -1;
for (int i = 0; i < availableCourses.size(); i++) {
if (availableCourses.get(i).getName().equals(currentClassification.category)) {
courseIndex = i;
break;
}
}
if (courseIndex >= 0) {
spinnerCourse.setSelection(courseIndex);
Course selectedCourse = availableCourses.get(courseIndex);
loadChaptersForCourse(String.valueOf(selectedCourse.getId()));
if (availableChapters != null && !availableChapters.isEmpty()) {
int chapterIndex = -1;
for (int j = 0; j < availableChapters.size(); j++) {
if (availableChapters.get(j).getName().equals(currentClassification.chapter)) {
chapterIndex = j;
break;
}
}
if (chapterIndex >= 0) spinnerChapter.setSelection(chapterIndex);
}
}
}
}
private void acceptClassification() {
if (currentClassification == null || currentClassification.category.isEmpty()) {
Toast.makeText(this, "没有有效的分类结果", Toast.LENGTH_SHORT).show();
return;
}
// 创建最终笔记并保存(科目、章节均使用数据库选择结果)
Note finalNote = createNote(
etNoteTitle.getText().toString().trim(),
etNoteDescription.getText().toString().trim(),
"自动分类: " + currentClassification.category + " - " + currentClassification.chapter
);
int coursePos = spinnerCourse.getSelectedItemPosition();
if (availableCourses == null || availableCourses.isEmpty() || coursePos < 0 || coursePos >= availableCourses.size()) {
Toast.makeText(this, "请选择科目", Toast.LENGTH_SHORT).show();
showManualSelection();
return;
}
Course selectedCourse = availableCourses.get(coursePos);
int chapterPos = spinnerChapter.getSelectedItemPosition();
if (availableChapters == null || availableChapters.isEmpty() || chapterPos < 0 || chapterPos >= availableChapters.size()) {
Toast.makeText(this, "请选择章节", Toast.LENGTH_SHORT).show();
showManualSelection();
return;
}
Chapter selectedChapter = availableChapters.get(chapterPos);
finalNote.setCourseId(selectedCourse.getId());
finalNote.setChapterId(selectedChapter.getId());
finalNote.setUploaderId(com.example.myapplication.service.AuthService.getCurrentUser(this));
long result = noteRepository.insertNote(finalNote);
if (result != -1) {
Toast.makeText(this, "笔记上传成功!", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(this, "保存笔记失败", Toast.LENGTH_SHORT).show();
}
}
private void showManualSelection() {
cardClassification.setVisibility(View.GONE);
cardManualSelection.setVisibility(View.VISIBLE);
}
private void showProgress(boolean show) {
progressBar.setVisibility(show ? View.VISIBLE : View.GONE);
btnUpload.setEnabled(!show);
btnCancel.setEnabled(!show);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (courseRepository != null) {
courseRepository.close();
}
if (noteRepository != null) {
noteRepository.close();
}
}
}

@ -0,0 +1,78 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import com.example.myapplication.Utils;
import com.example.myapplication.service.AuthService;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
public class RegisterUI extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
EditText usernameEt = findViewById(R.id.usernameInput);
EditText passwordEt = findViewById(R.id.passwordInput);
EditText confirmEt = findViewById(R.id.confirmInput);
android.widget.Spinner gradeSp = findViewById(R.id.gradeSpinner);
android.widget.AutoCompleteTextView majorEt = findViewById(R.id.majorInput);
final long[] selectedMajorId = new long[]{-1};
majorEt.setThreshold(1);
majorEt.addTextChangedListener(new android.text.TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
selectedMajorId[0] = -1;
String q = s == null ? "" : s.toString();
com.example.myapplication.repository.MajorRepository mr = new com.example.myapplication.repository.MajorRepository(RegisterUI.this);
java.util.List<com.example.myapplication.domain.Major> majors = mr.findByNameLike(q, 10);
java.util.List<String> names = new java.util.ArrayList<>();
for (com.example.myapplication.domain.Major m : majors) names.add(m.getName());
android.widget.ArrayAdapter<String> ad = new android.widget.ArrayAdapter<>(RegisterUI.this, android.R.layout.simple_dropdown_item_1line, names);
majorEt.setAdapter(ad);
majorEt.showDropDown();
}
@Override public void afterTextChanged(android.text.Editable s) {}
});
majorEt.setOnItemClickListener((parent, view, position, id) -> {
String name = (String) parent.getItemAtPosition(position);
com.example.myapplication.repository.MajorRepository mr = new com.example.myapplication.repository.MajorRepository(RegisterUI.this);
java.util.List<com.example.myapplication.domain.Major> majors = mr.findByNameLike(name, 1);
if (majors != null && !majors.isEmpty()) selectedMajorId[0] = majors.get(0).getId();
});
Button registerBtn = findViewById(R.id.registerBtn);
java.util.List<String> grades = java.util.Arrays.asList("大一", "大二", "大三", "大四");
android.widget.ArrayAdapter<String> adapter = new android.widget.ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, grades);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
gradeSp.setAdapter(adapter);
registerBtn.setOnClickListener(v -> {
String username = usernameEt.getText().toString().trim();
String password = passwordEt.getText().toString().trim();
String confirm = confirmEt.getText().toString().trim();
String gradeName = null;
try { gradeName = (String) gradeSp.getSelectedItem(); } catch (Exception ignored) {}
Long majorId = selectedMajorId[0] >= 0 ? selectedMajorId[0] : null;
String err = AuthService.validateRegistration(username, password, confirm, gradeName, majorId);
if (err != null) {
Utils.toast(this, err);
return;
}
boolean ok = AuthService.register(this, username, password, gradeName, majorId);
if (!ok) {
Utils.toast(this, "注册失败,请稍后再试");
return;
}
Utils.toast(this, "注册成功!请登录");
startActivity(new Intent(this, LoginUI.class));
finish();
});
}
}

@ -0,0 +1,72 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class SearchUI extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
Button backBtn = findViewById(R.id.backBtn);
Button searchBtn = findViewById(R.id.searchBtn);
EditText searchInput = findViewById(R.id.searchInput);
TextView resultsInfo = findViewById(R.id.resultsInfo);
android.widget.LinearLayout resultsContainer = findViewById(R.id.resultsContainer);
backBtn.setOnClickListener(v -> finish());
searchBtn.setOnClickListener(v -> {
String q = searchInput.getText() == null ? "" : searchInput.getText().toString().trim();
java.util.List<com.example.myapplication.domain.Note> notes = com.example.myapplication.service.SearchService.search(this, q);
resultsContainer.removeAllViews();
if (notes == null || notes.isEmpty()) {
resultsInfo.setText("未找到匹配的笔记");
android.widget.TextView empty = new android.widget.TextView(this);
empty.setText("试试输入章节、科目或专业名称进行搜索");
empty.setTextColor(android.graphics.Color.parseColor("#666666"));
resultsContainer.addView(empty);
} else {
resultsInfo.setText("共 " + notes.size() + " 条结果");
int pad = (int) (12 * getResources().getDisplayMetrics().density);
for (com.example.myapplication.domain.Note n : notes) {
android.widget.LinearLayout item = new android.widget.LinearLayout(this);
item.setOrientation(android.widget.LinearLayout.VERTICAL);
item.setBackgroundColor(android.graphics.Color.WHITE);
item.setPadding(pad, pad, pad, pad);
android.widget.TextView title = new android.widget.TextView(this);
title.setText(n.getTitle() != null ? n.getTitle() : "(无标题)");
title.setTextColor(android.graphics.Color.parseColor("#333333"));
title.setTypeface(android.graphics.Typeface.DEFAULT_BOLD);
android.widget.TextView meta = new android.widget.TextView(this);
String created = n.getCreated() == null ? "" : n.getCreated();
int likes = n.getLikeCount();
meta.setText((created.isEmpty() ? "📅 未知" : "📅 " + created) + " 👍 " + likes);
meta.setTextColor(android.graphics.Color.parseColor("#666666"));
android.widget.TextView desc = new android.widget.TextView(this);
String content = n.getContent();
if (content == null) content = "";
desc.setText(content.length() > 120 ? content.substring(0, 120) + "..." : content);
desc.setTextColor(android.graphics.Color.parseColor("#888888"));
item.addView(title);
item.addView(meta);
item.addView(desc);
item.setOnClickListener(v2 -> {
com.example.myapplication.service.NoteService.setCurrentNoteId(this, (int) n.getId());
android.content.Intent intent = new android.content.Intent(this, com.example.myapplication.ui.NoteDetailUI.class);
intent.putExtra("noteId", (int) n.getId());
startActivity(intent);
});
resultsContainer.addView(item);
}
}
});
}
}

@ -0,0 +1,958 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import com.example.myapplication.Utils;
import com.example.myapplication.service.UploadService;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.net.Uri;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ImageView;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.OpenableColumns;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import java.util.List;
import java.util.ArrayList;
// 添加自动分类功能所需的 import
import android.util.Log;
import android.content.Context;
import android.content.res.AssetManager;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
// Google ML Kit文字识别导入
import com.google.mlkit.vision.common.InputImage;
// HanLP开源代码导入
// 删除以下 UI 中不应存在的导入:
// import com.hankcs.hanlp.HanLP;
// import com.hankcs.hanlp.seg.common.Term;
// import com.hankcs.hanlp.dictionary.stopword.CoreStopWordDictionary;
// 文档处理相关导入
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.text.PDFTextStripper;
import com.example.myapplication.service.AuthService;
import com.example.myapplication.repository.NoteRepository;
import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
public class UploadUI extends AppCompatActivity {
private ProgressBar progressBar;
private TextView progressText;
private Button submitBtn;
private ImageView previewImage;
// 删除private TextView ocrResultText;
private TextView fileInfoText;
private TextView categorySuggestions;
private LinearLayout fileListContainer;
private static final int REQ_OPEN_DOCUMENT = 1002;
private final List<Uri> selectedFiles = new ArrayList<>();
private Uri currentPreviewUri;
private TextView classificationResultText;
private com.example.myapplication.service.ClassificationService classificationService;
// 聚合所有文件的文本内容
private final StringBuilder aggregatedText = new StringBuilder();
private final Set<String> processedFiles = new HashSet<>();
private Long autoChapterId = null;
private Long autoSubjectId = null;
private java.util.List<com.example.myapplication.domain.Course> availableSubjects;
private java.util.List<com.example.myapplication.domain.Chapter> availableChapters;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);
EditText titleEt = findViewById(R.id.noteTitle);
Spinner subjectSp = findViewById(R.id.subjectSpinner);
EditText contentEt = findViewById(R.id.contentInput); // 替换描述为内容
EditText tagsEt = findViewById(R.id.tagsInput);
progressBar = findViewById(R.id.progressBar);
progressText = findViewById(R.id.progressText);
submitBtn = findViewById(R.id.submitBtn);
previewImage = findViewById(R.id.previewImage);
// 删除ocrResultText = findViewById(R.id.ocrResultText);
fileInfoText = findViewById(R.id.fileInfoText);
fileListContainer = findViewById(R.id.fileListContainer);
// 初始化自动分类功能
classificationResultText = findViewById(R.id.classificationResultText);
com.tom_roush.pdfbox.android.PDFBoxResourceLoader.init(getApplicationContext());
try {
classificationService = new com.example.myapplication.service.ClassificationService(this);
} catch (Exception e) {
android.util.Log.e("UploadUI", "ClassificationService init failed", e);
}
// 初始化科目下拉(数据库)
try {
com.example.myapplication.repository.CourseRepository cr = new com.example.myapplication.repository.CourseRepository(this);
String account = com.example.myapplication.service.AuthService.getCurrentUser(this);
com.example.myapplication.repository.UserRepository ur = new com.example.myapplication.repository.UserRepository(this);
com.example.myapplication.domain.User u = ur.getUserByAccount(account);
Long majorId = u != null ? u.getMajorId() : null;
String gradeText = ur.getGradeText(account);
availableSubjects = cr.getCoursesByMajorAndGrade(majorId, gradeText);
if (availableSubjects == null || availableSubjects.isEmpty()) availableSubjects = cr.getCoursesByMajor(majorId);
if (availableSubjects == null || availableSubjects.isEmpty()) availableSubjects = cr.getAllCourses();
java.util.List<String> subjectNames = new java.util.ArrayList<>();
for (com.example.myapplication.domain.Course c : availableSubjects) subjectNames.add(c.getName());
android.widget.ArrayAdapter<String> subjAdapter = new android.widget.ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, subjectNames);
subjAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
subjectSp.setAdapter(subjAdapter);
subjectSp.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override public void onItemSelected(android.widget.AdapterView<?> parent, android.view.View view, int position, long id) {
if (position >= 0 && position < availableSubjects.size()) {
long sid = availableSubjects.get(position).getId();
loadChaptersForSubject(sid);
}
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
} catch (Exception e) {
android.util.Log.e("UploadUI", "init subjects failed", e);
}
findViewById(R.id.selectFileBtn).setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
"image/*",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain"
});
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(intent, REQ_OPEN_DOCUMENT);
});
findViewById(R.id.backBtn).setOnClickListener(v -> finish());
submitBtn.setOnClickListener(v -> {
String title = titleEt.getText().toString().trim();
String content = contentEt.getText().toString().trim();
if (title.isEmpty()) {
com.example.myapplication.Utils.toast(this, "请输入笔记标题");
return;
}
simulateUpload(title, content);
});
}
// 将 ProgressListener 的实现改回本地 Handler 进度循环,避免生成 UploadUI$1.class
private void simulateUpload(String title, String content) {
submitBtn.setEnabled(false);
progressBar.setProgress(0);
progressText.setText("上传中... 0%");
progressBar.setVisibility(android.view.View.VISIBLE);
progressText.setVisibility(android.view.View.VISIBLE);
android.os.Handler handler = new android.os.Handler();
for (int i = 1; i <= 10; i++) {
final int progress = i * 10;
handler.postDelayed(() -> {
progressBar.setProgress(progress);
progressText.setText("上传中... " + progress + "%");
if (progress == 100) {
persistNoteAsync(title, content);
}
}, i * 200);
}
}
// 新增:持久化到 SQLite并复制首个选择的文件到内部存储
// 确保这是类级方法,而不是嵌套在 onCreate 里
private void persistNoteAsync(String title, String content) {
new Thread(() -> {
boolean ok = false;
String errMsg = null;
try {
String uploaderId = com.example.myapplication.service.AuthService.getCurrentUser(this);
Long courseId = autoChapterId;
try {
android.widget.Spinner chapterSp = findViewById(R.id.chapterSpinner);
if (chapterSp != null && availableChapters != null && !availableChapters.isEmpty()) {
int pos = chapterSp.getSelectedItemPosition();
if (pos >= 0 && pos < availableChapters.size()) courseId = availableChapters.get(pos).getId();
}
} catch (Exception ignored) {}
String storedPath = null;
if (!selectedFiles.isEmpty()) {
android.net.Uri uri = selectedFiles.get(0);
String name = com.example.myapplication.service.UploadService.getDisplayName(this, uri);
java.io.File dir = new java.io.File(getFilesDir(), "notes");
if (!dir.exists()) dir.mkdirs();
java.io.File out = new java.io.File(
dir,
System.currentTimeMillis() + "_" + (name == null ? "note" : name)
);
copyUriToFile(uri, out);
storedPath = out.getAbsolutePath();
}
com.example.myapplication.repository.NoteRepository repo =
new com.example.myapplication.repository.NoteRepository(this);
ok = repo.saveNote(title, content, storedPath, courseId, uploaderId);
if (ok && uploaderId != null && !uploaderId.isEmpty()) {
new com.example.myapplication.repository.UserRepository(this)
.addPoints(uploaderId, 10);
}
} catch (Exception e) {
errMsg = e.getMessage();
android.util.Log.e("UploadUI", "persistNote failed", e);
}
boolean finalOk = ok;
String finalErr = errMsg;
runOnUiThread(() -> {
submitBtn.setEnabled(true);
if (finalOk) {
progressText.setText("上传成功 100%");
com.example.myapplication.Utils.toast(this, "上传成功!");
finish();
} else {
progressText.setText("上传失败");
com.example.myapplication.Utils.toast(this, "上传失败" + (finalErr != null ? "" + finalErr : ""));
}
});
}).start();
}
// 新增:复制文件(使用完整类名,避免 import 依赖)
private void copyUriToFile(android.net.Uri uri, java.io.File outFile) throws java.io.IOException {
try (java.io.InputStream in = getContentResolver().openInputStream(uri);
java.io.OutputStream out = new java.io.FileOutputStream(outFile)) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.flush();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK || data == null) return;
// 已删除REQ_PICK_IMAGE 分支
if (requestCode == REQ_OPEN_DOCUMENT) {
if (data.getClipData() != null) {
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
Uri uri = data.getClipData().getItemAt(i).getUri();
addFileItem(uri);
}
} else {
Uri uri = data.getData();
if (uri != null) addFileItem(uri);
}
updatePreviewFromFirstImage();
}
}
private void autoFillFields(String fullText, String displayName, String mime) {
EditText titleEt = findViewById(R.id.noteTitle);
Spinner subjectSp = findViewById(R.id.subjectSpinner);
EditText contentEt = findViewById(R.id.contentInput);
EditText tagsEt = findViewById(R.id.tagsInput);
String title = titleEt.getText().toString().trim();
String fromName = (displayName == null) ? "" : displayName.replaceAll("\\.[^.]+$", "");
if (title.isEmpty()) {
if (fullText != null && !fullText.trim().isEmpty()) {
for (String line : fullText.split("\\r?\\n")) {
String t = line.trim();
if (!t.isEmpty()) { title = t; break; }
}
}
if (title.isEmpty() && !fromName.isEmpty()) title = fromName;
if (!title.isEmpty()) titleEt.setText(title);
}
String base = (fullText != null && !fullText.isEmpty()) ? fullText : fromName;
String subjectGuess = guessSubjectFromDatabase(base);
setSpinnerSelectionByText(subjectSp, subjectGuess);
// 仅当内容输入框为空时,才用摘要填充,避免覆盖完整 OCR 文本
boolean isContentEmpty = contentEt.getText().toString().trim().isEmpty();
if (fullText != null && !fullText.trim().isEmpty()) {
String[] lines = fullText.split("\\r?\\n");
StringBuilder sb = new StringBuilder();
int count = 0;
for (String l : lines) {
String line = l.trim();
if (line.isEmpty() || line.startsWith("---") || line.endsWith("---")) continue;
sb.append(line).append(" ");
if (++count >= 5) break;
}
String desc = sb.toString().trim();
if (!desc.isEmpty() && isContentEmpty) {
if (processedFiles.size() > 1) {
desc = "基于" + processedFiles.size() + "个文件的内容:" + desc;
}
contentEt.setText(desc);
}
} else if (!fromName.isEmpty() && isContentEmpty) {
contentEt.setText("文档:" + fromName + (mime != null ? "" + mime + "" : ""));
}
StringBuilder tags = new StringBuilder();
// 首先添加学科名称作为标签
if (subjectGuess != null) {
tags.append(subjectGuess);
}
// 然后添加具体的技术标签
for (String k : new String[]{"DH参数","矩阵变换","坐标系","机械臂","运动学","动力学","雅可比矩阵","线性代数","函数","极限","力学","算法","数据结构","人工智能","机器学习","深度学习","神经网络","计算机视觉","自然语言处理","数据库","操作系统","计算机网络","软件工程","编程语言","Web开发","移动开发"}) {
if ((base + " " + title).toLowerCase().contains(k.toLowerCase())) {
if (tags.length() > 0) tags.append(",");
tags.append(k);
}
}
if (tags.length() > 0) {
String currentTags = tagsEt.getText().toString().trim();
if (currentTags.isEmpty()) {
tagsEt.setText(tags.toString());
} else {
// 合并标签,避免重复
Set<String> tagSet = new HashSet<>(Arrays.asList(currentTags.split(",")));
for (String tag : tags.toString().split(",")) {
tagSet.add(tag.trim());
}
StringBuilder sb = new StringBuilder();
for (String s : tagSet) {
if (s == null) continue;
String trimmed = s.trim();
if (trimmed.isEmpty()) continue;
if (sb.length() > 0) sb.append(",");
sb.append(trimmed);
}
tagsEt.setText(sb.toString());
}
}
// (已删除)智能分类建议的拼接与设置,避免对不存在视图的 setText 调用
// StringBuilder sug = new StringBuilder();
// sug.append("智能分类建议:\n");
// if (subjectGuess != null) sug.append("科目:").append(subjectGuess).append("\n");
// if (tags.length() > 0) sug.append("标签:").append(tags);
// categorySuggestions.setText(sug.toString());
// 触发自动分类
onOcrTextReady(fullText);
}
private void setSpinnerSelectionByText(Spinner sp, String text) {
if (text == null) return;
android.widget.ArrayAdapter adapter = (android.widget.ArrayAdapter) sp.getAdapter();
if (adapter == null) return;
for (int i = 0; i < adapter.getCount(); i++) {
Object item = adapter.getItem(i);
if (item != null && text.equals(item.toString())) {
sp.setSelection(i);
return;
}
}
}
private String guessSubjectFromDatabase(String base) {
if (base == null) return null;
String lower = base.toLowerCase();
try {
com.example.myapplication.repository.CourseRepository cr = new com.example.myapplication.repository.CourseRepository(this);
java.util.List<com.example.myapplication.domain.Course> subjects = cr.getAllCourses();
for (com.example.myapplication.domain.Course s : subjects) {
String name = s.getName() == null ? "" : s.getName();
String code = s.getCode() == null ? "" : s.getCode();
if (!name.isEmpty() && lower.contains(name.toLowerCase())) return name;
if (!code.isEmpty() && lower.contains(code.toLowerCase())) return name;
java.util.List<com.example.myapplication.domain.Chapter> chapters = cr.getChaptersBySubjectId(s.getId());
for (com.example.myapplication.domain.Chapter ch : chapters) {
String cn = ch.getName() == null ? "" : ch.getName();
if (!cn.isEmpty() && lower.contains(cn.toLowerCase())) return name;
}
}
} catch (Exception e) {
android.util.Log.e("UploadUI", "guessSubjectFromDatabase failed", e);
}
return null;
}
private void addFileItem(android.net.Uri uri) {
if (uri == null) return;
selectedFiles.add(uri);
String name = UploadService.getDisplayName(this, uri);
String mime = UploadService.getMimeType(this, uri);
LinearLayout row = new LinearLayout(this);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
TextView tv = new TextView(this);
tv.setText((name == null ? "文件" : name) + " | " + (mime == null ? "" : mime));
tv.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
Button removeBtn = new Button(this);
removeBtn.setText("移除");
removeBtn.setOnClickListener(v -> {
selectedFiles.remove(uri);
fileListContainer.removeView(row);
// 清理聚合文本内容
String fileKey = name + "_" + mime;
if (processedFiles.contains(fileKey)) {
processedFiles.remove(fileKey);
// 重新构建聚合文本
rebuildAggregatedText();
}
if (currentPreviewUri != null && currentPreviewUri.equals(uri)) {
if (previewImage != null) previewImage.setImageDrawable(null);
// 删除旧ocrResultText.setText(...)
if (fileInfoText != null) fileInfoText.setText("未选择文件");
updatePreviewFromFirstImage();
}
});
row.addView(tv);
row.addView(removeBtn);
fileListContainer.addView(row);
// 自动提取文档文本内容
extractDocumentText(uri, name, mime);
}
// 文档文本提取方法
// 将图片 OCR 调用切换到 OCRService替换原 runTextRecognition 的使用)
private void extractDocumentText(android.net.Uri uri, String displayName, String mimeType) {
new Thread(() -> {
try {
String extractedText = "";
if (mimeType != null) {
if (mimeType.contains("pdf")) {
extractedText = com.example.myapplication.service.UploadService.extractPdfText(this, uri);
} else if (mimeType.contains("text/plain")) {
extractedText = com.example.myapplication.service.UploadService.extractPlainText(this, uri);
} else if (mimeType.contains("msword") || mimeType.contains("wordprocessingml")) {
runOnUiThread(() -> {
if (fileInfoText != null) {
fileInfoText.setText("Word文档暂不支持建议转换为PDF或文本格式");
}
});
return;
} else if (mimeType.contains("image")) {
runOnUiThread(() -> {
try {
com.google.mlkit.vision.common.InputImage image =
com.google.mlkit.vision.common.InputImage.fromFilePath(this, uri);
com.example.myapplication.service.OCRService.recognize(
this,
image,
new OcrDocCallback(UploadUI.this, displayName, mimeType)
);
} catch (java.io.IOException e) {
android.util.Log.e("OCR", "图片加载失败", e);
}
});
return;
}
}
// 如果提取到文本在主线程更新UI增加 null 安全判断)
if (extractedText != null && !extractedText.trim().isEmpty()) {
final String finalText = extractedText;
runOnUiThread(() -> {
onOcrTextReady(finalText); // 直接写入内容框并联动分类
autoFillFields(finalText, displayName, mimeType); // 保持其他字段自动填充
});
}
} catch (Exception e) {
android.util.Log.e("UploadUI", "extractDocumentText failed", e);
}
}).start();
}
// PDF文本提取
private String extractPdfText(Uri uri) throws IOException {
InputStream inputStream = getContentResolver().openInputStream(uri);
PDDocument document = PDDocument.load(inputStream);
PDFTextStripper stripper = new PDFTextStripper();
// 限制提取页数,避免处理时间过长
int pageCount = document.getNumberOfPages();
if (pageCount > 10) {
stripper.setEndPage(10); // 只提取前10页
}
String text = stripper.getText(document);
document.close();
inputStream.close();
return text;
}
// 纯文本文件提取
private String extractPlainText(Uri uri) throws IOException {
InputStream inputStream = getContentResolver().openInputStream(uri);
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
inputStream.close();
return result.toString("UTF-8");
}
private void updatePreviewFromFirstImage() {
for (Uri u : selectedFiles) {
String mime = com.example.myapplication.service.UploadService.getMimeType(this, u);
if (mime != null && mime.startsWith("image/")) {
if (previewImage != null) previewImage.setImageURI(u);
currentPreviewUri = u;
if (fileInfoText != null) {
fileInfoText.setText(
com.example.myapplication.service.UploadService.getDisplayName(this, u) + " | " + mime
);
}
try {
com.google.mlkit.vision.common.InputImage image =
com.google.mlkit.vision.common.InputImage.fromFilePath(this, u);
com.example.myapplication.service.OCRService.recognize(
this,
image,
new OcrPreviewCallback(UploadUI.this, u, mime)
);
} catch (Exception e) {
Utils.toast(this, "读取图片失败:" + e.getMessage());
}
return;
}
}
previewImage.setImageDrawable(null);
currentPreviewUri = null;
}
// 命名的 OCR 回调类,替代匿名内部类 $1/$2改为静态内部类以避免某些 D8 处理内部类元数据时的崩溃
static class OcrDocCallback implements com.example.myapplication.service.OCRService.Callback {
private final UploadUI ui;
private final String displayName;
private final String mimeType;
OcrDocCallback(UploadUI ui, String displayName, String mimeType) {
this.ui = ui;
this.displayName = displayName;
this.mimeType = mimeType;
}
@Override
public void onSuccess(String text) {
ui.runOnUiThread(() -> {
android.widget.EditText contentEt = ui.findViewById(R.id.contentInput);
if (contentEt != null) {
contentEt.setText(text != null ? text : "");
}
ui.onOcrTextReady(text);
ui.autoFillFields(text, displayName, mimeType);
});
}
@Override
public void onFailure(Exception e) {
com.example.myapplication.Utils.toast(ui, "识别失败:" + e.getMessage());
}
}
private static class OcrPreviewCallback implements com.example.myapplication.service.OCRService.Callback {
private final UploadUI ui;
private final Uri uri;
private final String mime;
OcrPreviewCallback(UploadUI ui, Uri uri, String mime) {
this.ui = ui;
this.uri = uri;
this.mime = mime;
}
@Override
public void onSuccess(String text) {
android.widget.EditText contentEt = ui.findViewById(R.id.contentInput);
if (contentEt != null) {
contentEt.setText((text == null || text.isEmpty()) ? "未识别到文字" : text);
}
ui.autoFillFields(text,
com.example.myapplication.service.UploadService.getDisplayName(ui, uri),
mime);
ui.onOcrTextReady(text);
}
@Override
public void onFailure(Exception e) {
Utils.toast(ui, "读取图片失败:" + e.getMessage());
}
}
private String getMimeType(Uri uri) {
ContentResolver cr = getContentResolver();
return cr.getType(uri);
}
private String getDisplayName(Uri uri) {
String name = null;
try (Cursor cursor = getContentResolver()
.query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
name = cursor.getString(0);
}
} catch (Exception ignored) {}
return name;
}
// 安全方法:避免视图为 null 时崩溃
private void safeSetText(TextView view, CharSequence text) {
if (view != null) {
view.setText(text != null ? text : "");
}
}
private void safeSetSelection(Spinner spinner, int position) {
if (spinner != null && position >= 0) {
spinner.setSelection(position);
}
}
private void onOcrTextReady(String ocrText) {
if (classificationService != null && ocrText != null && ocrText.trim().length() > 0) {
com.example.myapplication.service.ClassificationService.Result res =
classificationService.classifyWithSegmentation(ocrText);
if (classificationResultText != null) {
classificationResultText.setVisibility(android.view.View.VISIBLE);
String confidenceLevel = "";
if (res.confidence >= 0.8) {
confidenceLevel = "高";
} else if (res.confidence >= 0.6) {
confidenceLevel = "中";
} else if (res.confidence >= 0.4) {
confidenceLevel = "低";
} else {
confidenceLevel = "很低";
}
classificationResultText.setText(
"推荐分类:" + res.bestTitle +
"(匹配度" + String.format(java.util.Locale.getDefault(), "%.0f%%", res.confidence * 100) + ""
);
}
try {
applyAutoChapterSelection(res);
} catch (Exception e) {
android.util.Log.e("UploadUI", "auto chapter match failed", e);
}
// 低置信度时静默应用最佳候选,不再弹窗打扰用户
if (res.confidence < 0.6 && res.topCandidates != null && !res.topCandidates.isEmpty()) {
// 直接取第一个候选作为推荐结果应用
com.example.myapplication.service.ClassificationService.Candidate sel = res.topCandidates.get(0);
autoChapterId = sel.chapterId;
autoSubjectId = sel.subjectId;
Spinner subjectSp = findViewById(R.id.subjectSpinner);
if (subjectSp != null) {
com.example.myapplication.repository.CourseRepository cr = new com.example.myapplication.repository.CourseRepository(this);
java.util.List<com.example.myapplication.domain.Course> subs = cr.getAllCourses();
java.util.List<String> names = new java.util.ArrayList<>();
int subjIndex = -1;
for (int i = 0; i < subs.size(); i++) {
names.add(subs.get(i).getName());
if (subs.get(i).getId() == autoSubjectId) subjIndex = i;
}
android.widget.ArrayAdapter<String> subjAdapter = new android.widget.ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, names);
subjAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
subjectSp.setAdapter(subjAdapter);
if (subjIndex >= 0) subjectSp.setSelection(subjIndex);
}
}
}
}
private void applyAutoChapterSelection(com.example.myapplication.service.ClassificationService.Result res) {
if (res == null || res.bestTitle == null) return;
String hint = res.bestTitle.trim();
com.example.myapplication.repository.CourseRepository cr = new com.example.myapplication.repository.CourseRepository(this);
java.util.List<com.example.myapplication.domain.Chapter> hits = cr.findChaptersByNameLike(hint, 1);
if (hits == null || hits.isEmpty()) return;
com.example.myapplication.domain.Chapter ch = hits.get(0);
autoChapterId = ch.getId();
autoSubjectId = (long) ch.getCourseId();
// 预选中科目下拉
Spinner subjectSp = findViewById(R.id.subjectSpinner);
if (subjectSp != null) {
if (availableSubjects == null || availableSubjects.isEmpty()) {
String account = com.example.myapplication.service.AuthService.getCurrentUser(this);
com.example.myapplication.repository.UserRepository ur2 = new com.example.myapplication.repository.UserRepository(this);
com.example.myapplication.domain.User u2 = ur2.getUserByAccount(account);
Long majorId = u2 != null ? u2.getMajorId() : null;
String gradeText = ur2.getGradeText(account);
availableSubjects = cr.getCoursesByMajorAndGrade(majorId, gradeText);
if (availableSubjects == null || availableSubjects.isEmpty()) availableSubjects = cr.getCoursesByMajor(majorId);
if (availableSubjects == null || availableSubjects.isEmpty()) availableSubjects = cr.getAllCourses();
}
int subjIndex = -1;
for (int i = 0; i < availableSubjects.size(); i++) {
if (availableSubjects.get(i).getId() == autoSubjectId) { subjIndex = i; break; }
}
if (subjIndex >= 0) subjectSp.setSelection(subjIndex);
}
loadChaptersForSubject(autoSubjectId);
android.widget.Spinner chapterSp = findViewById(R.id.chapterSpinner);
if (chapterSp != null && availableChapters != null) {
int chIndex = -1;
for (int i = 0; i < availableChapters.size(); i++) if (availableChapters.get(i).getId() == autoChapterId) { chIndex = i; break; }
if (chIndex >= 0) chapterSp.setSelection(chIndex);
}
}
private void loadChaptersForSubject(long subjectId) {
com.example.myapplication.repository.CourseRepository cr = new com.example.myapplication.repository.CourseRepository(this);
availableChapters = cr.getChaptersBySubjectId(subjectId);
java.util.List<String> names = new java.util.ArrayList<>();
for (com.example.myapplication.domain.Chapter c : availableChapters) names.add(c.getName());
android.widget.ArrayAdapter<String> ad = new android.widget.ArrayAdapter<>(this, android.R.layout.simple_spinner_item, names);
ad.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
android.widget.Spinner chapterSp = findViewById(R.id.chapterSpinner);
if (chapterSp != null) chapterSp.setAdapter(ad);
}
// HanLP增强版分类器使用真正的HanLP开源代码
private static class KeywordClassifier {
private final Context context;
private final List<Node> leafNodes = new ArrayList<>();
private final Set<String> stopwords = new HashSet<>(Arrays.asList(
"的", "和", "与", "或", "在", "是", "为", "及", "等", "公式", "计算", "方法", "简介"
));
private boolean hanLPInitialized = false;
static class Node {
String id;
String title;
List<String> keywords = new ArrayList<>();
List<String> aliases = new ArrayList<>();
}
static class Result {
String bestId;
String bestTitle;
double confidence;
List<Node> topCandidates = new ArrayList<>();
}
KeywordClassifier(Context ctx) throws IOException, JSONException {
this.context = ctx.getApplicationContext();
loadOutlineFromAssets("course_outline_robotics.json");
// 初始化HanLP
try {
// 测试HanLP是否可用
Log.i("HanLP", "HanLP初始化成功");
} catch (Exception e) {
Log.w("HanLP", "HanLP初始化失败将使用基础分类: " + e.getMessage());
}
}
private void loadOutlineFromAssets(String fileName) throws IOException, JSONException {
String json = readAssetText(fileName);
JSONObject root = new JSONObject(json);
JSONArray nodes = root.getJSONArray("nodes");
for (int i = 0; i < nodes.length(); i++) {
JSONObject chapter = nodes.getJSONObject(i);
parseNodeRecursive(chapter);
}
}
private void parseNodeRecursive(JSONObject obj) throws JSONException {
String id = obj.optString("id");
String title = obj.optString("title");
List<String> keywords = jsonArrayToList(obj.optJSONArray("keywords"));
List<String> aliases = jsonArrayToList(obj.optJSONArray("aliases"));
JSONArray children = obj.optJSONArray("children");
if (children == null || children.length() == 0) {
Node node = new Node();
node.id = id;
node.title = title;
node.keywords.addAll(keywords);
node.aliases.addAll(aliases);
leafNodes.add(node);
} else {
Node node = new Node();
node.id = id;
node.title = title;
node.keywords.addAll(keywords);
node.aliases.addAll(aliases);
leafNodes.add(node);
for (int i = 0; i < children.length(); i++) {
parseNodeRecursive(children.getJSONObject(i));
}
}
}
private List<String> jsonArrayToList(JSONArray arr) {
List<String> list = new ArrayList<>();
if (arr == null) return list;
for (int i = 0; i < arr.length(); i++) {
list.add(arr.optString(i));
}
return list;
}
private String readAssetText(String fileName) throws IOException {
AssetManager am = context.getAssets();
InputStream is = am.open(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
is.close();
return baos.toString("UTF-8");
}
}
// 重新构建聚合文本(当文件被移除时)
private void rebuildAggregatedText() {
// 注意:这是一个简化实现,实际应用中可能需要重新提取所有文件
// 这里只是清空聚合文本,让用户重新上传文件
// 如果还有文件,提示用户重新处理
if (!selectedFiles.isEmpty()) {
fileInfoText.setText("文件已移除,请重新上传以更新内容分析");
} else {
fileInfoText.setText("未选择文件");
// 清空表单
EditText titleEt = findViewById(R.id.noteTitle);
EditText descEt = findViewById(R.id.contentInput);
EditText tagsEt = findViewById(R.id.tagsInput);
if (titleEt != null) titleEt.setText("");
if (descEt != null) descEt.setText("");
if (tagsEt != null) tagsEt.setText("");
if (classificationResultText != null) {
classificationResultText.setVisibility(View.GONE);
}
}
}
// HanLP增强分类器真正集成HanLP
private static class EnhancedKeywordClassifier {
private final Context context;
private boolean hanLPInitialized = false;
static class Result {
String bestId = "";
String bestTitle = "";
double confidence = 0.0;
boolean isSegmented = false;
List<Object> topCandidates = new ArrayList<>();
}
EnhancedKeywordClassifier(Context ctx) throws IOException, JSONException {
this.context = ctx.getApplicationContext();
// 移除 HanLP 依赖UI 层仅使用基础分类逻辑
hanLPInitialized = false;
}
Result classifyWithSegmentation(String text) {
Result result = new Result();
if (text == null || text.trim().isEmpty()) {
result.bestTitle = "未能判定,建议人工选择章节";
return result;
}
// 仅使用基础分类逻辑,移除 HanLP 相关增强
result = performBasicClassification(text);
return result;
}
private Result performBasicClassification(String text) {
Result result = new Result();
String lowerText = text.toLowerCase();
// 机器人学相关关键词匹配
if (lowerText.contains("机器人") || lowerText.contains("robot") || lowerText.contains("robotics")) {
result.bestTitle = "第1章 机器人学概述";
result.confidence = 0.8;
} else if (lowerText.contains("坐标") || lowerText.contains("变换") || lowerText.contains("transform") ||
lowerText.contains("齐次") || lowerText.contains("homogeneous")) {
result.bestTitle = "第2章 坐标变换与齐次变换";
result.confidence = 0.75;
} else if (lowerText.contains("运动学") || lowerText.contains("kinematics") ||
lowerText.contains("正运动学") || lowerText.contains("逆运动学")) {
result.bestTitle = "第3章 机器人运动学";
result.confidence = 0.8;
} else if (lowerText.contains("雅可比") || lowerText.contains("jacobian") ||
lowerText.contains("速度") || lowerText.contains("velocity")) {
result.bestTitle = "第4章 雅可比矩阵";
result.confidence = 0.85;
} else if (lowerText.contains("动力学") || lowerText.contains("dynamics") ||
lowerText.contains("拉格朗日") || lowerText.contains("lagrange")) {
result.bestTitle = "第5章 机器人动力学";
result.confidence = 0.8;
} else if (lowerText.contains("轨迹") || lowerText.contains("路径") || lowerText.contains("trajectory") ||
lowerText.contains("规划") || lowerText.contains("planning")) {
result.bestTitle = "第6章 轨迹规划";
result.confidence = 0.75;
} else if (lowerText.contains("控制") || lowerText.contains("control") ||
lowerText.contains("pid") || lowerText.contains("反馈")) {
result.bestTitle = "第7章 机器人控制";
result.confidence = 0.8;
} else {
result.bestTitle = "第1章 机器人学概述";
result.confidence = 0.5;
}
return result;
}
}
}

@ -0,0 +1,225 @@
package com.example.myapplication.ui;
import com.example.myapplication.R;
import com.example.myapplication.Utils;
import com.example.myapplication.service.UserService;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.text.TextUtils;
import androidx.appcompat.app.AppCompatActivity;
public class UserCenterUI extends AppCompatActivity {
private TextView statUploaded, statLikes, statPoints, majorNameTv, gradeNameTv;
private EditText usernameEt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_center);
findViewById(R.id.backBtn).setOnClickListener(v -> finish());
statUploaded = findViewById(R.id.statUploaded);
statLikes = findViewById(R.id.statLikes);
statPoints = findViewById(R.id.statPoints);
majorNameTv = findViewById(R.id.majorNameTv);
gradeNameTv = findViewById(R.id.gradeNameTv);
usernameEt = findViewById(R.id.usernameInput);
// 可选:去掉写死示例,统一由 refreshStats() 赋值
statUploaded.setText("15");
statLikes.setText("128");
usernameEt.setText(com.example.myapplication.service.UserService.getUser(this));
// 刷新数据库统计
refreshStats();
// 动态渲染我的笔记
renderMyNotes();
}
@Override
protected void onResume() {
super.onResume();
refreshStats();
renderMyNotes();
}
private void refreshStats() {
String account = com.example.myapplication.service.AuthService.getCurrentUser(this);
com.example.myapplication.repository.NoteRepository noteRepo =
new com.example.myapplication.repository.NoteRepository(this);
java.util.List<com.example.myapplication.domain.Note> myNotes =
noteRepo.getNotesByUser(account);
int uploadedCount = myNotes != null ? myNotes.size() : 0;
int totalLikes = new com.example.myapplication.repository.NoteRepository(this)
.getTotalLikesByUser(account);
com.example.myapplication.repository.UserRepository userRepo =
new com.example.myapplication.repository.UserRepository(this);
int points = userRepo.getPoints(account);
com.example.myapplication.domain.User u = userRepo.getUserByAccount(account);
String majorName = "未设置";
String gradeText = null;
if (u != null && u.getMajorId() != null) {
com.example.myapplication.repository.MajorRepository mr = new com.example.myapplication.repository.MajorRepository(this);
com.example.myapplication.domain.Major m = mr.getById(u.getMajorId());
if (m != null && m.getName() != null) majorName = m.getName();
}
statUploaded.setText(String.valueOf(uploadedCount));
statLikes.setText(String.valueOf(totalLikes));
statPoints.setText(String.valueOf(points));
if (majorNameTv != null) majorNameTv.setText("专业:" + majorName);
gradeText = new com.example.myapplication.repository.UserRepository(this).getGradeText(account);
if (gradeNameTv != null) gradeNameTv.setText("年级:" + (gradeText == null ? "未设置" : gradeText));
}
// 动态渲染“我的笔记”
private void renderMyNotes() {
android.widget.LinearLayout container = findViewById(R.id.myNotesContainer);
if (container == null) return;
container.removeAllViews();
String account = com.example.myapplication.service.AuthService.getCurrentUser(this);
com.example.myapplication.repository.NoteRepository repo =
new com.example.myapplication.repository.NoteRepository(this);
java.util.List<com.example.myapplication.domain.Note> notes =
repo.getNotesByUser(account);
if (notes == null || notes.isEmpty()) {
android.widget.TextView empty = new android.widget.TextView(this);
empty.setText("暂无笔记,点击“上传笔记”添加吧~");
empty.setTextColor(android.graphics.Color.parseColor("#666666"));
container.addView(empty);
return;
}
float density = getResources().getDisplayMetrics().density;
int pad = (int) (12 * density);
int mt = (int) (12 * density);
com.example.myapplication.repository.LikeRepository likeRepo =
new com.example.myapplication.repository.LikeRepository(this);
for (com.example.myapplication.domain.Note n : notes) {
android.widget.LinearLayout item = new android.widget.LinearLayout(this);
item.setOrientation(android.widget.LinearLayout.VERTICAL);
item.setBackgroundResource(R.drawable.card_background);
item.setPadding(pad, pad, pad, pad);
android.widget.LinearLayout.LayoutParams lp =
new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(0, mt, 0, 0);
item.setLayoutParams(lp);
item.setElevation(4f);
android.widget.TextView titleTv = new android.widget.TextView(this);
titleTv.setText(n.getTitle() != null ? n.getTitle() : "(无标题)");
titleTv.setTextColor(getResources().getColor(R.color.text_primary));
titleTv.setTypeface(android.graphics.Typeface.DEFAULT_BOLD);
titleTv.setTextSize(16);
int likes = n.getLikeCount();
String created = n.getCreated() != null ? n.getCreated() : "";
android.widget.TextView metaTv = new android.widget.TextView(this);
metaTv.setText((TextUtils.isEmpty(created) ? "📅 未知" : "📅 " + created) + " 👍 " + likes);
metaTv.setTextColor(getResources().getColor(R.color.text_secondary));
metaTv.setTextSize(14);
item.addView(titleTv);
item.addView(metaTv);
android.widget.LinearLayout actions = new android.widget.LinearLayout(this);
actions.setOrientation(android.widget.LinearLayout.HORIZONTAL);
android.widget.LinearLayout.LayoutParams alp = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
alp.topMargin = (int) (12 * density);
actions.setLayoutParams(alp);
android.widget.Button viewBtn = new android.widget.Button(this);
viewBtn.setText("查看");
viewBtn.setAllCaps(false);
viewBtn.setPadding(pad, pad / 2, pad, pad / 2);
viewBtn.setTextColor(android.graphics.Color.WHITE);
androidx.core.view.ViewCompat.setBackgroundTintList(viewBtn,
android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.primary)));
android.widget.Button editBtn = new android.widget.Button(this);
editBtn.setText("编辑");
editBtn.setAllCaps(false);
editBtn.setPadding(pad, pad / 2, pad, pad / 2);
editBtn.setTextColor(android.graphics.Color.WHITE);
androidx.core.view.ViewCompat.setBackgroundTintList(editBtn,
android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.success)));
android.widget.Button delBtn = new android.widget.Button(this);
delBtn.setText("删除");
delBtn.setAllCaps(false);
delBtn.setPadding(pad, pad / 2, pad, pad / 2);
delBtn.setTextColor(android.graphics.Color.WHITE);
androidx.core.view.ViewCompat.setBackgroundTintList(delBtn,
android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.danger)));
android.widget.LinearLayout.LayoutParams btnLp = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT,
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
);
actions.addView(viewBtn, btnLp);
android.widget.Space sp1 = new android.widget.Space(this);
actions.addView(sp1, new android.widget.LinearLayout.LayoutParams((int)(8 * density), 1));
actions.addView(editBtn, btnLp);
android.widget.Space sp2 = new android.widget.Space(this);
actions.addView(sp2, new android.widget.LinearLayout.LayoutParams((int)(8 * density), 1));
actions.addView(delBtn, btnLp);
viewBtn.setOnClickListener(v -> {
com.example.myapplication.service.NoteService.setCurrentNoteId(this, (int) n.getId());
android.content.Intent intent = new android.content.Intent(this, com.example.myapplication.ui.NoteDetailUI.class);
intent.putExtra("noteId", (int) n.getId());
startActivity(intent);
});
editBtn.setOnClickListener(v -> {
com.example.myapplication.service.NoteService.setCurrentNoteId(this, (int) n.getId());
android.content.Intent intent = new android.content.Intent(this, com.example.myapplication.ui.NoteEditUI.class);
startActivity(intent);
});
delBtn.setOnClickListener(v -> {
android.app.AlertDialog.Builder b = new android.app.AlertDialog.Builder(this);
b.setTitle("删除笔记");
b.setMessage("确定要删除这条笔记吗?");
b.setPositiveButton("删除", (d, which) -> {
boolean okDel = new com.example.myapplication.repository.NoteRepository(this).deleteNote(n.getId());
if (okDel) {
com.example.myapplication.Utils.toast(this, "已删除");
refreshStats();
renderMyNotes();
} else {
com.example.myapplication.Utils.toast(this, "删除失败");
}
});
b.setNegativeButton("取消", (d, which) -> {});
b.show();
});
item.addView(actions);
container.addView(item);
}
}
}

@ -0,0 +1,95 @@
package net.micode.myapplication.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "MyNote.db";
private static final int DATABASE_VERSION = 1;
// 表名
public static final String TABLE_COURSES = "courses";
public static final String TABLE_CHAPTERS = "chapters";
public static final String TABLE_NOTES = "notes";
// Courses表列名
public static final String COLUMN_COURSE_ID = "course_id";
public static final String COLUMN_COURSE_NAME = "course_name";
public static final String COLUMN_SCHOOL = "school";
// Chapters表列名
public static final String COLUMN_CHAPTER_ID = "chapter_id";
public static final String COLUMN_CHAPTER_NUMBER = "chapter_number";
public static final String COLUMN_CHAPTER_TITLE = "chapter_title";
public static final String COLUMN_KEYWORDS = "keywords";
public static final String COLUMN_ALIASES = "aliases";
// Notes表列名
public static final String COLUMN_NOTE_ID = "note_id";
public static final String COLUMN_TITLE = "title";
public static final String COLUMN_DESCRIPTION = "description";
public static final String COLUMN_OCR_TEXT = "ocr_text";
public static final String COLUMN_FILE_PATH = "file_path";
public static final String COLUMN_UPLOAD_TIME = "upload_time";
public static final String COLUMN_UPDATE_TIME = "update_time";
// 创建Courses表的SQL语句
private static final String CREATE_TABLE_COURSES = "CREATE TABLE " + TABLE_COURSES + " (" +
COLUMN_COURSE_ID + " TEXT PRIMARY KEY, " +
COLUMN_COURSE_NAME + " TEXT NOT NULL, " +
COLUMN_SCHOOL + " TEXT" +
");";
// 创建Chapters表的SQL语句
private static final String CREATE_TABLE_CHAPTERS = "CREATE TABLE " + TABLE_CHAPTERS + " (" +
COLUMN_CHAPTER_ID + " TEXT PRIMARY KEY, " +
COLUMN_COURSE_ID + " TEXT NOT NULL, " +
COLUMN_CHAPTER_NUMBER + " INTEGER NOT NULL, " +
COLUMN_CHAPTER_TITLE + " TEXT NOT NULL, " +
COLUMN_KEYWORDS + " TEXT, " +
COLUMN_ALIASES + " TEXT, " +
"FOREIGN KEY(" + COLUMN_COURSE_ID + ") REFERENCES " + TABLE_COURSES + "(" + COLUMN_COURSE_ID + ")" +
");";
// 创建Notes表的SQL语句
private static final String CREATE_TABLE_NOTES = "CREATE TABLE " + TABLE_NOTES + " (" +
COLUMN_NOTE_ID + " TEXT PRIMARY KEY, " +
COLUMN_TITLE + " TEXT NOT NULL, " +
COLUMN_DESCRIPTION + " TEXT, " +
COLUMN_OCR_TEXT + " TEXT, " +
COLUMN_COURSE_ID + " TEXT, " +
COLUMN_CHAPTER_ID + " TEXT, " +
COLUMN_FILE_PATH + " TEXT, " +
COLUMN_UPLOAD_TIME + " INTEGER, " +
COLUMN_UPDATE_TIME + " INTEGER, " +
"FOREIGN KEY(" + COLUMN_COURSE_ID + ") REFERENCES " + TABLE_COURSES + "(" + COLUMN_COURSE_ID + "), " +
"FOREIGN KEY(" + COLUMN_CHAPTER_ID + ") REFERENCES " + TABLE_CHAPTERS + "(" + COLUMN_CHAPTER_ID + ")" +
");";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_COURSES);
db.execSQL(CREATE_TABLE_CHAPTERS);
db.execSQL(CREATE_TABLE_NOTES);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NOTES);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_CHAPTERS);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_COURSES);
onCreate(db);
}
// 清除所有数据的方法
public void clearAllData(SQLiteDatabase db) {
db.execSQL("DELETE FROM " + TABLE_NOTES);
db.execSQL("DELETE FROM " + TABLE_CHAPTERS);
db.execSQL("DELETE FROM " + TABLE_COURSES);
}
}

@ -0,0 +1,101 @@
package net.micode.myapplication.model;
import java.util.ArrayList;
import java.util.List;
public class Chapter {
private String chapterId;
private String courseId;
private int chapterNumber;
private String chapterTitle;
private List<String> keywords;
private List<String> aliases;
public Chapter() {
this.keywords = new ArrayList<>();
this.aliases = new ArrayList<>();
}
public Chapter(String chapterId, String courseId, int chapterNumber, String chapterTitle) {
this.chapterId = chapterId;
this.courseId = courseId;
this.chapterNumber = chapterNumber;
this.chapterTitle = chapterTitle;
this.keywords = new ArrayList<>();
this.aliases = new ArrayList<>();
}
public String getChapterId() {
return chapterId;
}
public void setChapterId(String chapterId) {
this.chapterId = chapterId;
}
public String getCourseId() {
return courseId;
}
public void setCourseId(String courseId) {
this.courseId = courseId;
}
public int getChapterNumber() {
return chapterNumber;
}
public void setChapterNumber(int chapterNumber) {
this.chapterNumber = chapterNumber;
}
public String getChapterTitle() {
return chapterTitle;
}
public void setChapterTitle(String chapterTitle) {
this.chapterTitle = chapterTitle;
}
public List<String> getKeywords() {
return keywords;
}
public void setKeywords(List<String> keywords) {
this.keywords = keywords;
}
public List<String> getAliases() {
return aliases;
}
public void setAliases(List<String> aliases) {
this.aliases = aliases;
}
public void addKeyword(String keyword) {
if (this.keywords == null) {
this.keywords = new ArrayList<>();
}
this.keywords.add(keyword);
}
public void addAlias(String alias) {
if (this.aliases == null) {
this.aliases = new ArrayList<>();
}
this.aliases.add(alias);
}
@Override
public String toString() {
return "Chapter{" +
"chapterId='" + chapterId + '\'' +
", courseId='" + courseId + '\'' +
", chapterNumber=" + chapterNumber +
", chapterTitle='" + chapterTitle + '\'' +
", keywords=" + keywords +
", aliases=" + aliases +
'}';
}
}

@ -0,0 +1,60 @@
package net.micode.myapplication.model;
public class ClassificationResult {
private Course course;
private Chapter chapter;
private double confidenceScore;
private String reason;
public ClassificationResult() {
}
public ClassificationResult(Course course, Chapter chapter, double confidenceScore, String reason) {
this.course = course;
this.chapter = chapter;
this.confidenceScore = confidenceScore;
this.reason = reason;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
public Chapter getChapter() {
return chapter;
}
public void setChapter(Chapter chapter) {
this.chapter = chapter;
}
public double getConfidenceScore() {
return confidenceScore;
}
public void setConfidenceScore(double confidenceScore) {
this.confidenceScore = confidenceScore;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
@Override
public String toString() {
return "ClassificationResult{" +
"course=" + (course != null ? course.getCourseName() : "null") +
", chapter=" + (chapter != null ? chapter.getChapterTitle() : "null") +
", confidenceScore=" + confidenceScore +
", reason='" + reason + '\'' +
'}';
}
}

@ -0,0 +1,49 @@
package net.micode.myapplication.model;
public class Course {
private String courseId;
private String courseName;
private String school;
public Course() {
}
public Course(String courseId, String courseName, String school) {
this.courseId = courseId;
this.courseName = courseName;
this.school = school;
}
public String getCourseId() {
return courseId;
}
public void setCourseId(String courseId) {
this.courseId = courseId;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Course{" +
"courseId='" + courseId + '\'' +
", courseName='" + courseName + '\'' +
", school='" + school + '\'' +
'}';
}
}

@ -0,0 +1,111 @@
package net.micode.myapplication.model;
public class Note {
private String noteId;
private String title;
private String description;
private String ocrText;
private String courseId;
private String chapterId;
private String filePath;
private long uploadTime;
private long updateTime;
public Note() {
this.uploadTime = System.currentTimeMillis();
this.updateTime = this.uploadTime;
}
public Note(String noteId, String title, String description, String filePath) {
this.noteId = noteId;
this.title = title;
this.description = description;
this.filePath = filePath;
this.uploadTime = System.currentTimeMillis();
this.updateTime = this.uploadTime;
}
public String getNoteId() {
return noteId;
}
public void setNoteId(String noteId) {
this.noteId = noteId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getOcrText() {
return ocrText;
}
public void setOcrText(String ocrText) {
this.ocrText = ocrText;
}
public String getCourseId() {
return courseId;
}
public void setCourseId(String courseId) {
this.courseId = courseId;
}
public String getChapterId() {
return chapterId;
}
public void setChapterId(String chapterId) {
this.chapterId = chapterId;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public long getUploadTime() {
return uploadTime;
}
public void setUploadTime(long uploadTime) {
this.uploadTime = uploadTime;
}
public long getUpdateTime() {
return updateTime;
}
public void setUpdateTime(long updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "Note{" +
"noteId='" + noteId + '\'' +
", title='" + title + '\'' +
", description='" + description + '\'' +
", courseId='" + courseId + '\'' +
", chapterId='" + chapterId + '\'' +
", uploadTime=" + uploadTime +
'}';
}
}

@ -0,0 +1,211 @@
package net.micode.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import net.micode.myapplication.database.DatabaseHelper;
import net.micode.myapplication.model.Chapter;
import net.micode.myapplication.model.Course;
import java.util.ArrayList;
import java.util.List;
public class CourseRepository {
private DatabaseHelper dbHelper;
public CourseRepository(Context context) {
this.dbHelper = new DatabaseHelper(context);
}
// Course相关操作
public long insertCourse(Course course) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_COURSE_ID, course.getCourseId());
values.put(DatabaseHelper.COLUMN_COURSE_NAME, course.getCourseName());
values.put(DatabaseHelper.COLUMN_SCHOOL, course.getSchool());
return db.insert(DatabaseHelper.TABLE_COURSES, null, values);
}
public Course getCourseById(String courseId) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_COURSES,
null,
DatabaseHelper.COLUMN_COURSE_ID + " = ?",
new String[]{courseId},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
Course course = new Course();
course.setCourseId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_ID)));
course.setCourseName(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_NAME)));
course.setSchool(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_SCHOOL)));
cursor.close();
return course;
}
if (cursor != null) {
cursor.close();
}
return null;
}
public List<Course> getAllCourses() {
List<Course> courses = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_COURSES, null, null, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
Course course = new Course();
course.setCourseId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_ID)));
course.setCourseName(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_NAME)));
course.setSchool(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_SCHOOL)));
courses.add(course);
} while (cursor.moveToNext());
cursor.close();
}
return courses;
}
// Chapter相关操作
public long insertChapter(Chapter chapter) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_CHAPTER_ID, chapter.getChapterId());
values.put(DatabaseHelper.COLUMN_COURSE_ID, chapter.getCourseId());
values.put(DatabaseHelper.COLUMN_CHAPTER_NUMBER, chapter.getChapterNumber());
values.put(DatabaseHelper.COLUMN_CHAPTER_TITLE, chapter.getChapterTitle());
values.put(DatabaseHelper.COLUMN_KEYWORDS, listToString(chapter.getKeywords()));
values.put(DatabaseHelper.COLUMN_ALIASES, listToString(chapter.getAliases()));
return db.insert(DatabaseHelper.TABLE_CHAPTERS, null, values);
}
public Chapter getChapterById(String chapterId) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_CHAPTERS,
null,
DatabaseHelper.COLUMN_CHAPTER_ID + " = ?",
new String[]{chapterId},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
Chapter chapter = new Chapter();
chapter.setChapterId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_ID)));
chapter.setCourseId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_ID)));
chapter.setChapterNumber(cursor.getInt(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_NUMBER)));
chapter.setChapterTitle(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_TITLE)));
chapter.setKeywords(stringToList(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_KEYWORDS))));
chapter.setAliases(stringToList(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_ALIASES))));
cursor.close();
return chapter;
}
if (cursor != null) {
cursor.close();
}
return null;
}
public List<Chapter> getChaptersByCourseId(String courseId) {
List<Chapter> chapters = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_CHAPTERS,
null,
DatabaseHelper.COLUMN_COURSE_ID + " = ?",
new String[]{courseId},
null, null, DatabaseHelper.COLUMN_CHAPTER_NUMBER + " ASC");
if (cursor != null && cursor.moveToFirst()) {
do {
Chapter chapter = new Chapter();
chapter.setChapterId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_ID)));
chapter.setCourseId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_ID)));
chapter.setChapterNumber(cursor.getInt(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_NUMBER)));
chapter.setChapterTitle(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_TITLE)));
chapter.setKeywords(stringToList(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_KEYWORDS))));
chapter.setAliases(stringToList(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_ALIASES))));
chapters.add(chapter);
} while (cursor.moveToNext());
cursor.close();
}
return chapters;
}
public List<Chapter> getAllChapters() {
List<Chapter> chapters = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_CHAPTERS, null, null, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
Chapter chapter = new Chapter();
chapter.setChapterId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_ID)));
chapter.setCourseId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_ID)));
chapter.setChapterNumber(cursor.getInt(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_NUMBER)));
chapter.setChapterTitle(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_TITLE)));
chapter.setKeywords(stringToList(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_KEYWORDS))));
chapter.setAliases(stringToList(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_ALIASES))));
chapters.add(chapter);
} while (cursor.moveToNext());
cursor.close();
}
return chapters;
}
// 批量插入课程和章节数据
public void insertCourseWithChapters(Course course, List<Chapter> chapters) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
insertCourse(course);
for (Chapter chapter : chapters) {
insertChapter(chapter);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
// 辅助方法将List转换为String用逗号分隔
private String listToString(List<String> list) {
if (list == null || list.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
sb.append(",");
}
sb.append(list.get(i));
}
return sb.toString();
}
// 辅助方法将String转换为List
private List<String> stringToList(String str) {
List<String> list = new ArrayList<>();
if (str != null && !str.trim().isEmpty()) {
String[] items = str.split(",");
for (String item : items) {
if (!item.trim().isEmpty()) {
list.add(item.trim());
}
}
}
return list;
}
// 关闭数据库连接
public void close() {
dbHelper.close();
}
}

@ -0,0 +1,183 @@
package net.micode.myapplication.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import net.micode.myapplication.database.DatabaseHelper;
import net.micode.myapplication.model.Note;
import java.util.ArrayList;
import java.util.List;
public class NoteRepository {
private DatabaseHelper dbHelper;
public NoteRepository(Context context) {
this.dbHelper = new DatabaseHelper(context);
}
public long insertNote(Note note) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_NOTE_ID, note.getNoteId());
values.put(DatabaseHelper.COLUMN_TITLE, note.getTitle());
values.put(DatabaseHelper.COLUMN_DESCRIPTION, note.getDescription());
values.put(DatabaseHelper.COLUMN_OCR_TEXT, note.getOcrText());
values.put(DatabaseHelper.COLUMN_COURSE_ID, note.getCourseId());
values.put(DatabaseHelper.COLUMN_CHAPTER_ID, note.getChapterId());
values.put(DatabaseHelper.COLUMN_FILE_PATH, note.getFilePath());
values.put(DatabaseHelper.COLUMN_UPLOAD_TIME, note.getUploadTime());
values.put(DatabaseHelper.COLUMN_UPDATE_TIME, note.getUpdateTime());
return db.insert(DatabaseHelper.TABLE_NOTES, null, values);
}
public Note getNoteById(String noteId) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_NOTES,
null,
DatabaseHelper.COLUMN_NOTE_ID + " = ?",
new String[]{noteId},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
Note note = cursorToNote(cursor);
cursor.close();
return note;
}
if (cursor != null) {
cursor.close();
}
return null;
}
public List<Note> getAllNotes() {
List<Note> notes = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_NOTES, null, null, null, null, null,
DatabaseHelper.COLUMN_UPLOAD_TIME + " DESC");
if (cursor != null && cursor.moveToFirst()) {
do {
notes.add(cursorToNote(cursor));
} while (cursor.moveToNext());
cursor.close();
}
return notes;
}
public List<Note> getNotesByCourse(String courseId) {
List<Note> notes = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_NOTES,
null,
DatabaseHelper.COLUMN_COURSE_ID + " = ?",
new String[]{courseId},
null, null, DatabaseHelper.COLUMN_UPLOAD_TIME + " DESC");
if (cursor != null && cursor.moveToFirst()) {
do {
notes.add(cursorToNote(cursor));
} while (cursor.moveToNext());
cursor.close();
}
return notes;
}
public List<Note> getNotesByChapter(String chapterId) {
List<Note> notes = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(DatabaseHelper.TABLE_NOTES,
null,
DatabaseHelper.COLUMN_CHAPTER_ID + " = ?",
new String[]{chapterId},
null, null, DatabaseHelper.COLUMN_UPLOAD_TIME + " DESC");
if (cursor != null && cursor.moveToFirst()) {
do {
notes.add(cursorToNote(cursor));
} while (cursor.moveToNext());
cursor.close();
}
return notes;
}
public int updateNote(Note note) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatabaseHelper.COLUMN_TITLE, note.getTitle());
values.put(DatabaseHelper.COLUMN_DESCRIPTION, note.getDescription());
values.put(DatabaseHelper.COLUMN_OCR_TEXT, note.getOcrText());
values.put(DatabaseHelper.COLUMN_COURSE_ID, note.getCourseId());
values.put(DatabaseHelper.COLUMN_CHAPTER_ID, note.getChapterId());
values.put(DatabaseHelper.COLUMN_FILE_PATH, note.getFilePath());
values.put(DatabaseHelper.COLUMN_UPDATE_TIME, System.currentTimeMillis());
return db.update(DatabaseHelper.TABLE_NOTES, values,
DatabaseHelper.COLUMN_NOTE_ID + " = ?",
new String[]{note.getNoteId()});
}
public int deleteNote(String noteId) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
return db.delete(DatabaseHelper.TABLE_NOTES,
DatabaseHelper.COLUMN_NOTE_ID + " = ?",
new String[]{noteId});
}
public int deleteNotesByCourse(String courseId) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
return db.delete(DatabaseHelper.TABLE_NOTES,
DatabaseHelper.COLUMN_COURSE_ID + " = ?",
new String[]{courseId});
}
private Note cursorToNote(Cursor cursor) {
Note note = new Note();
note.setNoteId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_NOTE_ID)));
note.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_TITLE)));
note.setDescription(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_DESCRIPTION)));
note.setOcrText(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_OCR_TEXT)));
note.setCourseId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_COURSE_ID)));
note.setChapterId(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_CHAPTER_ID)));
note.setFilePath(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_FILE_PATH)));
note.setUploadTime(cursor.getLong(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_UPLOAD_TIME)));
note.setUpdateTime(cursor.getLong(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_UPDATE_TIME)));
return note;
}
// 搜索笔记
public List<Note> searchNotes(String query) {
List<Note> notes = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
String selection = DatabaseHelper.COLUMN_TITLE + " LIKE ? OR " +
DatabaseHelper.COLUMN_DESCRIPTION + " LIKE ? OR " +
DatabaseHelper.COLUMN_OCR_TEXT + " LIKE ?";
String[] selectionArgs = new String[]{"%" + query + "%", "%" + query + "%", "%" + query + "%"};
Cursor cursor = db.query(DatabaseHelper.TABLE_NOTES,
null, selection, selectionArgs, null, null,
DatabaseHelper.COLUMN_UPLOAD_TIME + " DESC");
if (cursor != null && cursor.moveToFirst()) {
do {
notes.add(cursorToNote(cursor));
} while (cursor.moveToNext());
cursor.close();
}
return notes;
}
// 关闭数据库连接
public void close() {
dbHelper.close();
}
}

@ -0,0 +1,261 @@
package net.micode.myapplication.service;
import net.micode.myapplication.model.Chapter;
import net.micode.myapplication.model.ClassificationResult;
import net.micode.myapplication.model.Course;
import net.micode.myapplication.model.Note;
import net.micode.myapplication.repository.CourseRepository;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class ClassificationService {
private CourseRepository courseRepository;
private TextProcessor textProcessor;
// 分类阈值
private static final double MIN_CONFIDENCE_THRESHOLD = 0.3; // 最低置信度阈值
private static final double HIGH_CONFIDENCE_THRESHOLD = 0.7; // 高置信度阈值
public ClassificationService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
this.textProcessor = new TextProcessor();
}
/**
*
*/
public ClassificationResult classifyNote(Note note) {
if (note == null) {
return createNoMatchResult("笔记为空");
}
// 获取所有课程和章节
List<Course> allCourses = courseRepository.getAllCourses();
if (allCourses.isEmpty()) {
return createNoMatchResult("没有可用的课程数据");
}
// 组合笔记的文本内容
String noteText = combineNoteText(note);
if (noteText.isEmpty()) {
return createNoMatchResult("笔记内容为空");
}
// 预处理文本
String processedText = textProcessor.preprocessText(noteText);
// 计算每个章节的匹配分数
List<ChapterMatch> chapterMatches = new ArrayList<>();
for (Course course : allCourses) {
List<Chapter> chapters = courseRepository.getChaptersByCourseId(course.getCourseId());
for (Chapter chapter : chapters) {
double score = calculateMatchScore(processedText, chapter);
if (score > 0) {
chapterMatches.add(new ChapterMatch(course, chapter, score));
}
}
}
// 如果没有找到匹配的章节
if (chapterMatches.isEmpty()) {
return createNoMatchResult("没有找到匹配的课程章节");
}
// 按分数排序,找到最佳匹配
Collections.sort(chapterMatches, (a, b) -> Double.compare(b.score, a.score));
ChapterMatch bestMatch = chapterMatches.get(0);
// 检查置信度
if (bestMatch.score < MIN_CONFIDENCE_THRESHOLD) {
return createLowConfidenceResult(bestMatch.course, bestMatch.chapter, bestMatch.score);
}
// 返回分类结果
return createClassificationResult(bestMatch.course, bestMatch.chapter, bestMatch.score);
}
/**
*
*/
private double calculateMatchScore(String noteText, Chapter chapter) {
double totalScore = 0.0;
// 1. 关键词匹配分数 (权重: 0.5)
double keywordScore = calculateKeywordScore(noteText, chapter);
totalScore += keywordScore * 0.5;
// 2. 章节标题相似度分数 (权重: 0.3)
double titleScore = calculateTitleSimilarity(noteText, chapter);
totalScore += titleScore * 0.3;
// 3. 别名匹配分数 (权重: 0.2)
double aliasScore = calculateAliasScore(noteText, chapter);
totalScore += aliasScore * 0.2;
return totalScore;
}
/**
*
*/
private double calculateKeywordScore(String noteText, Chapter chapter) {
if (chapter.getKeywords() == null || chapter.getKeywords().isEmpty()) {
return 0.0;
}
return textProcessor.calculateKeywordMatchScore(noteText, chapter.getKeywords());
}
/**
*
*/
private double calculateTitleSimilarity(String noteText, Chapter chapter) {
if (chapter.getChapterTitle() == null || chapter.getChapterTitle().isEmpty()) {
return 0.0;
}
// 提取标题中的关键词
List<String> titleKeywords = textProcessor.extractKeywords(chapter.getChapterTitle(), 5);
if (titleKeywords.isEmpty()) {
return 0.0;
}
return textProcessor.calculateKeywordMatchScore(noteText, titleKeywords);
}
/**
*
*/
private double calculateAliasScore(String noteText, Chapter chapter) {
if (chapter.getAliases() == null || chapter.getAliases().isEmpty()) {
return 0.0;
}
return textProcessor.calculateKeywordMatchScore(noteText, chapter.getAliases());
}
/**
*
*/
private String combineNoteText(Note note) {
StringBuilder text = new StringBuilder();
if (note.getTitle() != null && !note.getTitle().trim().isEmpty()) {
text.append(note.getTitle()).append(" ");
}
if (note.getDescription() != null && !note.getDescription().trim().isEmpty()) {
text.append(note.getDescription()).append(" ");
}
if (note.getOcrText() != null && !note.getOcrText().trim().isEmpty()) {
text.append(note.getOcrText());
}
return text.toString().trim();
}
/**
*
*/
private ClassificationResult createClassificationResult(Course course, Chapter chapter, double score) {
ClassificationResult result = new ClassificationResult();
result.setCourse(course);
result.setChapter(chapter);
result.setConfidenceScore(score);
String reason;
if (score >= HIGH_CONFIDENCE_THRESHOLD) {
reason = "高置信度匹配:关键词和标题高度相关";
} else {
reason = "中等置信度匹配:发现相关关键词";
}
result.setReason(reason);
return result;
}
/**
*
*/
private ClassificationResult createLowConfidenceResult(Course course, Chapter chapter, double score) {
ClassificationResult result = new ClassificationResult();
result.setCourse(course);
result.setChapter(chapter);
result.setConfidenceScore(score);
result.setReason("低置信度匹配:建议用户确认或手动选择");
return result;
}
/**
*
*/
private ClassificationResult createNoMatchResult(String reason) {
ClassificationResult result = new ClassificationResult();
result.setConfidenceScore(0.0);
result.setReason(reason);
return result;
}
/**
* N
*/
public List<ClassificationResult> getTopClassifications(Note note, int topN) {
List<ClassificationResult> results = new ArrayList<>();
if (note == null) {
return results;
}
List<Course> allCourses = courseRepository.getAllCourses();
if (allCourses.isEmpty()) {
return results;
}
String noteText = combineNoteText(note);
String processedText = textProcessor.preprocessText(noteText);
List<ChapterMatch> chapterMatches = new ArrayList<>();
for (Course course : allCourses) {
List<Chapter> chapters = courseRepository.getChaptersByCourseId(course.getCourseId());
for (Chapter chapter : chapters) {
double score = calculateMatchScore(processedText, chapter);
if (score > 0) {
chapterMatches.add(new ChapterMatch(course, chapter, score));
}
}
}
Collections.sort(chapterMatches, (a, b) -> Double.compare(b.score, a.score));
int count = Math.min(topN, chapterMatches.size());
for (int i = 0; i < count; i++) {
ChapterMatch match = chapterMatches.get(i);
results.add(createClassificationResult(match.course, match.chapter, match.score));
}
return results;
}
/**
*
*/
private static class ChapterMatch {
Course course;
Chapter chapter;
double score;
ChapterMatch(Course course, Chapter chapter, double score) {
this.course = course;
this.chapter = chapter;
this.score = score;
}
}
}

@ -0,0 +1,314 @@
package net.micode.myapplication.service;
import android.content.Context;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.micode.myapplication.model.Chapter;
import net.micode.myapplication.model.Course;
import net.micode.myapplication.repository.CourseRepository;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class CourseOutlineImporter {
private static final String TAG = "CourseOutlineImporter";
private CourseRepository courseRepository;
private Gson gson;
public CourseOutlineImporter(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
this.gson = new Gson();
}
/**
* assets
*/
public boolean importCourseOutlineFromAssets(Context context, String fileName) {
try {
InputStream inputStream = context.getAssets().open(fileName);
return importCourseOutline(inputStream);
} catch (IOException e) {
Log.e(TAG, "无法读取assets文件: " + fileName, e);
return false;
}
}
/**
*
*/
public boolean importCourseOutline(InputStream inputStream) {
try {
JsonObject jsonObject = JsonParser.parseReader(new InputStreamReader(inputStream)).getAsJsonObject();
return parseAndImportCourseOutline(jsonObject);
} catch (Exception e) {
Log.e(TAG, "解析JSON失败", e);
return false;
} finally {
try {
inputStream.close();
} catch (IOException e) {
Log.e(TAG, "关闭输入流失败", e);
}
}
}
/**
*
*/
private boolean parseAndImportCourseOutline(JsonObject jsonObject) {
try {
// 解析课程基本信息
Course course = parseCourseInfo(jsonObject);
if (course == null) {
Log.e(TAG, "解析课程信息失败");
return false;
}
// 解析章节信息
List<Chapter> chapters = parseChapters(jsonObject, course.getCourseId());
if (chapters.isEmpty()) {
Log.w(TAG, "没有找到章节信息");
}
// 导入到数据库
return importToDatabase(course, chapters);
} catch (Exception e) {
Log.e(TAG, "解析课程大纲失败", e);
return false;
}
}
/**
*
*/
private Course parseCourseInfo(JsonObject jsonObject) {
try {
String courseId = getStringValue(jsonObject, "id");
String title = getStringValue(jsonObject, "title");
if (courseId == null || title == null) {
Log.e(TAG, "课程ID或标题为空");
return null;
}
Course course = new Course();
course.setCourseId(courseId);
course.setCourseName(title);
// 可选字段
String school = getStringValue(jsonObject, "school");
if (school != null) {
course.setSchool(school);
}
return course;
} catch (Exception e) {
Log.e(TAG, "解析课程基本信息失败", e);
return null;
}
}
/**
*
*/
private List<Chapter> parseChapters(JsonObject jsonObject, String courseId) {
List<Chapter> chapters = new ArrayList<>();
try {
JsonArray nodes = jsonObject.getAsJsonArray("nodes");
if (nodes == null) {
Log.w(TAG, "没有找到nodes数组");
return chapters;
}
int chapterNumber = 1;
for (JsonElement element : nodes) {
if (element.isJsonObject()) {
JsonObject chapterObj = element.getAsJsonObject();
Chapter chapter = parseChapter(chapterObj, courseId, chapterNumber);
if (chapter != null) {
chapters.add(chapter);
chapterNumber++;
}
}
}
} catch (Exception e) {
Log.e(TAG, "解析章节信息失败", e);
}
return chapters;
}
/**
*
*/
private Chapter parseChapter(JsonObject chapterObj, String courseId, int chapterNumber) {
try {
String chapterId = getStringValue(chapterObj, "id");
String chapterTitle = getStringValue(chapterObj, "title");
if (chapterId == null || chapterTitle == null) {
Log.w(TAG, "章节ID或标题为空");
return null;
}
Chapter chapter = new Chapter();
chapter.setChapterId(chapterId);
chapter.setCourseId(courseId);
chapter.setChapterNumber(chapterNumber);
chapter.setChapterTitle(chapterTitle);
// 解析关键词
List<String> keywords = parseKeywords(chapterObj);
for (String keyword : keywords) {
chapter.addKeyword(keyword);
}
// 解析别名
List<String> aliases = parseAliases(chapterObj);
for (String alias : aliases) {
chapter.addAlias(alias);
}
return chapter;
} catch (Exception e) {
Log.e(TAG, "解析单个章节失败", e);
return null;
}
}
/**
*
*/
private List<String> parseKeywords(JsonObject obj) {
List<String> keywords = new ArrayList<>();
try {
JsonArray keywordsArray = obj.getAsJsonArray("keywords");
if (keywordsArray != null) {
for (JsonElement element : keywordsArray) {
if (element.isJsonPrimitive()) {
keywords.add(element.getAsString());
}
}
}
} catch (Exception e) {
Log.w(TAG, "解析关键词失败", e);
}
return keywords;
}
/**
*
*/
private List<String> parseAliases(JsonObject obj) {
List<String> aliases = new ArrayList<>();
try {
JsonArray aliasesArray = obj.getAsJsonArray("aliases");
if (aliasesArray != null) {
for (JsonElement element : aliasesArray) {
if (element.isJsonPrimitive()) {
aliases.add(element.getAsString());
}
}
}
} catch (Exception e) {
Log.w(TAG, "解析别名失败", e);
}
return aliases;
}
/**
*
*/
private String getStringValue(JsonObject obj, String key) {
try {
JsonElement element = obj.get(key);
if (element != null && element.isJsonPrimitive()) {
return element.getAsString();
}
} catch (Exception e) {
Log.w(TAG, "获取字符串值失败: " + key, e);
}
return null;
}
/**
*
*/
private boolean importToDatabase(Course course, List<Chapter> chapters) {
try {
// 检查课程是否已存在
Course existingCourse = courseRepository.getCourseById(course.getCourseId());
if (existingCourse != null) {
Log.i(TAG, "课程已存在,将更新数据: " + course.getCourseId());
// 可以选择更新或跳过
}
// 批量插入课程和章节
courseRepository.insertCourseWithChapters(course, chapters);
boolean success = true; // 方法没有返回值,假设成功
if (success) {
Log.i(TAG, String.format("成功导入课程: %s (%d 个章节)",
course.getCourseName(), chapters.size()));
} else {
Log.e(TAG, "导入课程失败");
}
return success;
} catch (Exception e) {
Log.e(TAG, "导入到数据库失败", e);
return false;
}
}
/**
*
*/
public boolean importMultipleCourseOutlines(Context context, List<String> fileNames) {
boolean allSuccess = true;
int successCount = 0;
for (String fileName : fileNames) {
boolean success = importCourseOutlineFromAssets(context, fileName);
if (success) {
successCount++;
} else {
allSuccess = false;
Log.e(TAG, "导入失败: " + fileName);
}
}
Log.i(TAG, String.format("批量导入完成: %d/%d 成功", successCount, fileNames.size()));
return allSuccess;
}
/**
*
*/
public String getImportStatusMessage(boolean success, String fileName) {
if (success) {
return String.format("成功导入课程大纲: %s", fileName);
} else {
return String.format("导入失败: %s", fileName);
}
}
}

@ -0,0 +1,320 @@
package net.micode.myapplication.service;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.util.Log;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.text.Text;
import com.google.mlkit.vision.text.TextRecognition;
import com.google.mlkit.vision.text.TextRecognizer;
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.rendering.PDFRenderer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class OcrService {
private static final String TAG = "OcrService";
private static final int MAX_IMAGE_SIZE = 2048; // 最大图片尺寸
private static final int PDF_DPI = 150; // PDF渲染DPI
private Context context;
private TextRecognizer textRecognizer;
public OcrService(Context context) {
this.context = context;
// 初始化中文文本识别器
this.textRecognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());
}
/**
* PDF
*/
public String extractTextFromFile(String filePath) throws Exception {
if (filePath == null || filePath.isEmpty()) {
throw new IllegalArgumentException("文件路径不能为空");
}
File file = new File(filePath);
if (!file.exists()) {
throw new IOException("文件不存在: " + filePath);
}
String fileName = file.getName().toLowerCase();
try {
if (fileName.endsWith(".pdf")) {
return extractTextFromPdf(filePath);
} else if (isImageFile(fileName)) {
return extractTextFromImage(filePath);
} else {
throw new UnsupportedOperationException("不支持的文件类型: " + fileName);
}
} catch (Exception e) {
Log.e(TAG, "OCR提取文本失败: " + filePath, e);
throw e;
}
}
/**
*
*/
public String extractTextFromImage(String imagePath) throws Exception {
Bitmap bitmap = loadAndProcessImage(imagePath);
if (bitmap == null) {
throw new IOException("无法加载图片: " + imagePath);
}
try {
InputImage image = InputImage.fromBitmap(bitmap, 0);
return performTextRecognition(image);
} finally {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
/**
* Bitmap
*/
public String extractTextFromBitmap(Bitmap bitmap) throws Exception {
if (bitmap == null) {
throw new IllegalArgumentException("Bitmap不能为空");
}
try {
InputImage image = InputImage.fromBitmap(bitmap, 0);
return performTextRecognition(image);
} catch (Exception e) {
Log.e(TAG, "从Bitmap提取文本失败", e);
throw e;
}
}
/**
* PDF
*/
public String extractTextFromPdf(String pdfPath) throws Exception {
PDDocument document = null;
StringBuilder result = new StringBuilder();
try {
document = PDDocument.load(new File(pdfPath));
PDFRenderer renderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages();
Log.i(TAG, "PDF总页数: " + pageCount);
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
Log.i(TAG, "处理PDF第 " + (pageIndex + 1) + " 页");
// 将PDF页面渲染为Bitmap
Bitmap bitmap = renderer.renderImageWithDPI(pageIndex, PDF_DPI);
if (bitmap != null) {
try {
// 从Bitmap提取文本
String pageText = extractTextFromBitmap(bitmap);
if (pageText != null && !pageText.trim().isEmpty()) {
result.append(pageText);
if (pageIndex < pageCount - 1) {
result.append("\n\n"); // 页间分隔
}
}
} finally {
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
}
return result.toString().trim();
} catch (Exception e) {
Log.e(TAG, "PDF处理失败: " + pdfPath, e);
throw e;
} finally {
if (document != null) {
try {
document.close();
} catch (IOException e) {
Log.e(TAG, "关闭PDF文档失败", e);
}
}
}
}
/**
*
*/
private String performTextRecognition(InputImage image) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<String> result = new AtomicReference<>("");
final AtomicReference<Exception> error = new AtomicReference<>(null);
textRecognizer.process(image)
.addOnSuccessListener(visionText -> {
try {
String extractedText = processVisionText(visionText);
result.set(extractedText);
} catch (Exception e) {
error.set(e);
} finally {
latch.countDown();
}
})
.addOnFailureListener(e -> {
error.set(e);
latch.countDown();
});
// 等待识别完成最多30秒
if (!latch.await(30, TimeUnit.SECONDS)) {
throw new Exception("文本识别超时");
}
if (error.get() != null) {
throw error.get();
}
return result.get();
}
/**
* Vision Text
*/
private String processVisionText(Text visionText) {
StringBuilder result = new StringBuilder();
for (Text.TextBlock block : visionText.getTextBlocks()) {
for (Text.Line line : block.getLines()) {
result.append(line.getText()).append("\n");
}
result.append("\n"); // 块间分隔
}
return result.toString().trim();
}
/**
*
*/
private Bitmap loadAndProcessImage(String imagePath) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
// 计算缩放比例
int scaleFactor = calculateInSampleSize(options, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE);
options.inJustDecodeBounds = false;
options.inSampleSize = scaleFactor;
Bitmap bitmap = BitmapFactory.decodeFile(imagePath, options);
if (bitmap == null) {
return null;
}
// 处理图片方向
bitmap = handleImageOrientation(bitmap, imagePath);
return bitmap;
}
/**
*
*/
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
*
*/
private Bitmap handleImageOrientation(Bitmap bitmap, String imagePath) {
try {
ExifInterface exif = new ExifInterface(imagePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
Matrix matrix = new Matrix();
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.postRotate(90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.postRotate(180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.postRotate(270);
break;
default:
return bitmap;
}
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, true);
// 回收原始bitmap
if (rotatedBitmap != bitmap && !bitmap.isRecycled()) {
bitmap.recycle();
}
return rotatedBitmap;
} catch (IOException e) {
Log.e(TAG, "处理图片方向失败", e);
return bitmap;
}
}
/**
*
*/
private boolean isImageFile(String fileName) {
String[] imageExtensions = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".gif"};
String lowerFileName = fileName.toLowerCase();
for (String ext : imageExtensions) {
if (lowerFileName.endsWith(ext)) {
return true;
}
}
return false;
}
/**
* OCR
*/
public void close() {
if (textRecognizer != null) {
textRecognizer.close();
}
}
}

@ -0,0 +1,218 @@
package net.micode.myapplication.service;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Pattern;
public class TextProcessor {
// 常见停用词列表
private static final String[] STOP_WORDS = {
"的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也", "很", "到", "说", "要", "去", "你", "会", "着", "没有", "看", "好", "自己", "这"
};
// 标点符号正则表达式
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("[\\p{Punct}\\s]+");
/**
*
*/
public String preprocessText(String text) {
if (text == null || text.trim().isEmpty()) {
return "";
}
// 去除标点符号和特殊字符
text = PUNCTUATION_PATTERN.matcher(text).replaceAll(" ");
// 去除多余空格
text = text.replaceAll("\\s+", " ").trim();
return text.toLowerCase();
}
/**
* 使HanLP
*/
public List<String> extractKeywords(String text, int maxKeywords) {
List<String> keywords = new ArrayList<>();
if (text == null || text.trim().isEmpty()) {
return keywords;
}
try {
// 使用HanLP进行分词
List<Term> terms = HanLP.segment(text);
// 统计词频
Map<String, Integer> wordFrequency = new HashMap<>();
for (Term term : terms) {
String word = term.word.trim();
if (word.length() >= 2 && !isStopWord(word)) { // 只保留长度>=2的词
Integer count = wordFrequency.get(word);
wordFrequency.put(word, count == null ? 1 : count + 1);
}
}
// 按词频排序并返回前maxKeywords个词
List<Map.Entry<String, Integer>> entries = new ArrayList<>(wordFrequency.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> a, Map.Entry<String, Integer> b) {
return b.getValue().compareTo(a.getValue());
}
});
for (int i = 0; i < Math.min(maxKeywords, entries.size()); i++) {
keywords.add(entries.get(i).getKey());
}
} catch (Exception e) {
e.printStackTrace();
// 如果HanLP失败使用简单的空格分割
String[] words = text.split("\\s+");
for (String word : words) {
if (word.length() >= 2 && !isStopWord(word)) {
keywords.add(word);
if (keywords.size() >= maxKeywords) {
break;
}
}
}
}
return keywords;
}
/**
*
*/
public Map<String, Object> extractTextFeatures(String text) {
Map<String, Object> features = new HashMap<>();
if (text == null || text.trim().isEmpty()) {
return features;
}
String processedText = preprocessText(text);
List<String> keywords = extractKeywords(processedText, 20);
// 计算词频
Map<String, Integer> wordFrequency = new HashMap<>();
String[] words = processedText.split("\\s+");
for (String word : words) {
if (word.length() >= 2 && !isStopWord(word)) {
Integer count = wordFrequency.get(word);
wordFrequency.put(word, count == null ? 1 : count + 1);
}
}
features.put("text", processedText);
features.put("keywords", keywords);
features.put("wordFrequency", wordFrequency);
features.put("wordCount", words.length);
features.put("uniqueWordCount", wordFrequency.size());
return features;
}
/**
*
*/
public double calculateTextSimilarity(String text1, String text2) {
if (text1 == null || text2 == null || text1.isEmpty() || text2.isEmpty()) {
return 0.0;
}
List<String> keywords1 = extractKeywords(preprocessText(text1), 10);
List<String> keywords2 = extractKeywords(preprocessText(text2), 10);
if (keywords1.isEmpty() || keywords2.isEmpty()) {
return 0.0;
}
// 计算交集
List<String> intersection = new ArrayList<>(keywords1);
intersection.retainAll(keywords2);
// 计算并集
List<String> union = new ArrayList<>(keywords1);
for (String keyword : keywords2) {
if (!union.contains(keyword)) {
union.add(keyword);
}
}
// Jaccard相似度
return (double) intersection.size() / union.size();
}
/**
*
*/
public double calculateKeywordMatchScore(String text, List<String> targetKeywords) {
if (text == null || targetKeywords == null || targetKeywords.isEmpty()) {
return 0.0;
}
String processedText = preprocessText(text);
List<String> textKeywords = extractKeywords(processedText, 50); // 提取更多关键词用于匹配
int matchCount = 0;
for (String targetKeyword : targetKeywords) {
String processedTarget = preprocessText(targetKeyword);
if (textKeywords.contains(processedTarget) || processedText.contains(processedTarget)) {
matchCount++;
}
}
return (double) matchCount / targetKeywords.size();
}
/**
*
*/
private boolean isStopWord(String word) {
if (word == null || word.trim().isEmpty()) {
return true;
}
for (String stopWord : STOP_WORDS) {
if (stopWord.equals(word)) {
return true;
}
}
return false;
}
/**
*
*/
public List<Integer> extractNumbers(String text) {
List<Integer> numbers = new ArrayList<>();
if (text == null || text.trim().isEmpty()) {
return numbers;
}
Pattern pattern = Pattern.compile("\\d+");
java.util.regex.Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
try {
numbers.add(Integer.parseInt(matcher.group()));
} catch (NumberFormatException e) {
// 忽略解析错误的数字
}
}
return numbers;
}
}

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- 背景圆角卡片 -->
<path android:pathData="M18,18h72v72h-72z" android:fillColor="#FFFFFF"/>
<!-- 主笔记纸张 -->
<path android:pathData="M24,24h60a8,8 0 0 1 8,8v44a8,8 0 0 1 -8,8H24a8,8 0 0 1 -8,-8V32a8,8 0 0 1 8,-8z">
<aapt:attr name="android:fillColor">
<gradient android:type="linear" android:startX="24" android:startY="24" android:endX="84" android:endY="84">
<item android:offset="0" android:color="#F0F8FF"/>
<item android:offset="1" android:color="#E6F2FF"/>
</gradient>
</aapt:attr>
</path>
<!-- 纸张左侧装订线 -->
<path android:pathData="M32,32h2v48h-2z" android:fillColor="#4A90E2" android:alpha="0.3"/>
<!-- 横线 -->
<path android:pathData="M40,42h36" android:strokeColor="#4A90E2" android:strokeWidth="2" android:alpha="0.4"/>
<path android:pathData="M40,50h36" android:strokeColor="#4A90E2" android:strokeWidth="2" android:alpha="0.4"/>
<path android:pathData="M40,58h36" android:strokeColor="#4A90E2" android:strokeWidth="2" android:alpha="0.4"/>
<path android:pathData="M40,66h36" android:strokeColor="#4A90E2" android:strokeWidth="2" android:alpha="0.4"/>
<!-- 铅笔图标 -->
<path android:pathData="M68,38l12,12l-6,6l-12,-12z" android:fillColor="#FFD93D"/>
<path android:pathData="M74,44l-8,-8l4,-4l8,8z" android:fillColor="#FF6B6B"/>
<!-- 铅笔芯 -->
<path android:pathData="M66,36l2,2" android:strokeColor="#333333" android:strokeWidth="2"/>
<!-- 笔记标题高亮 -->
<path android:pathData="M40,38h20" android:strokeColor="#4A90E2" android:strokeWidth="4"/>
</vector>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/bg_card"/>
<corners android:radius="12dp"/>
<stroke
android:width="1dp"
android:color="@color/divider"/>
</shape>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>

@ -0,0 +1,10 @@
<?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="#FFFFFF"
android:pathData="M0,0h108v108h-108z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/text_hint"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/text_hint"
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1v-1.1c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM4,18h16v2L4,20z"/>
</vector>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/bg_input"/>
<corners android:radius="12dp"/>
<stroke
android:width="1dp"
android:color="@color/divider"/>
</shape>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/bg_card"/>
<corners android:radius="8dp"/>
<stroke
android:width="1dp"
android:color="@color/divider"/>
</shape>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/accent"/>
<corners android:radius="12dp"/>
</shape>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/primary"/>
<corners android:radius="12dp"/>
</shape>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/success"/>
<corners android:radius="12dp"/>
</shape>

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/primary"
android:fillViewport="true"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:background="@color/bg_card"
android:padding="32dp"
android:elevation="8dp"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="16dp">
<TextView
android:text="课程笔记共享系统"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<TextView
android:text="知识共享,学习无界"
android:textSize="16sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="32dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"/>
<TextView
android:text="用户名"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/usernameInput"
android:hint="请输入用户名"
android:layout_marginBottom="20dp"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:inputType="textPersonName"
android:drawableLeft="@drawable/ic_person"
android:drawablePadding="12dp"
android:textSize="16sp"
android:drawableTint="@color/text_hint"/>
<TextView
android:text="密码"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/passwordInput"
android:hint="请输入密码"
android:layout_marginBottom="32dp"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:inputType="textPassword"
android:drawableLeft="@drawable/ic_lock"
android:drawablePadding="12dp"
android:textSize="16sp"
android:drawableTint="@color/text_hint"/>
<Button
android:id="@+id/loginBtn"
android:text="登录"
android:layout_width="match_parent"
android:layout_height="56dp"
android:backgroundTint="@color/primary"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
android:elevation="4dp"
android:layout_marginBottom="24dp"/>
<TextView
android:id="@+id/registerLink"
android:text="还没有账号?立即注册"
android:textColor="@color/primary"
android:textSize="15sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="8dp"/>
</LinearLayout>
</ScrollView>

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/bg_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 顶部栏 -->
<LinearLayout
android:background="@color/primary"
android:padding="20dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp">
<TextView
android:id="@+id/welcomeText"
android:text="欢迎,张三"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:id="@+id/logoutBtn"
android:text="退出登录"
android:textColor="@android:color/white"
android:backgroundTint="@color/primary_dark"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textSize="14sp"/>
</LinearLayout>
<!-- 搜索栏 -->
<LinearLayout
android:orientation="horizontal"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/searchBtn"
android:layout_width="11dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:backgroundTint="@color/primary"
android:drawableTop="@drawable/ic_search"
android:drawablePadding="4dp"
android:text="搜索"
android:textColor="@android:color/white"
android:textSize="16sp" />
<Button
android:id="@+id/uploadBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:backgroundTint="@color/accent"
android:drawableTop="@drawable/ic_upload"
android:drawablePadding="4dp"
android:text="上传"
android:textColor="@android:color/white"
android:textSize="16sp" />
<Button
android:id="@+id/userCenterBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:backgroundTint="@color/primary"
android:drawableTop="@drawable/ic_person_outline"
android:drawablePadding="4dp"
android:text="我的"
android:textColor="@android:color/white"
android:textSize="16sp" />
</LinearLayout>
<!-- 推荐笔记 -->
<LinearLayout
android:orientation="vertical"
android:padding="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<TextView
android:text="为您推荐的笔记"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/recommendedRefreshBtn"
android:text="刷新推荐"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:backgroundTint="@color/primary_light"
android:textColor="@color/primary"
android:textSize="14sp"/>
</LinearLayout>
<!-- 推荐渲染容器 -->
<LinearLayout
android:id="@+id/recommendedNotesContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
</LinearLayout>
<!-- 笔记列表 -->
<LinearLayout
android:orientation="vertical"
android:padding="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="最新笔记"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<!-- 动态渲染容器 -->
<LinearLayout
android:id="@+id/latestNotesContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,261 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/bg_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 顶部导航栏 -->
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<TextView
android:text="笔记详情"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="@color/text_primary"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/backBtn"
android:text="返回"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:backgroundTint="@color/primary_dark"
android:textColor="@android:color/white"
android:textSize="14sp"
android:paddingLeft="16dp"
android:paddingRight="16dp"/>
</LinearLayout>
<!-- 笔记内容卡片 -->
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<TextView
android:id="@+id/noteTitle"
android:text="笔记标题"
android:textStyle="bold"
android:textSize="22sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/noteMeta"
android:text="元信息"
android:textColor="@color/text_secondary"
android:textSize="14sp"
android:layout_marginBottom="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/noteBody"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:lineSpacingMultiplier="1.6"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 编辑模式输入区域 -->
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="20dp"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<TextView
android:text="编辑笔记"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="标题"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/editNoteTitle"
android:hint="编辑标题"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:textSize="16sp"
android:layout_marginBottom="16dp"/>
<TextView
android:text="内容"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/editNoteBody"
android:hint="编辑内容"
android:minLines="8"
android:gravity="top"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:lineSpacingMultiplier="1.4"/>
</LinearLayout>
<!-- 操作按钮区域 -->
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<Button
android:id="@+id/downloadBtn"
android:text="📥 下载笔记"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:backgroundTint="@color/primary"
android:textColor="@android:color/white"
android:textSize="15sp"
android:layout_marginRight="8dp"/>
<Button
android:id="@+id/likeBtn"
android:text="👍 点赞"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:backgroundTint="@color/accent"
android:textColor="@android:color/white"
android:textSize="15sp"
android:layout_marginRight="8dp"/>
<TextView
android:id="@+id/likeCount"
android:text="👍 0"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:gravity="center"
android:layout_width="80dp"
android:layout_height="48dp"
android:background="@drawable/input_background"
android:padding="8dp"/>
</LinearLayout>
<!-- 编辑操作按钮区域(仅作者可见) -->
<LinearLayout
android:id="@+id/editActions"
android:orientation="horizontal"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/startEditBtn"
android:text="编辑笔记"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:backgroundTint="@color/warning"
android:textColor="@android:color/white"
android:textSize="15sp"
android:layout_marginRight="8dp"/>
<Button
android:id="@+id/saveEditBtn"
android:text="保存修改"
android:visibility="gone"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:backgroundTint="@color/success"
android:textColor="@android:color/white"
android:textSize="15sp"
android:layout_marginRight="8dp"/>
<Button
android:id="@+id/deleteNoteBtn"
android:text="删除笔记"
android:visibility="gone"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:backgroundTint="@color/danger"
android:textColor="@android:color/white"
android:textSize="15sp"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="评论"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/commentListContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/commentInput"
android:hint="发表你的看法"
android:background="@drawable/input_background"
android:padding="12dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"/>
<Button
android:id="@+id/commentSubmitBtn"
android:text="发布"
android:layout_width="100dp"
android:layout_height="48dp"
android:backgroundTint="@color/success"
android:textColor="@android:color/white"
android:textSize="14sp"
android:layout_marginLeft="8dp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="编辑笔记"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="#333"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/editNoteTitle"
android:hint="标题"
android:layout_marginTop="12dp"
android:layout_width="match_parent"
android:layout_height="48dp"/>
<EditText
android:id="@+id/editNoteContent"
android:hint="内容"
android:minLines="6"
android:gravity="top"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_marginTop="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/saveNoteBtn"
android:text="保存"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:backgroundTint="#4CAF50"
android:textColor="@android:color/white"/>
<Space android:layout_width="8dp" android:layout_height="1dp"/>
<Button
android:id="@+id/cancelEditBtn"
android:text="取消"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"/>
</LinearLayout>
<Button
android:id="@+id/deleteNoteBtn"
android:text="删除笔记"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:backgroundTint="#DC3545"
android:textColor="@android:color/white"/>
</LinearLayout>
</ScrollView>

@ -0,0 +1,272 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="上传笔记"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_marginBottom="24dp"
android:gravity="center" />
<!-- 文件选择区域 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择文件"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<Button
android:id="@+id/btn_select_file"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="选择图片或PDF文件"
android:drawableLeft="@android:drawable/ic_menu_gallery"
android:drawablePadding="8dp"
android:backgroundTint="#2196F3"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/tv_selected_file"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="未选择文件"
android:textSize="14sp"
android:textColor="#666666"
android:layout_marginTop="8dp"
android:padding="8dp"
android:background="#F0F0F0" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 笔记信息区域 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="笔记信息"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="12dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:hint="笔记标题">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_note_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="笔记描述">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_note_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:minLines="3"
android:maxLines="5" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 分类推荐区域 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_classification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自动分类推荐"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="12dp" />
<TextView
android:id="@+id/tv_classification_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="正在分析..."
android:textSize="14sp"
android:textColor="#333333"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tv_classification_confidence"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textSize="12sp"
android:textColor="#666666"
android:layout_marginBottom="12dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_accept_classification"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="接受推荐"
android:layout_marginEnd="8dp"
android:backgroundTint="#4CAF50"
android:textColor="@android:color/white" />
<Button
android:id="@+id/btn_reject_classification"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="手动选择"
android:backgroundTint="#FF9800"
android:textColor="@android:color/white" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 手动选择区域 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_manual_selection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="手动选择课程和章节"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="12dp" />
<Spinner
android:id="@+id/spinner_course"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp" />
<Spinner
android:id="@+id/spinner_chapter"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 操作按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="24dp">
<Button
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="取消"
android:layout_marginEnd="8dp"
android:backgroundTint="#9E9E9E"
android:textColor="@android:color/white" />
<Button
android:id="@+id/btn_upload"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="上传笔记"
android:layout_marginStart="8dp"
android:backgroundTint="#2196F3"
android:textColor="@android:color/white" />
</LinearLayout>
<!-- 进度条 -->
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:layout_marginTop="16dp" />
</LinearLayout>
</ScrollView>

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#FF667EEA"
android:fillViewport="true"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:background="@android:color/white"
android:padding="24dp"
android:elevation="6dp"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:text="课程笔记共享系统 - 注册"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/usernameInput"
android:hint="请输入用户名3-20个字符"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="12dp"
android:inputType="textPersonName"/>
<EditText
android:id="@+id/passwordInput"
android:hint="请输入密码至少6位"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="12dp"
android:inputType="textPassword"/>
<EditText
android:id="@+id/confirmInput"
android:hint="请再次输入密码"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:inputType="textPassword"/>
<TextView
android:text="年级:"
android:textColor="#333333"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/gradeSpinner"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="12dp"/>
<!-- 新增:专业 -->
<AutoCompleteTextView
android:id="@+id/majorInput"
android:hint="请输入专业(支持模糊搜索)"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:inputType="text"
android:completionThreshold="1"/>
<Button
android:id="@+id/registerBtn"
android:text="注册"
android:layout_width="match_parent"
android:layout_height="48dp"
android:backgroundTint="#667eea"
android:textColor="@android:color/white"/>
</LinearLayout>
</ScrollView>

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/bg_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 顶部导航栏 -->
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<TextView
android:text="搜索笔记"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="@color/text_primary"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/backBtn"
android:text="返回"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:backgroundTint="@color/primary_dark"
android:textColor="@android:color/white"
android:textSize="14sp"
android:paddingLeft="16dp"
android:paddingRight="16dp"/>
</LinearLayout>
<!-- 搜索输入区域 -->
<LinearLayout
android:orientation="horizontal"
android:background="@drawable/card_background"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<EditText
android:id="@+id/searchInput"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:background="@drawable/input_background"
android:drawableLeft="@drawable/ic_search"
android:drawablePadding="12dp"
android:drawableTint="@color/text_hint"
android:hint="搜索笔记"
android:padding="16dp"
android:textSize="16sp" />
<Button
android:id="@+id/searchBtn"
android:text="搜索"
android:layout_width="80dp"
android:layout_height="56dp"
android:backgroundTint="@color/primary"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"/>
</LinearLayout>
<!-- 搜索结果统计 -->
<TextView
android:id="@+id/resultsInfo"
android:text=""
android:textColor="@color/text_secondary"
android:textSize="14sp"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- 搜索结果容器 -->
<LinearLayout
android:id="@+id/resultsContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/bg_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 顶部标题栏 -->
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<TextView
android:text="上传学习笔记"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="@color/text_primary"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/backBtn"
android:text="返回主页"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:backgroundTint="@color/primary_dark"
android:textColor="@android:color/white"
android:textSize="14sp"
android:paddingLeft="16dp"
android:paddingRight="16dp"/>
</LinearLayout>
<!-- 文件选择与识别区域 -->
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<TextView
android:text="选择笔记文件"
android:textSize="16sp"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/selectFileBtn"
android:text="选择图片文件"
android:layout_width="match_parent"
android:layout_height="56dp"
android:backgroundTint="@color/accent"
android:textColor="@android:color/white"
android:textSize="16sp"
android:drawableLeft="@drawable/ic_image"
android:drawablePadding="12dp"
android:layout_marginBottom="16dp"/>
<!-- 文件预览(图片时展示) -->
<ImageView
android:id="@+id/previewImage"
android:contentDescription="文件预览"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:background="@drawable/input_background"
android:layout_marginBottom="12dp"
android:visibility="gone"/>
<!-- 文件信息提示(非图片文件时显示) -->
<TextView
android:id="@+id/fileInfoText"
android:text=""
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_secondary"
android:textSize="14sp"/>
<!-- 动态文件列表 -->
<LinearLayout
android:id="@+id/fileListContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 笔记信息填写区域 -->
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="笔记信息"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:layout_marginBottom="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- 自动分类推荐 -->
<TextView
android:id="@+id/classificationResultText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="12dp"
android:background="@color/primary_light"
android:textColor="@color/primary"
android:textSize="14sp"
android:text="智能分类推荐将显示在这里"
android:visibility="gone" />
<TextView
android:text="笔记标题"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/noteTitle"
android:hint="请输入笔记标题"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:textSize="16sp"
android:layout_marginBottom="16dp"/>
<TextView
android:text="科目分类"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/subjectSpinner"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@drawable/spinner_background"
android:padding="16dp"
android:layout_marginBottom="16dp"/>
<TextView
android:text="章节"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/chapterSpinner"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@drawable/spinner_background"
android:padding="16dp"
android:layout_marginBottom="16dp"/>
<TextView
android:text="笔记内容"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/contentInput"
android:hint="粘贴或自动填充OCR识别的全文内容"
android:minLines="6"
android:gravity="top"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:lineSpacingMultiplier="1.4"
android:layout_marginBottom="16dp"/>
<TextView
android:text="关键标签"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/tagsInput"
android:hint="请输入关键词,用逗号分隔"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:textSize="16sp"
android:layout_marginBottom="20dp"/>
<!-- 上传进度 -->
<ProgressBar
android:id="@+id/progressBar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:max="100"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/progressText"
android:text="上传中... 0%"
android:visibility="gone"
android:textColor="@color/text_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/submitBtn"
android:text="发布笔记"
android:layout_width="match_parent"
android:layout_height="56dp"
android:backgroundTint="@color/success"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
android:elevation="4dp"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/bg_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 顶部导航栏 -->
<LinearLayout
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<TextView
android:text="用户中心"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="@color/text_primary"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/backBtn"
android:text="返回主页"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:backgroundTint="@color/primary_dark"
android:textColor="@android:color/white"
android:textSize="14sp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:elevation="2dp"/>
</LinearLayout>
<!-- 概览统计卡片 -->
<LinearLayout
android:orientation="horizontal"
android:weightSum="3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<LinearLayout
android:orientation="vertical"
android:layout_weight="1"
android:padding="20dp"
android:background="@drawable/stat_card_primary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="4dp"
android:layout_margin="4dp">
<TextView
android:text="上传笔记"
android:textColor="@android:color/white"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/statUploaded"
android:text="0"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<Space android:layout_width="12dp" android:layout_height="1dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_weight="1"
android:padding="20dp"
android:background="@drawable/stat_card_accent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="4dp"
android:layout_margin="4dp">
<TextView
android:text="获得点赞"
android:textColor="@android:color/white"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/statLikes"
android:text="0"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<Space android:layout_width="12dp" android:layout_height="1dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_weight="1"
android:padding="20dp"
android:background="@drawable/stat_card_success"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="4dp"
android:layout_margin="4dp">
<TextView
android:text="积分"
android:textColor="@android:color/white"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/statPoints"
android:text="0"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
<!-- 个人信息卡片 -->
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="24dp"
android:layout_marginBottom="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="个人信息"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="用户名"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/usernameInput"
android:hint="用户名"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:textSize="16sp"
android:layout_marginBottom="16dp"/>
<TextView
android:text="专业信息"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/majorNameTv"
android:text="专业:未设置"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="16dp"/>
<TextView
android:text="年级信息"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/gradeNameTv"
android:text="年级:未设置"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:background="@drawable/input_background"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="56dp"/>
</LinearLayout>
<!-- 我的笔记卡片 -->
<LinearLayout
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="我的笔记"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/myNotesContainer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

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

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

@ -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="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save