diff --git a/doc/ruanjian/.gitignore b/doc/ruanjian/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/doc/ruanjian/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/doc/ruanjian/.idea/.gitignore b/doc/ruanjian/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/doc/ruanjian/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/doc/ruanjian/.idea/compiler.xml b/doc/ruanjian/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/doc/ruanjian/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/.idea/deploymentTargetSelector.xml b/doc/ruanjian/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..8b6e693
--- /dev/null
+++ b/doc/ruanjian/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/.idea/gradle.xml b/doc/ruanjian/.idea/gradle.xml
new file mode 100644
index 0000000..7b3006b
--- /dev/null
+++ b/doc/ruanjian/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/.idea/kotlinc.xml b/doc/ruanjian/.idea/kotlinc.xml
new file mode 100644
index 0000000..0fc3113
--- /dev/null
+++ b/doc/ruanjian/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/.idea/migrations.xml b/doc/ruanjian/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/doc/ruanjian/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/.idea/misc.xml b/doc/ruanjian/.idea/misc.xml
new file mode 100644
index 0000000..b2c751a
--- /dev/null
+++ b/doc/ruanjian/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/.idea/runConfigurations.xml b/doc/ruanjian/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/doc/ruanjian/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/.gitignore b/doc/ruanjian/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/doc/ruanjian/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/doc/ruanjian/app/build.gradle.kts b/doc/ruanjian/app/build.gradle.kts
new file mode 100644
index 0000000..af39c2b
--- /dev/null
+++ b/doc/ruanjian/app/build.gradle.kts
@@ -0,0 +1,35 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+}
+
+android {
+ namespace = "com.example.ruanjian"
+ compileSdk = 33
+
+ defaultConfig {
+ applicationId = "com.example.ruanjian"
+ minSdk = 21
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+}
+
+dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10")
+ implementation("androidx.core:core-ktx:1.10.1")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.9.0")
+ implementation("androidx.recyclerview:recyclerview:1.3.0")
+}
diff --git a/doc/ruanjian/app/proguard-rules.pro b/doc/ruanjian/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/doc/ruanjian/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/androidTest/java/com/example/ruanjian/ExampleInstrumentedTest.java b/doc/ruanjian/app/src/androidTest/java/com/example/ruanjian/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..bcf196f
--- /dev/null
+++ b/doc/ruanjian/app/src/androidTest/java/com/example/ruanjian/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.ruanjian;
+
+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 Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.example.ruanjian", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/AndroidManifest.xml b/doc/ruanjian/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8fa65ce
--- /dev/null
+++ b/doc/ruanjian/app/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/ic_launcher-playstore.png b/doc/ruanjian/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..7c74649
Binary files /dev/null and b/doc/ruanjian/app/src/main/ic_launcher-playstore.png differ
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/AddTaskActivity.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/AddTaskActivity.java
new file mode 100644
index 0000000..266e6d1
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/AddTaskActivity.java
@@ -0,0 +1,32 @@
+package com.example.ruanjian;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class AddTaskActivity extends AppCompatActivity {
+
+ private EditText editTextTask;
+ private Button btnSave;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_add_task);
+
+ editTextTask = findViewById(R.id.editTextTask);
+ btnSave = findViewById(R.id.btnSave);
+
+ btnSave.setOnClickListener(v -> {
+ String content = editTextTask.getText().toString().trim();
+ if (!content.isEmpty()) {
+ // 新建 Task 对象,默认优先级为 "中"
+ Task task = new Task(content, "中");
+ MainActivity.taskList.add(task); // 添加 Task 对象
+ }
+ finish(); // 关闭当前页面,返回主界面
+ });
+ }
+}
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/DetailActivity.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/DetailActivity.java
new file mode 100644
index 0000000..e0e2605
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/DetailActivity.java
@@ -0,0 +1,201 @@
+package com.example.ruanjian;
+
+import android.app.AlarmManager;
+import android.app.DatePickerDialog;
+import android.app.PendingIntent;
+import android.app.TimePickerDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ArrayAdapter;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+public class DetailActivity extends AppCompatActivity {
+
+ private EditText editTextTaskDetail;
+ private Spinner spinnerPriority;
+ private Button buttonSave, buttonDelete, buttonCancel, btnSetDeadline;
+ private TextView tvDeadline;
+ private int taskIndex = -1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_detail);
+
+ // 初始化原有视图
+ editTextTaskDetail = findViewById(R.id.editTextTaskDetail);
+ spinnerPriority = findViewById(R.id.spinnerPriority);
+ buttonSave = findViewById(R.id.buttonSave);
+ buttonDelete = findViewById(R.id.buttonDelete);
+ buttonCancel = findViewById(R.id.buttonCancel);
+
+ // 初始化新增视图
+ btnSetDeadline = findViewById(R.id.btnSetDeadline);
+ tvDeadline = findViewById(R.id.tvDeadline);
+
+ // 原有优先级下拉列表设置
+ ArrayAdapter priorityAdapter = new ArrayAdapter<>(
+ this,
+ android.R.layout.simple_spinner_item,
+ new String[]{"高", "中", "低"});
+ priorityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinnerPriority.setAdapter(priorityAdapter);
+
+ // 原有任务数据加载
+ taskIndex = getIntent().getIntExtra("index", -1);
+ if (taskIndex != -1 && taskIndex < MainActivity.taskList.size()) {
+ Task task = MainActivity.taskList.get(taskIndex);
+ editTextTaskDetail.setText(task.getContent());
+
+ // 原有优先级设置
+ int spinnerPos = priorityAdapter.getPosition(task.getPriority());
+ spinnerPriority.setSelection(spinnerPos);
+
+ // 新增截止时间显示
+ if (task.getDeadline() != null) {
+ tvDeadline.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(task.getDeadline()));
+ }
+ } else {
+ Toast.makeText(this, "任务不存在", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ // 原有按钮点击事件
+ buttonSave.setOnClickListener(v -> {
+ String newContent = editTextTaskDetail.getText().toString().trim();
+ String newPriority = spinnerPriority.getSelectedItem().toString();
+
+ if (!newContent.isEmpty()) {
+ Task originalTask = MainActivity.taskList.get(taskIndex);
+ Task updatedTask = new Task(newContent, newPriority);
+ // 保留原有创建时间和完成状态
+ updatedTask.setCreateTime(originalTask.getCreateTime());
+ updatedTask.setCompleted(originalTask.isCompleted());
+ // 保留新增的截止时间和提醒设置
+ updatedTask.setDeadline(originalTask.getDeadline());
+ updatedTask.setHasReminder(originalTask.hasReminder());
+
+ MainActivity.taskList.set(taskIndex, updatedTask);
+ Toast.makeText(DetailActivity.this, "任务已保存", Toast.LENGTH_SHORT).show();
+ finish();
+ } else {
+ Toast.makeText(DetailActivity.this, "任务内容不能为空", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ buttonDelete.setOnClickListener(v -> {
+ MainActivity.taskList.remove(taskIndex);
+ Toast.makeText(DetailActivity.this, "任务已删除", Toast.LENGTH_SHORT).show();
+ finish();
+ });
+
+ buttonCancel.setOnClickListener(v -> {
+ Toast.makeText(DetailActivity.this, "已取消,未保存任务", Toast.LENGTH_SHORT).show();
+ finish();
+ });
+
+ // 新增截止时间设置按钮
+ btnSetDeadline.setOnClickListener(v -> showDateTimePicker());
+ }
+
+ // 新增方法:显示日期时间选择器
+ private void showDateTimePicker() {
+ final Calendar calendar = Calendar.getInstance();
+ Task currentTask = MainActivity.taskList.get(taskIndex);
+
+ // 如果已有截止时间,使用该时间初始化
+ if (currentTask.getDeadline() != null) {
+ calendar.setTime(currentTask.getDeadline());
+ }
+
+ // 先选择日期
+ new DatePickerDialog(
+ this,
+ (view, year, month, day) -> {
+ calendar.set(year, month, day);
+ // 再选择时间
+ new TimePickerDialog(
+ this,
+ (view1, hour, minute) -> {
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ calendar.set(Calendar.MINUTE, minute);
+
+ // 设置截止时间
+ currentTask.setDeadline(calendar.getTime());
+ tvDeadline.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(calendar.getTime()));
+
+ // 询问是否设置提醒
+ showReminderDialog(currentTask);
+ },
+ calendar.get(Calendar.HOUR_OF_DAY),
+ calendar.get(Calendar.MINUTE),
+ true)
+ .show();
+ },
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH))
+ .show();
+ }
+
+ // 新增方法:显示提醒设置对话框
+ private void showReminderDialog(Task task) {
+ new AlertDialog.Builder(this)
+ .setTitle("设置提醒")
+ .setMessage("要在截止时间前15分钟提醒吗?")
+ .setPositiveButton("是", (dialog, which) -> {
+ task.setHasReminder(true);
+ scheduleReminder(task);
+ Toast.makeText(this, "已设置提醒", Toast.LENGTH_SHORT).show();
+ })
+ .setNegativeButton("否", (dialog, which) -> {
+ task.setHasReminder(false);
+ cancelReminder(task);
+ })
+ .show();
+ }
+
+ // 新增方法:设置提醒
+ private void scheduleReminder(Task task) {
+ if (task.getDeadline() == null) return;
+
+ AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
+ Intent intent = new Intent(this, ReminderReceiver.class);
+ intent.putExtra("task_content", task.getContent());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this,
+ task.getContent().hashCode(),
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ // 截止时间前15分钟提醒
+ long reminderTime = task.getDeadline().getTime() - (15 * 60 * 1000);
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
+ }
+
+ // 新增方法:取消提醒
+ private void cancelReminder(Task task) {
+ AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
+ Intent intent = new Intent(this, ReminderReceiver.class);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this,
+ task.getContent().hashCode(),
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ alarmManager.cancel(pendingIntent);
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/MainActivity.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/MainActivity.java
new file mode 100644
index 0000000..1c6a15f
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/MainActivity.java
@@ -0,0 +1,172 @@
+package com.example.ruanjian;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.ArrayAdapter;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+
+public class MainActivity extends AppCompatActivity {
+
+ private RecyclerView recyclerView;
+ private TaskAdapter adapter;
+ private Button btnAddTask, btnSort;
+ private EditText editTextSearch;
+ private int currentSortType = 0; // 0=默认,1=优先级,2=时间
+
+ public static ArrayList taskList = new ArrayList<>();
+ private ArrayList filteredList = new ArrayList<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ recyclerView = findViewById(R.id.recyclerView);
+ btnAddTask = findViewById(R.id.btnAddTask);
+ btnSort = findViewById(R.id.btnSort);
+ editTextSearch = findViewById(R.id.editTextSearch);
+
+ adapter = new TaskAdapter(filteredList, position -> {
+ Task clickedTask = filteredList.get(position);
+ int realIndex = taskList.indexOf(clickedTask);
+ Intent intent = new Intent(MainActivity.this, DetailActivity.class);
+ intent.putExtra("index", realIndex);
+ startActivity(intent);
+ });
+
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setAdapter(adapter);
+
+ btnAddTask.setOnClickListener(v -> showAddTaskDialog());
+ btnSort.setOnClickListener(v -> showSortDialog());
+
+ editTextSearch.addTextChangedListener(new 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) {
+ filterTasks(s.toString());
+ }
+ @Override public void afterTextChanged(Editable s) {}
+ });
+
+ filterTasks("");
+ }
+
+ private void filterTasks(String query) {
+ filteredList.clear();
+ for (Task task : taskList) {
+ if (task.getContent().toLowerCase().contains(query.toLowerCase())) {
+ filteredList.add(task);
+ }
+ }
+ applyCurrentSort();
+ adapter.notifyDataSetChanged();
+ }
+
+ private void applyCurrentSort() {
+ switch (currentSortType) {
+ case 1:
+ sortByPriority();
+ break;
+ case 2:
+ sortByCreateTime();
+ break;
+ default:
+ // 默认不排序
+ break;
+ }
+ }
+
+ private void sortByPriority() {
+ Collections.sort(filteredList, (t1, t2) -> {
+ int p1 = getPriorityValue(t1.getPriority());
+ int p2 = getPriorityValue(t2.getPriority());
+ return p2 - p1; // 降序
+ });
+ currentSortType = 1;
+ adapter.notifyDataSetChanged();
+ }
+
+ private void sortByCreateTime() {
+ Collections.sort(filteredList, (t1, t2) ->
+ t2.getCreateTime().compareTo(t1.getCreateTime())
+ );
+ currentSortType = 2;
+ adapter.notifyDataSetChanged();
+ }
+
+ private int getPriorityValue(String priority) {
+ switch (priority) {
+ case "高": return 3;
+ case "中": return 2;
+ case "低": return 1;
+ default: return 0;
+ }
+ }
+
+ private void showAddTaskDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("添加任务");
+
+ final EditText input = new EditText(this);
+ input.setHint("请输入任务内容");
+ input.setSingleLine();
+ input.setPadding(30, 20, 30, 20);
+
+ final Spinner prioritySpinner = new Spinner(this);
+ ArrayAdapter spinnerAdapter = new ArrayAdapter<>(
+ this, android.R.layout.simple_spinner_item,
+ new String[]{"高", "中", "低"});
+ spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ prioritySpinner.setAdapter(spinnerAdapter);
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(30, 20, 30, 10);
+ layout.addView(input);
+ layout.addView(prioritySpinner);
+
+ builder.setView(layout);
+
+ builder.setPositiveButton("确定", (dialog, which) -> {
+ String content = input.getText().toString().trim();
+ String priority = prioritySpinner.getSelectedItem().toString();
+ if (!content.isEmpty()) {
+ taskList.add(new Task(content, priority));
+ filterTasks(editTextSearch.getText().toString());
+ }
+ });
+
+ builder.setNegativeButton("取消", null);
+ builder.show();
+ }
+
+ private void showSortDialog() {
+ new AlertDialog.Builder(this)
+ .setTitle("排序方式")
+ .setItems(new String[]{"默认顺序", "按优先级排序", "按创建时间排序"}, (dialog, which) -> {
+ currentSortType = which;
+ applyCurrentSort();
+ })
+ .show();
+ }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ filterTasks(editTextSearch.getText().toString());
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/MainApplication.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/MainApplication.java
new file mode 100644
index 0000000..67937b7
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/MainApplication.java
@@ -0,0 +1,11 @@
+package com.example.ruanjian;
+
+import android.app.Application;
+
+public class MainApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ NotificationHelper.createNotificationChannel(this);
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/NotificationHelper.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/NotificationHelper.java
new file mode 100644
index 0000000..68398d9
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/NotificationHelper.java
@@ -0,0 +1,41 @@
+// NotificationHelper.java
+package com.example.ruanjian;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.core.app.NotificationCompat;
+
+public class NotificationHelper {
+ private static final String CHANNEL_ID = "task_reminder_channel";
+
+ public static void createNotificationChannel(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "Task Reminders",
+ NotificationManager.IMPORTANCE_HIGH);
+ channel.setDescription("Channel for task reminder notifications");
+
+ NotificationManager manager = context.getSystemService(NotificationManager.class);
+ manager.createNotificationChannel(channel);
+ }
+ }
+
+ public static void showNotification(Context context, String title, String content) {
+ Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setAutoCancel(true)
+ .build();
+
+ NotificationManager manager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.notify((int) System.currentTimeMillis(), notification);
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/ReminderReceiver.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/ReminderReceiver.java
new file mode 100644
index 0000000..87400c9
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/ReminderReceiver.java
@@ -0,0 +1,17 @@
+// ReminderReceiver.java
+package com.example.ruanjian;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class ReminderReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String taskContent = intent.getStringExtra("task_content");
+ NotificationHelper.showNotification(
+ context,
+ "任务提醒",
+ "您的任务 '" + taskContent + "' 即将到期!");
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/Task.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/Task.java
new file mode 100644
index 0000000..99f1f22
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/Task.java
@@ -0,0 +1,79 @@
+package com.example.ruanjian;
+
+import java.util.Date;
+
+public class Task {
+ private String content;
+ private String priority; // "高", "中", "低"
+ private boolean completed;
+ private Date createTime;
+ private Date deadline; // 新增截止时间
+ private boolean hasReminder; // 新增是否设置提醒
+
+ public Task(String content, String priority) {
+ this.content = content;
+ this.priority = priority;
+ this.completed = false;
+ this.createTime = new Date();
+ this.deadline = null;
+ this.hasReminder = false;
+ }
+
+ // 原有方法
+ public String getContent() {
+ return content;
+ }
+
+ public String getPriority() {
+ return priority;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public void setPriority(String priority) {
+ this.priority = priority;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ // 新增方法
+ public Date getDeadline() {
+ return deadline;
+ }
+
+ public void setDeadline(Date deadline) {
+ this.deadline = deadline;
+ }
+
+ public boolean hasReminder() {
+ return hasReminder;
+ }
+
+ public void setHasReminder(boolean hasReminder) {
+ this.hasReminder = hasReminder;
+ }
+
+ // 新增辅助方法
+ public String getFormattedDeadline() {
+ if (deadline == null) {
+ return "未设置";
+ }
+ return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm").format(deadline);
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/TaskAdapter.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/TaskAdapter.java
new file mode 100644
index 0000000..6d77407
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/TaskAdapter.java
@@ -0,0 +1,114 @@
+package com.example.ruanjian;
+
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+
+public class TaskAdapter extends RecyclerView.Adapter {
+
+ private ArrayList taskList;
+ private OnItemClickListener listener;
+
+ public interface OnItemClickListener {
+ void onItemClick(int position);
+ }
+
+ public TaskAdapter(ArrayList taskList, OnItemClickListener listener) {
+ this.taskList = taskList;
+ this.listener = listener;
+ }
+
+ @NonNull
+ @Override
+ public TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_task, parent, false);
+ return new TaskViewHolder(view);
+ }
+
+ @Override
+
+ public void onBindViewHolder(@NonNull TaskViewHolder holder, int position) {
+ Task task = taskList.get(position);
+
+ holder.textViewTask.setText(task.getContent());
+
+ // 先移除监听,避免复用导致多次触发
+ holder.checkBoxCompleted.setOnCheckedChangeListener(null);
+ holder.checkBoxCompleted.setChecked(task.isCompleted());
+
+ if (task.isCompleted()) {
+ holder.textViewTask.setPaintFlags(holder.textViewTask.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ holder.textViewTask.setTextColor(Color.GRAY);
+ } else {
+ holder.textViewTask.setPaintFlags(holder.textViewTask.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
+ switch (task.getPriority()) {
+ case "高":
+ holder.textViewTask.setTextColor(Color.RED);
+ break;
+ case "中":
+ holder.textViewTask.setTextColor(Color.parseColor("#FF9800")); // 橙色
+ break;
+ case "低":
+ holder.textViewTask.setTextColor(Color.parseColor("#4CAF50")); // 绿色
+ break;
+ default:
+ holder.textViewTask.setTextColor(Color.BLACK);
+ break;
+ }
+ }
+
+ // 重新设置监听,避免闪退
+ holder.checkBoxCompleted.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ task.setCompleted(isChecked);
+ // 直接更新UI,不调用 notifyItemChanged(position)
+ if (isChecked) {
+ holder.textViewTask.setPaintFlags(holder.textViewTask.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ holder.textViewTask.setTextColor(Color.GRAY);
+ } else {
+ holder.textViewTask.setPaintFlags(holder.textViewTask.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
+ switch (task.getPriority()) {
+ case "高":
+ holder.textViewTask.setTextColor(Color.RED);
+ break;
+ case "中":
+ holder.textViewTask.setTextColor(Color.parseColor("#FF9800"));
+ break;
+ case "低":
+ holder.textViewTask.setTextColor(Color.parseColor("#4CAF50"));
+ break;
+ default:
+ holder.textViewTask.setTextColor(Color.BLACK);
+ break;
+ }
+ }
+ });
+
+ holder.itemView.setOnClickListener(v -> listener.onItemClick(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return taskList.size();
+ }
+
+ static class TaskViewHolder extends RecyclerView.ViewHolder {
+ TextView textViewTask;
+ CheckBox checkBoxCompleted;
+
+ public TaskViewHolder(@NonNull View itemView) {
+ super(itemView);
+ textViewTask = itemView.findViewById(R.id.textViewTask);
+ checkBoxCompleted = itemView.findViewById(R.id.checkBoxCompleted);
+ }
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/ToDoAdapter.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/ToDoAdapter.java
new file mode 100644
index 0000000..40a3cc6
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/ToDoAdapter.java
@@ -0,0 +1,62 @@
+package com.example.ruanjian;
+
+import android.graphics.Paint;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+
+public class ToDoAdapter extends RecyclerView.Adapter {
+ private ArrayList todoList;
+
+ public ToDoAdapter(ArrayList todoList) {
+ this.todoList = todoList;
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView todoText;
+ public CardView card;
+
+ public ViewHolder(View view) {
+ super(view);
+ todoText = view.findViewById(R.id.todoText);
+ card = view.findViewById(R.id.cardView);
+ }
+ }
+
+ @Override
+ public ToDoAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_todo, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ ToDoItem item = todoList.get(position);
+ holder.todoText.setText(item.getText());
+
+ if (item.isDone()) {
+ holder.todoText.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
+ holder.card.setCardBackgroundColor(0xFFE0E0E0); // 灰色
+ } else {
+ holder.todoText.setPaintFlags(0);
+ holder.card.setCardBackgroundColor(0xFFFFFFFF); // 白色
+ }
+
+ holder.itemView.setOnClickListener(v -> {
+ item.toggleDone();
+ notifyItemChanged(position);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return todoList.size();
+ }
+}
diff --git a/doc/ruanjian/app/src/main/java/com/example/ruanjian/ToDoItem.java b/doc/ruanjian/app/src/main/java/com/example/ruanjian/ToDoItem.java
new file mode 100644
index 0000000..ce9fed3
--- /dev/null
+++ b/doc/ruanjian/app/src/main/java/com/example/ruanjian/ToDoItem.java
@@ -0,0 +1,23 @@
+package com.example.ruanjian;
+
+public class ToDoItem {
+ private String text;
+ private boolean isDone;
+
+ public ToDoItem(String text) {
+ this.text = text;
+ this.isDone = false;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public boolean isDone() {
+ return isDone;
+ }
+
+ public void toggleDone() {
+ this.isDone = !this.isDone;
+ }
+}
diff --git a/doc/ruanjian/app/src/main/res/drawable/an.png b/doc/ruanjian/app/src/main/res/drawable/an.png
new file mode 100644
index 0000000..a8a6a5c
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/drawable/an.png differ
diff --git a/doc/ruanjian/app/src/main/res/drawable/bg_main.jpg b/doc/ruanjian/app/src/main/res/drawable/bg_main.jpg
new file mode 100644
index 0000000..add66ab
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/drawable/bg_main.jpg differ
diff --git a/doc/ruanjian/app/src/main/res/drawable/bg_todo.xml b/doc/ruanjian/app/src/main/res/drawable/bg_todo.xml
new file mode 100644
index 0000000..5a404a3
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/bg_todo.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/doc/ruanjian/app/src/main/res/drawable/detail_background.xml b/doc/ruanjian/app/src/main/res/drawable/detail_background.xml
new file mode 100644
index 0000000..62e7f42
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/detail_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/drawable/dialog_bg.xml b/doc/ruanjian/app/src/main/res/drawable/dialog_bg.xml
new file mode 100644
index 0000000..a68b45a
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/dialog_bg.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/drawable/edittext_bg.xml b/doc/ruanjian/app/src/main/res/drawable/edittext_bg.xml
new file mode 100644
index 0000000..c652bae
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/edittext_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/drawable/gou.png b/doc/ruanjian/app/src/main/res/drawable/gou.png
new file mode 100644
index 0000000..3a3a06e
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/drawable/gou.png differ
diff --git a/doc/ruanjian/app/src/main/res/drawable/gu.png b/doc/ruanjian/app/src/main/res/drawable/gu.png
new file mode 100644
index 0000000..4c8ef95
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/drawable/gu.png differ
diff --git a/doc/ruanjian/app/src/main/res/drawable/ic_launcher_background.xml b/doc/ruanjian/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..ca3826a
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/drawable/ic_launcher_foreground.xml b/doc/ruanjian/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/drawable/ic_notification.xml b/doc/ruanjian/app/src/main/res/drawable/ic_notification.xml
new file mode 100644
index 0000000..44ac014
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/drawable/ic_notification.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/drawable/wa.png b/doc/ruanjian/app/src/main/res/drawable/wa.png
new file mode 100644
index 0000000..60065c0
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/drawable/wa.png differ
diff --git a/doc/ruanjian/app/src/main/res/layout/activity_add_task.xml b/doc/ruanjian/app/src/main/res/layout/activity_add_task.xml
new file mode 100644
index 0000000..ba25232
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/layout/activity_add_task.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/layout/activity_detail.xml b/doc/ruanjian/app/src/main/res/layout/activity_detail.xml
new file mode 100644
index 0000000..e202dd0
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/layout/activity_detail.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/layout/activity_main.xml b/doc/ruanjian/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e09740d
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/layout/item_task.xml b/doc/ruanjian/app/src/main/res/layout/item_task.xml
new file mode 100644
index 0000000..8ff7a3e
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/layout/item_task.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/layout/item_todo.xml b/doc/ruanjian/app/src/main/res/layout/item_todo.xml
new file mode 100644
index 0000000..a1f00f8
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/layout/item_todo.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/doc/ruanjian/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/doc/ruanjian/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..c4a603d
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..060d03e
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..213e628
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..f5c0cf7
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..fa2bfca
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..6e3f450
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..88a9085
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..5cec316
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..15ff45e
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..e3eb4ef
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..7800295
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..71ad3d1
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..a7765b4
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..cdc2085
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..2cf682e
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..6642758
Binary files /dev/null and b/doc/ruanjian/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/doc/ruanjian/app/src/main/res/values-night/themes.xml b/doc/ruanjian/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..e390e47
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/values/arrays.xml b/doc/ruanjian/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..3b2d3de
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/values/arrays.xml
@@ -0,0 +1,7 @@
+
+
+ - 高
+ - 中
+ - 低
+
+
diff --git a/doc/ruanjian/app/src/main/res/values/colors.xml b/doc/ruanjian/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..5f8cb63
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF6B6B
+ #C71C1C
+
diff --git a/doc/ruanjian/app/src/main/res/values/strings.xml b/doc/ruanjian/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6dc0d45
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ ruanjian
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/values/styles.xml b/doc/ruanjian/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..2789df3
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/values/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/values/themes.xml b/doc/ruanjian/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..8011d60
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/values/themes.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/doc/ruanjian/app/src/main/res/xml/backup_rules.xml b/doc/ruanjian/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/xml/data_extraction_rules.xml b/doc/ruanjian/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/main/res/xml/styles.xml b/doc/ruanjian/app/src/main/res/xml/styles.xml
new file mode 100644
index 0000000..f4cd2e9
--- /dev/null
+++ b/doc/ruanjian/app/src/main/res/xml/styles.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/ruanjian/app/src/test/java/com/example/ruanjian/ExampleUnitTest.java b/doc/ruanjian/app/src/test/java/com/example/ruanjian/ExampleUnitTest.java
new file mode 100644
index 0000000..d67ca9a
--- /dev/null
+++ b/doc/ruanjian/app/src/test/java/com/example/ruanjian/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.ruanjian;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/doc/ruanjian/build.gradle.kts b/doc/ruanjian/build.gradle.kts
new file mode 100644
index 0000000..e19623e
--- /dev/null
+++ b/doc/ruanjian/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ kotlin("android") version "1.8.10" apply false
+ id("com.android.application") version "8.1.0" apply false
+}
diff --git a/doc/ruanjian/gradle.properties b/doc/ruanjian/gradle.properties
new file mode 100644
index 0000000..4387edc
--- /dev/null
+++ b/doc/ruanjian/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/doc/ruanjian/gradle/libs.versions.toml b/doc/ruanjian/gradle/libs.versions.toml
new file mode 100644
index 0000000..08c8f02
--- /dev/null
+++ b/doc/ruanjian/gradle/libs.versions.toml
@@ -0,0 +1,22 @@
+[versions]
+agp = "8.7.2"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+appcompat = "1.6.1"
+material = "1.10.0"
+activity = "1.8.0"
+constraintlayout = "2.1.4"
+
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+
diff --git a/doc/ruanjian/gradle/wrapper/gradle-wrapper.jar b/doc/ruanjian/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/doc/ruanjian/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/doc/ruanjian/gradle/wrapper/gradle-wrapper.properties b/doc/ruanjian/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..1745603
--- /dev/null
+++ b/doc/ruanjian/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+
diff --git a/doc/ruanjian/gradlew b/doc/ruanjian/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/doc/ruanjian/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/doc/ruanjian/gradlew.bat b/doc/ruanjian/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/doc/ruanjian/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/doc/ruanjian/settings.gradle.kts b/doc/ruanjian/settings.gradle.kts
new file mode 100644
index 0000000..4846159
--- /dev/null
+++ b/doc/ruanjian/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "ruanjian"
+include(":app")
diff --git a/src/文档模板-开源软件维护报告文档 (1)(2)(3).docx b/src/文档模板-开源软件维护报告文档 (1)(2)(3).docx
new file mode 100644
index 0000000..bdf16e7
Binary files /dev/null and b/src/文档模板-开源软件维护报告文档 (1)(2)(3).docx differ
diff --git a/src/软件工程(2).pptx b/src/软件工程(2).pptx
new file mode 100644
index 0000000..9769923
Binary files /dev/null and b/src/软件工程(2).pptx differ