diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml
new file mode 100644
index 0000000..c4aa63d
--- /dev/null
+++ b/.github/workflows/android_ci.yml
@@ -0,0 +1,42 @@
+name: Android CI
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4.1.1
+ - name: set up JDK 17
+ uses: actions/setup-java@v4.0.0
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Build with Gradle
+ run: ./gradlew assembleRelease
+
+ - uses: ilharp/sign-android-release@nightly
+ name: Sign APK
+ id: sign_app
+ with:
+ releaseDir: app/build/outputs/apk/release
+ signingKey: ${{ secrets.ANDROID_SIGNING_KEY }}
+ keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }}
+ keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
+ keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
+ buildToolsVersion: 33.0.0
+
+ - name: Upload APK
+ uses: actions/upload-artifact@v4.0.0
+ with:
+ name: Release
+ path: ${{steps.sign_app.outputs.signedFile}}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 347e252..c4b6c3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ google-services.json
# Android Profiling
*.hprof
+
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
index f17a84f..4f34b01 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-# ToDo
+# 待办 | ToDo
简单的待办应用,遵循Material Design 3
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..44cb119
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,72 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+val baseVersionName = "1.0.0"
+val commitHash by lazy { "git rev-parse --short HEAD".exec() }
+val verCode by lazy { "git rev-list --count HEAD".exec().toInt() }
+
+android {
+ namespace = "cn.super12138.todo"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "cn.super12138.todo"
+ minSdk = 24
+ targetSdk = 34
+ versionCode = verCode
+ versionName = "${baseVersionName}-${commitHash}"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+
+ // AndroidX
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ implementation("androidx.fragment:fragment:1.6.2")
+ implementation("androidx.fragment:fragment-ktx:1.6.2")
+ implementation("androidx.recyclerview:recyclerview:1.3.2")
+ implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.2")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
+ implementation("androidx.preference:preference:1.2.1")
+ implementation("androidx.preference:preference-ktx:1.2.1")
+ // Material Design
+ implementation("com.google.android.material:material:1.12.0-alpha02")
+ // Test
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+}
+
+fun String.exec(): String = exec(this)
+
+@Suppress("UnstableApiUsage")
+fun Project.exec(command: String): String = providers.exec {
+ commandLine(command.split(" "))
+}.standardOutput.asText.get().trim()
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/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/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..54a4fa5
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "cn.super12138.todo",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File"
+}
\ No newline at end of file
diff --git a/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt b/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..1a9a74c
--- /dev/null
+++ b/app/src/androidTest/kotlin/cn/super12138/todo/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package cn.super12138.todo
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("cn.super12138.todo", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b9d28c8
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/ToDoApplication.kt b/app/src/main/kotlin/cn/super12138/todo/ToDoApplication.kt
new file mode 100644
index 0000000..1b03ab0
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/ToDoApplication.kt
@@ -0,0 +1,23 @@
+package cn.super12138.todo
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.Context
+import cn.super12138.todo.views.crash.CrashHandler
+import com.google.android.material.color.DynamicColors
+
+class ToDoApplication : Application() {
+ companion object {
+ @SuppressLint("StaticFieldLeak")
+ lateinit var context: Context
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ DynamicColors.applyToActivitiesIfAvailable(this)
+ context = applicationContext
+
+ val crashHandler = CrashHandler(this)
+ Thread.setDefaultUncaughtExceptionHandler(crashHandler)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt
new file mode 100644
index 0000000..ca57bd5
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt
@@ -0,0 +1,11 @@
+package cn.super12138.todo.constant
+
+object Constants {
+ const val DB_NAME = "ToDo.db"
+ const val DB_VERSION = 1
+ const val TABLE_NAME = "ToDo"
+
+ const val AUTHOR_GITHUB_URL = "https://github.com/Super12138/"
+ const val REPO_GITHUB_URL = "https://github.com/Super12138/ToDo"
+ const val UPDATE_URL = "https://github.com/Super12138/ToDo/releases"
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt b/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt
new file mode 100644
index 0000000..13bbd7c
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt
@@ -0,0 +1,20 @@
+package cn.super12138.todo.logic
+
+import android.content.ContentValues
+import android.content.Context
+import cn.super12138.todo.logic.database.DBHelper
+import cn.super12138.todo.logic.database.SPHelper
+
+object Repository {
+ fun getCompleteTotalCount() = DBHelper.getCompleteTotalCount()
+
+ fun insertData(data: ContentValues) = DBHelper.insertData(data)
+
+ fun deleteData(deleteAll: Boolean, uuid: String?) = DBHelper.deleteData(deleteAll, uuid)
+
+ fun updateData(uuid: String, newData: ContentValues) = DBHelper.updateData(uuid, newData)
+
+ fun getAllData() = DBHelper.getAllData()
+
+ fun getPreferenceString(context: Context, key: String, defaultValue: String) = SPHelper.getPreferenceString(context, key, defaultValue)
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/DBHelper.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/DBHelper.kt
new file mode 100644
index 0000000..569d9be
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/DBHelper.kt
@@ -0,0 +1,109 @@
+package cn.super12138.todo.logic.database
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import cn.super12138.todo.ToDoApplication
+import cn.super12138.todo.constant.Constants
+import cn.super12138.todo.logic.model.Progress
+import cn.super12138.todo.logic.model.ToDo
+import cn.super12138.todo.logic.model.ToDoDatabase
+
+object DBHelper {
+
+ fun insertData(data: ContentValues) {
+ ToDoDatabase(
+ ToDoApplication.context,
+ Constants.DB_NAME,
+ Constants.DB_VERSION
+ ).writableDatabase.use {db ->
+ db.insert(Constants.TABLE_NAME, null, data)
+ }
+ }
+
+ fun deleteData(delAll: Boolean, uuid: String?) {
+ ToDoDatabase(
+ ToDoApplication.context,
+ Constants.DB_NAME,
+ Constants.DB_VERSION
+ ).writableDatabase.use { db ->
+ if (delAll) {
+ db.delete(Constants.TABLE_NAME, null, null)
+ } else {
+ if (uuid == null) {
+ throw RuntimeException("uuid cannot be null")
+ }
+ db.delete(Constants.TABLE_NAME, "uuid = ?", arrayOf(uuid))
+ }
+ }
+ }
+
+ fun updateData(uuid: String, newData: ContentValues) {
+ val dbHelper = ToDoDatabase(
+ ToDoApplication.context,
+ Constants.DB_NAME,
+ Constants.DB_VERSION
+ ).writableDatabase
+ dbHelper.update(Constants.TABLE_NAME, newData, "uuid = ?", arrayOf(uuid))
+ }
+
+ @SuppressLint("Range")
+ fun getCompleteTotalCount(): Progress {
+ var total = 0
+ var complete = 0
+
+ ToDoDatabase(
+ ToDoApplication.context,
+ Constants.DB_NAME,
+ Constants.DB_VERSION
+ ).writableDatabase.use { db ->
+ db.query(Constants.TABLE_NAME, null, null, null, null, null, null, null).use { cursor ->
+ if (cursor.moveToFirst()) {
+ do {
+ val state = cursor.getString(cursor.getColumnIndex("state"))
+ if (state.toInt() == 1) {
+ complete += 1
+ }
+ total += 1
+ } while (cursor.moveToNext())
+ cursor.close()
+ }
+ }
+ }
+
+ return Progress(complete, total)
+ }
+
+ @SuppressLint("Range")
+ fun getAllData() : MutableList {
+ val todoList = mutableListOf()
+ ToDoDatabase(
+ ToDoApplication.context,
+ Constants.DB_NAME,
+ Constants.DB_VERSION
+ ).writableDatabase.query(
+ Constants.TABLE_NAME,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ).use { cursor ->
+ if (cursor.moveToFirst()) {
+ do {
+ val uuid = cursor.getString(cursor.getColumnIndex("uuid"))
+ val subject = cursor.getString(cursor.getColumnIndex("subject"))
+ val state = cursor.getString(cursor.getColumnIndex("state"))
+ val todoContext = cursor.getString(cursor.getColumnIndex("context"))
+
+ if (state.toInt() != 1) {
+ todoList.add(ToDo(uuid, todoContext, subject))
+ }
+ } while (cursor.moveToNext())
+ cursor.close()
+ }
+ }
+ return todoList
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/database/SPHelper.kt b/app/src/main/kotlin/cn/super12138/todo/logic/database/SPHelper.kt
new file mode 100644
index 0000000..f3aab33
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/logic/database/SPHelper.kt
@@ -0,0 +1,11 @@
+package cn.super12138.todo.logic.database
+
+import android.content.Context
+import androidx.preference.PreferenceManager
+
+object SPHelper {
+ fun getPreferenceString(context: Context, name: String, defaultValue: String): String? {
+ val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context /* Activity context */)
+ return sharedPreferences.getString(name, defaultValue)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/Progress.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/Progress.kt
new file mode 100644
index 0000000..897432c
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/Progress.kt
@@ -0,0 +1,3 @@
+package cn.super12138.todo.logic.model
+
+data class Progress(val complete: Int, val total: Int)
diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/ToDo.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/ToDo.kt
new file mode 100644
index 0000000..b670c7b
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/ToDo.kt
@@ -0,0 +1,19 @@
+package cn.super12138.todo.logic.model
+
+data class ToDo(val uuid: String, val context: String, val subject: String)
+
+/*
+package cn.super12138.todo.logic.model
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "todo")
+data class ToDo (
+ @PrimaryKey @ColumnInfo(name = "uuid") val uuid: String,
+ @ColumnInfo(name = "state") val state: Int,
+ @ColumnInfo(name = "subject") val subject: String,
+ @ColumnInfo(name = "context") val context: String
+)
+ */
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/model/ToDoDatabase.kt b/app/src/main/kotlin/cn/super12138/todo/logic/model/ToDoDatabase.kt
new file mode 100644
index 0000000..8e9d0d3
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/logic/model/ToDoDatabase.kt
@@ -0,0 +1,28 @@
+package cn.super12138.todo.logic.model
+
+import android.content.Context
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteOpenHelper
+
+class ToDoDatabase(val context: Context, name: String, version: Int) :
+ SQLiteOpenHelper(context, name, null, version) {
+ /*
+ * @param uuid: String 待办的uuid
+ * @param state: Int 待办的完成状态,0表示未完成,1表示完成
+ * @param subject: String 待办的学科
+ * @param context: String 待办的内容
+ */
+ private val createToDo = "create table ToDo (" +
+ "uuid text primary key," +
+ "state integer," +
+ "subject text," +
+ "context text)"
+
+ override fun onCreate(db: SQLiteDatabase) {
+ db.execSQL(createToDo)
+ }
+
+ override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
+
+ }
+}
diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/Toast.kt b/app/src/main/kotlin/cn/super12138/todo/utils/Toast.kt
new file mode 100644
index 0000000..04d9583
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/utils/Toast.kt
@@ -0,0 +1,14 @@
+import android.content.Context
+import android.widget.Toast
+import cn.super12138.todo.ToDoApplication
+
+fun String.showToast(
+ context: Context = ToDoApplication.context,
+ duration: Int = Toast.LENGTH_SHORT
+) {
+ Toast.makeText(context, this, duration).show()
+}
+
+fun Int.showToast(context: Context = ToDoApplication.context, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(context, this, duration).show()
+}
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/about/AboutActivity.kt b/app/src/main/kotlin/cn/super12138/todo/views/about/AboutActivity.kt
new file mode 100644
index 0000000..230a9f9
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/about/AboutActivity.kt
@@ -0,0 +1,77 @@
+package cn.super12138.todo.views.about
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import cn.super12138.todo.constant.Constants
+import cn.super12138.todo.databinding.ActivityAboutBinding
+import showToast
+
+class AboutActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityAboutBinding
+ private var clickCount = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ // 适配刘海屏
+ val lp = window.attributes
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ lp.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+ window.attributes = lp
+
+ binding = ActivityAboutBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ val pkgInfo = packageManager.getPackageInfo(packageName, 0)
+ val verName = pkgInfo.versionName
+ val verCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ pkgInfo.longVersionCode.toInt()
+ } else {
+ pkgInfo.versionCode
+ }
+ binding.appVersion.text = "$verName($verCode)"
+
+ binding.toolbar.setNavigationOnClickListener {
+ finish()
+ }
+
+ binding.checkUpdate.setOnClickListener {
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ data = Uri.parse(Constants.UPDATE_URL)
+ }
+ startActivity(intent)
+ }
+
+ binding.openSource.setOnClickListener {
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ data = Uri.parse(Constants.REPO_GITHUB_URL)
+ }
+ startActivity(intent)
+ }
+
+
+ binding.developerInfo.setOnClickListener {
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ data = Uri.parse(Constants.AUTHOR_GITHUB_URL)
+ }
+ startActivity(intent)
+ }
+
+ binding.appVersion.setOnClickListener {
+ clickCount++
+
+ if (clickCount == 5) {
+ clickCount = 0
+ "🧧".showToast()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/crash/CrashActivity.kt b/app/src/main/kotlin/cn/super12138/todo/views/crash/CrashActivity.kt
new file mode 100644
index 0000000..1094b5d
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/crash/CrashActivity.kt
@@ -0,0 +1,50 @@
+package cn.super12138.todo.views.crash
+
+import android.os.Build
+import android.os.Bundle
+import android.view.ViewGroup
+import android.view.WindowManager
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updateLayoutParams
+import cn.super12138.todo.databinding.ActivityCrashBinding
+
+class CrashActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityCrashBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+
+ // 适配刘海屏
+ val lp = window.attributes
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ lp.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+ window.attributes = lp
+
+ binding = ActivityCrashBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.exitApp) { view, windowInsets ->
+ val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.updateLayoutParams {
+ leftMargin = insets.left
+ bottomMargin = insets.bottom
+ rightMargin = insets.right
+ }
+
+ WindowInsetsCompat.CONSUMED
+ }
+
+ val crashLogs = intent.getStringExtra("crash_logs")
+ binding.crashLog.text = crashLogs
+
+ binding.exitApp.setOnClickListener {
+ this.finishAffinity()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/crash/CrashHandler.kt b/app/src/main/kotlin/cn/super12138/todo/views/crash/CrashHandler.kt
new file mode 100644
index 0000000..fa92578
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/crash/CrashHandler.kt
@@ -0,0 +1,28 @@
+package cn.super12138.todo.views.crash
+
+import android.content.Context
+import android.content.Intent
+import android.os.Process
+
+class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler {
+
+ private val defaultUEH = Thread.getDefaultUncaughtExceptionHandler()
+
+ override fun uncaughtException(thread: Thread, ex: Throwable) {
+ val stackTrace = ex.stackTraceToString()
+
+ // 启动新的 Activity 来显示崩溃日志
+ val intent = Intent(context, CrashActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra("crash_logs", stackTrace)
+ }
+ context.startActivity(intent)
+
+ // 杀掉崩溃的应用程序进程
+ Process.killProcess(Process.myPid())
+ System.exit(10)
+
+ // 传递异常给默认的异常处理器
+ defaultUEH?.uncaughtException(thread, ex)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/main/MainActivity.kt b/app/src/main/kotlin/cn/super12138/todo/views/main/MainActivity.kt
new file mode 100644
index 0000000..0d0af3f
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/main/MainActivity.kt
@@ -0,0 +1,76 @@
+package cn.super12138.todo.views.main
+// 2023.11.18立项
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import android.view.WindowManager
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.preference.PreferenceManager
+import cn.super12138.todo.R
+import cn.super12138.todo.ToDoApplication
+import cn.super12138.todo.databinding.ActivityMainBinding
+import cn.super12138.todo.logic.Repository
+import cn.super12138.todo.views.settings.SettingsActivity
+
+class MainActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ // 适配刘海屏
+ val lp = window.attributes
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ lp.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+ window.attributes = lp
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ setSupportActionBar(binding.toolbar)
+ /*val pref = getSharedPreferences("data",Context.MODE_PRIVATE)
+ val isFirstUse = pref.getBoolean("first_use", false)
+
+ if (!isFirstUse) {
+ MaterialAlertDialogBuilder(this)
+ .setTitle("欢迎使用待办")
+ .setMessage("")
+ .setPositiveButton("确定") { dialog, which ->
+ val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
+ editor.putBoolean("first_use", true)
+ editor.apply()
+ }
+ .show()
+ }
+*/
+ val isDarkMode = Repository.getPreferenceString(this, "dark_mode", "0")
+ when (isDarkMode) {
+ "0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+
+ "1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
+
+ "2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.main, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.item_settings -> {
+ val intent = Intent(ToDoApplication.context, SettingsActivity::class.java)
+ startActivity(intent)
+ }
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/progress/ProgressFragment.kt b/app/src/main/kotlin/cn/super12138/todo/views/progress/ProgressFragment.kt
new file mode 100644
index 0000000..1668d48
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/progress/ProgressFragment.kt
@@ -0,0 +1,45 @@
+package cn.super12138.todo.views.progress
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import cn.super12138.todo.databinding.FragmentProgressBinding
+
+
+class ProgressFragment : Fragment() {
+ private lateinit var viewModel: ProgressFragmentViewModel
+ private lateinit var binding: FragmentProgressBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentProgressBinding.inflate(inflater, container, false)
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewModel = ViewModelProvider(requireActivity()).get(ProgressFragmentViewModel::class.java)
+
+ viewModel.progress.observe(viewLifecycleOwner, Observer { value ->
+ binding.progressBar.setProgressCompat(value, true)
+ })
+
+ viewModel.totalCount.observe(viewLifecycleOwner, Observer { total ->
+ binding.totalTextView.text = total.toString()
+ })
+
+ viewModel.completeCount.observe(viewLifecycleOwner, Observer { complete ->
+ binding.completeTextView.text = complete.toString()
+ })
+
+ viewModel.updateProgress()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/progress/ProgressFragmentViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/views/progress/ProgressFragmentViewModel.kt
new file mode 100644
index 0000000..8042096
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/progress/ProgressFragmentViewModel.kt
@@ -0,0 +1,22 @@
+package cn.super12138.todo.views.progress
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import cn.super12138.todo.logic.Repository
+
+class ProgressFragmentViewModel : ViewModel() {
+ val totalCount: MutableLiveData = MutableLiveData()
+ val completeCount: MutableLiveData = MutableLiveData()
+ val progress: MutableLiveData = MutableLiveData()
+
+ fun updateProgress() {
+ val progressData = Repository.getCompleteTotalCount()
+ val total = progressData.total
+ val complete = progressData.complete
+ val calcProgress = (complete.toDouble() / total.toDouble()) * 100
+
+ progress.value = calcProgress.toInt()
+ totalCount.value = total
+ completeCount.value = complete
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/settings/SettingsActivity.kt b/app/src/main/kotlin/cn/super12138/todo/views/settings/SettingsActivity.kt
new file mode 100644
index 0000000..679a967
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/settings/SettingsActivity.kt
@@ -0,0 +1,62 @@
+package cn.super12138.todo.views.settings
+
+import android.os.Build
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceFragmentCompat
+import cn.super12138.todo.R
+import cn.super12138.todo.databinding.ActivitySettingsBinding
+
+class SettingsActivity : AppCompatActivity() {
+ private lateinit var binding: ActivitySettingsBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // 适配刘海屏
+ val lp = window.attributes
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ lp.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
+ window.attributes = lp
+
+ binding = ActivitySettingsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ if (savedInstanceState == null) {
+ supportFragmentManager
+ .beginTransaction()
+ .replace(R.id.settings_frame, SettingsFragment())
+ .commit()
+ }
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+
+ binding.toolbar.setNavigationOnClickListener {
+ finish()
+ }
+ }
+
+
+ class SettingsFragment : PreferenceFragmentCompat() {
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.preferences, rootKey)
+
+ findPreference("dark_mode")?.apply {
+ setOnPreferenceChangeListener { preference, newValue ->
+ when (newValue) {
+ "0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+
+ "1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
+
+ "2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
+ }
+ activity?.recreate()
+ true
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt
new file mode 100644
index 0000000..48cdd5b
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt
@@ -0,0 +1,118 @@
+package cn.super12138.todo.views.todo
+
+import android.content.ContentValues
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.recyclerview.widget.RecyclerView
+import cn.super12138.todo.R
+import cn.super12138.todo.logic.Repository
+import cn.super12138.todo.logic.model.ToDo
+import cn.super12138.todo.views.progress.ProgressFragmentViewModel
+import com.google.android.material.snackbar.Snackbar
+
+class ToDoAdapter(val todoList: MutableList, val viewModelStoreOwner: ViewModelStoreOwner) :
+ RecyclerView.Adapter() {
+
+ inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val todoContext: TextView = view.findViewById(R.id.todo_context)
+ val todoSubject: TextView = view.findViewById(R.id.todo_subject)
+ val checkToDoBtn: Button = view.findViewById(R.id.check_item_btn)
+ val delToDoBtn: Button = view.findViewById(R.id.delete_item_btn)
+ }
+
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val progressViewModel =
+ ViewModelProvider(viewModelStoreOwner).get(ProgressFragmentViewModel::class.java)
+ val todoViewModel =
+ ViewModelProvider(viewModelStoreOwner).get(ToDoFragmentViewModel::class.java)
+
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.todo_item, parent, false)
+ val holder = ViewHolder(view)
+
+ holder.checkToDoBtn.setOnClickListener {
+ val position = holder.absoluteAdapterPosition
+ if (position < 0 || position >= todoList.size) {
+ return@setOnClickListener
+ }
+ val todo = todoList[position]
+
+ todoList.removeAt(position)
+ notifyItemRemoved(position)
+ notifyItemRangeChanged(position, todoList.size)
+
+ val newState = ContentValues().apply {
+ put("state", true)
+ }
+ Repository.updateData(todo.uuid, newState)
+
+ progressViewModel.updateProgress()
+
+ // 设置空项目提示可见性
+ if (todoList.isEmpty()) {
+ todoViewModel.emptyTipVis.value = View.VISIBLE
+ } else {
+ todoViewModel.emptyTipVis.value = View.GONE
+ }
+ }
+
+ holder.delToDoBtn.setOnClickListener {
+ val position = holder.absoluteAdapterPosition
+ if (position < 0 || position >= todoList.size) {
+ return@setOnClickListener
+ }
+ val todo = todoList[position]
+
+ todoList.removeAt(position)
+ notifyItemRemoved(position)
+ notifyItemRangeChanged(position, todoList.size)
+
+ val tempTaskInfo = ContentValues().apply {
+ put("uuid", todo.uuid)
+ put("state", false)
+ put("subject", todo.subject)
+ put("context", todo.context)
+ }
+
+ Repository.deleteData(false, todo.uuid)
+
+ progressViewModel.updateProgress()
+
+ // 设置空项目提示可见性
+ if (todoList.isEmpty()) {
+ todoViewModel.emptyTipVis.value = View.VISIBLE
+ } else {
+ todoViewModel.emptyTipVis.value = View.GONE
+ }
+
+ Snackbar.make(it, R.string.task_deleted, Snackbar.LENGTH_LONG)
+ .setAction(R.string.delete_undo) {
+ if (todoList.size + 1 > 0) {
+ todoViewModel.emptyTipVis.value = View.GONE
+ }
+ todoList.add(ToDo(todo.uuid, todo.context, todo.subject))
+
+ todoViewModel.refreshData.value = 1
+ Repository.insertData(tempTaskInfo)
+ progressViewModel.updateProgress()
+ }
+ .show()
+ }
+ return holder
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val todo = todoList[position]
+ holder.todoContext.text = todo.context
+ holder.todoSubject.text = todo.subject
+
+ }
+
+ override fun getItemCount() = todoList.size
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt
new file mode 100644
index 0000000..7358376
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt
@@ -0,0 +1,168 @@
+package cn.super12138.todo.views.todo
+
+import android.content.ContentValues
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import cn.super12138.todo.R
+import cn.super12138.todo.ToDoApplication
+import cn.super12138.todo.databinding.DialogAddTodoBinding
+import cn.super12138.todo.databinding.FragmentTodoBinding
+import cn.super12138.todo.logic.Repository
+import cn.super12138.todo.logic.model.ToDo
+import cn.super12138.todo.views.progress.ProgressFragmentViewModel
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import java.util.UUID
+
+class ToDoFragment : Fragment() {
+
+ private lateinit var binding: FragmentTodoBinding
+ private lateinit var ToDoDialogBinding: DialogAddTodoBinding
+ private val todoList = ArrayList()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentTodoBinding.inflate(inflater, container, false)
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val todos = Repository.getAllData()
+ for (todo in todos) {
+ todoList.add(ToDo(todo.uuid, todo.context, todo.subject))
+ }
+
+ /*ViewCompat.setOnApplyWindowInsetsListener(binding.todoList) { view, windowInsets ->
+ val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.updateLayoutParams {
+ leftMargin = insets.left
+ bottomMargin = insets.bottom
+ rightMargin = insets.right
+ }
+
+ WindowInsetsCompat.CONSUMED
+ }*/
+
+ val layoutManager = LinearLayoutManager(ToDoApplication.context)
+ binding.todoList.layoutManager = layoutManager
+ val adapter = ToDoAdapter(todoList, requireActivity())
+ binding.todoList.adapter = adapter
+
+ val progressViewModel =
+ ViewModelProvider(requireActivity()).get(ProgressFragmentViewModel::class.java)
+ val todoViewModel =
+ ViewModelProvider(requireActivity()).get(ToDoFragmentViewModel::class.java)
+
+
+ if (todoList.size == 0) {
+ todoViewModel.emptyTipVis.value = View.VISIBLE
+ }
+ binding.addItem.setOnClickListener {
+ ToDoDialogBinding = DialogAddTodoBinding.inflate(layoutInflater)
+
+ activity?.let { it1 ->
+ MaterialAlertDialogBuilder(it1)
+ .setTitle(R.string.add_task)
+ .setView(ToDoDialogBinding.root)
+ .setPositiveButton(R.string.ok) { dialog, which ->
+ val randomUUID = UUID.randomUUID().toString()
+ val todoContext = ToDoDialogBinding.todoContext.editText?.text.toString()
+ val todoSubject = when (ToDoDialogBinding.todoSubject.checkedChipId) {
+ R.id.subject_chinese -> "语文"
+ R.id.subject_math -> "数学"
+ R.id.subject_english -> "英语"
+ R.id.subject_biology -> "生物"
+ R.id.subject_geography -> "地理"
+ R.id.subject_history -> "历史"
+ R.id.subject_physics -> "物理"
+ R.id.subject_law -> "道法"
+ else -> "未知"
+ }
+
+ // 显示RecyclerView
+ if (todoList.size + 1 > 0) {
+ todoViewModel.emptyTipVis.value = View.GONE
+ }
+
+ // 添加到RecyclerView
+ todoList.add(
+ ToDo(randomUUID, todoContext, todoSubject)
+ )
+
+ // 添加到数据库
+ val todoData = ContentValues().apply {
+ put("uuid", randomUUID)
+ put("state", 0)
+ put("subject", todoSubject)
+ put("context", todoContext)
+ }
+ Repository.insertData(todoData)
+ // dbHelper.writableDatabase.insert("ToDo", null, todoData)
+
+ binding.todoList.adapter?.notifyItemInserted(todoList.size + 1)
+
+ progressViewModel.updateProgress()
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ }
+ }
+
+ binding.addItem.setOnLongClickListener {
+ activity?.let { it1 ->
+ MaterialAlertDialogBuilder(it1)
+ .setTitle(R.string.warning)
+ .setMessage(R.string.delete_confirm)
+ .setPositiveButton(R.string.ok) { dialog, which ->
+ todoList.clear()
+ Repository.deleteData(true, null)
+ // dbHelper.writableDatabase.delete("ToDo", null, null)
+ binding.todoList.adapter?.notifyItemRangeRemoved(0, todoList.size + 1)
+
+ progressViewModel.updateProgress()
+
+ todoViewModel.emptyTipVis.value = View.VISIBLE
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ }
+ true
+ }
+
+ binding.todoList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+ val fab = binding.addItem
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ if (dy > 0 && fab.isShown) {
+ fab.hide()
+ }
+ if (dy < 0 && !fab.isShown) {
+ fab.show()
+ }
+ }
+ })
+
+ todoViewModel.emptyTipVis.observe(viewLifecycleOwner, Observer { visibility ->
+ if (visibility == View.VISIBLE) {
+ binding.todoList.visibility = View.GONE
+ binding.emptyTip.visibility = View.VISIBLE
+ } else {
+ binding.todoList.visibility = View.VISIBLE
+ binding.emptyTip.visibility = View.GONE
+ }
+ })
+ todoViewModel.refreshData.observe(viewLifecycleOwner, Observer {
+ binding.todoList.adapter?.notifyItemInserted(todoList.size + 1)
+ })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt
new file mode 100644
index 0000000..1902b67
--- /dev/null
+++ b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt
@@ -0,0 +1,10 @@
+package cn.super12138.todo.views.todo
+
+import android.view.View
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class ToDoFragmentViewModel : ViewModel() {
+ val emptyTipVis = MutableLiveData(View.GONE)
+ val refreshData = MutableLiveData(0)
+}
\ No newline at end of file
diff --git a/app/src/main/play_store_512.png b/app/src/main/play_store_512.png
new file mode 100644
index 0000000..a1a9f37
Binary files /dev/null and b/app/src/main/play_store_512.png differ
diff --git a/app/src/main/res/drawable/add_24.xml b/app/src/main/res/drawable/add_24.xml
new file mode 100644
index 0000000..a9503fd
--- /dev/null
+++ b/app/src/main/res/drawable/add_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/arrow_back_24.xml b/app/src/main/res/drawable/arrow_back_24.xml
new file mode 100644
index 0000000..cd06f30
--- /dev/null
+++ b/app/src/main/res/drawable/arrow_back_24.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/check_24.xml b/app/src/main/res/drawable/check_24.xml
new file mode 100644
index 0000000..5623ef0
--- /dev/null
+++ b/app/src/main/res/drawable/check_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/delete_24.xml b/app/src/main/res/drawable/delete_24.xml
new file mode 100644
index 0000000..288869d
--- /dev/null
+++ b/app/src/main/res/drawable/delete_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/delete_forever_24.xml b/app/src/main/res/drawable/delete_forever_24.xml
new file mode 100644
index 0000000..5615a26
--- /dev/null
+++ b/app/src/main/res/drawable/delete_forever_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/exit_to_app_24.xml b/app/src/main/res/drawable/exit_to_app_24.xml
new file mode 100644
index 0000000..348fab8
--- /dev/null
+++ b/app/src/main/res/drawable/exit_to_app_24.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/github_24.xml b/app/src/main/res/drawable/github_24.xml
new file mode 100644
index 0000000..f32ae57
--- /dev/null
+++ b/app/src/main/res/drawable/github_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000..6e4f78d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/person_24.xml b/app/src/main/res/drawable/person_24.xml
new file mode 100644
index 0000000..74ba415
--- /dev/null
+++ b/app/src/main/res/drawable/person_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/settings_24.xml b/app/src/main/res/drawable/settings_24.xml
new file mode 100644
index 0000000..a7c7678
--- /dev/null
+++ b/app/src/main/res/drawable/settings_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/update_24.xml b/app/src/main/res/drawable/update_24.xml
new file mode 100644
index 0000000..14c24e7
--- /dev/null
+++ b/app/src/main/res/drawable/update_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml
new file mode 100644
index 0000000..d5ea165
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_main.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..a15f87b
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_crash.xml b/app/src/main/res/layout/activity_crash.xml
new file mode 100644
index 0000000..7edd0ee
--- /dev/null
+++ b/app/src/main/res/layout/activity_crash.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..b319978
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..0a79d29
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_add_todo.xml b/app/src/main/res/layout/dialog_add_todo.xml
new file mode 100644
index 0000000..5e35bb1
--- /dev/null
+++ b/app/src/main/res/layout/dialog_add_todo.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_progress.xml b/app/src/main/res/layout/fragment_progress.xml
new file mode 100644
index 0000000..64e65cb
--- /dev/null
+++ b/app/src/main/res/layout/fragment_progress.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_todo.xml b/app/src/main/res/layout/fragment_todo.xml
new file mode 100644
index 0000000..4f9a32d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_todo.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/todo_item.xml b/app/src/main/res/layout/todo_item.xml
new file mode 100644
index 0000000..6767100
--- /dev/null
+++ b/app/src/main/res/layout/todo_item.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
new file mode 100644
index 0000000..2c4e87d
--- /dev/null
+++ b/app/src/main/res/menu/main.xml
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..345888d
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..b698387
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..4bce769
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100644
index 0000000..f75a929
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..5cfaa2c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000..b8a2995
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d4457c9
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round_background.png
new file mode 100644
index 0000000..f75a929
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round_background.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round_foreground.png
new file mode 100644
index 0000000..5cfaa2c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round_monochrome.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round_monochrome.png
new file mode 100644
index 0000000..bdb9fb6
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round_monochrome.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..7c42fc8
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100644
index 0000000..62abb68
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..b66eb7d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000..b710d3a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..939206a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round_background.png
new file mode 100644
index 0000000..62abb68
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round_background.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round_foreground.png
new file mode 100644
index 0000000..b66eb7d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round_monochrome.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round_monochrome.png
new file mode 100644
index 0000000..6375041
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round_monochrome.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..4ec1f5f
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..ca84278
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..2711c0e
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000..7537ab8
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1e0ea67
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_background.png
new file mode 100644
index 0000000..ca84278
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_background.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_foreground.png
new file mode 100644
index 0000000..2711c0e
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round_monochrome.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_monochrome.png
new file mode 100644
index 0000000..2dd0a42
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round_monochrome.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..485bb14
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..3ecea46
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..23a1723
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000..87e0075
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..96c249b
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_background.png
new file mode 100644
index 0000000..3ecea46
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_background.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_foreground.png
new file mode 100644
index 0000000..23a1723
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_monochrome.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_monochrome.png
new file mode 100644
index 0000000..34aabec
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_monochrome.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..7275110
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..bcdf6ad
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..0310a45
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000..049ac1b
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..30efdbb
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_background.png
new file mode 100644
index 0000000..bcdf6ad
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_background.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_foreground.png
new file mode 100644
index 0000000..0310a45
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_monochrome.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_monochrome.png
new file mode 100644
index 0000000..45efed0
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_monochrome.png differ
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..ceb654e
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,143 @@
+
+ #A0CAFD
+ #003258
+ #1A4975
+ #D1E4FF
+ #BBC7DB
+ #253140
+ #3B4858
+ #D7E3F8
+ #D7BEE4
+ #3B2948
+ #523F5F
+ #F3DAFF
+ #FFB4AB
+ #690005
+ #93000A
+ #FFDAD6
+ #111418
+ #E1E2E8
+ #111418
+ #E1E2E8
+ #43474E
+ #C3C6CF
+ #8D9199
+ #43474E
+ #000000
+ #E1E2E8
+ #2E3135
+ #36618E
+ #D1E4FF
+ #001D36
+ #A0CAFD
+ #1A4975
+ #D7E3F8
+ #101C2B
+ #BBC7DB
+ #3B4858
+ #F3DAFF
+ #251431
+ #D7BEE4
+ #523F5F
+ #111418
+ #36393E
+ #0B0E13
+ #191C20
+ #1D2024
+ #272A2F
+ #32353A
+ #A7CEFF
+ #00172E
+ #6B94C4
+ #000000
+ #BFCCDF
+ #0A1725
+ #8592A4
+ #000000
+ #DBC2E9
+ #1F0F2C
+ #9F88AD
+ #000000
+ #FFBAB1
+ #370001
+ #FF5449
+ #000000
+ #111418
+ #E1E2E8
+ #111418
+ #FAFAFF
+ #43474E
+ #C7CBD3
+ #9FA3AB
+ #7F838B
+ #000000
+ #E1E2E8
+ #272A2F
+ #1B4A76
+ #D1E4FF
+ #001225
+ #A0CAFD
+ #003862
+ #D7E3F8
+ #051220
+ #BBC7DB
+ #2B3746
+ #F3DAFF
+ #1A0926
+ #D7BEE4
+ #412F4E
+ #111418
+ #36393E
+ #0B0E13
+ #191C20
+ #1D2024
+ #272A2F
+ #32353A
+ #FAFAFF
+ #000000
+ #A7CEFF
+ #000000
+ #FAFAFF
+ #000000
+ #BFCCDF
+ #000000
+ #FFF9FB
+ #000000
+ #DBC2E9
+ #000000
+ #FFF9F9
+ #000000
+ #FFBAB1
+ #000000
+ #111418
+ #E1E2E8
+ #111418
+ #FFFFFF
+ #43474E
+ #FAFAFF
+ #C7CBD3
+ #C7CBD3
+ #000000
+ #E1E2E8
+ #000000
+ #002B4E
+ #D9E8FF
+ #000000
+ #A7CEFF
+ #00172E
+ #DBE8FC
+ #000000
+ #BFCCDF
+ #0A1725
+ #F5DFFF
+ #000000
+ #DBC2E9
+ #1F0F2C
+ #111418
+ #36393E
+ #0B0E13
+ #191C20
+ #1D2024
+ #272A2F
+ #32353A
+
diff --git a/app/src/main/res/values-night/theme_overlays.xml b/app/src/main/res/values-night/theme_overlays.xml
new file mode 100644
index 0000000..bcb9d03
--- /dev/null
+++ b/app/src/main/res/values-night/theme_overlays.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..ffd70e6
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,50 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml
new file mode 100644
index 0000000..7be0b54
--- /dev/null
+++ b/app/src/main/res/values-v23/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/arrays.xml b/app/src/main/res/values-zh-rCN/arrays.xml
new file mode 100644
index 0000000..d226d83
--- /dev/null
+++ b/app/src/main/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,24 @@
+
+
+
+ - 字词小故事
+ - 诊断
+ - E 听说
+ - 卷子
+ - 试卷整理
+ - 改错
+ - 每周一练
+ - 作文
+
+
+ - 跟随系统设置
+ - 开启
+ - 关闭
+
+
+
+ - 0
+ - 1
+ - 2
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..cc19fe8
--- /dev/null
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,42 @@
+
+
+ 待办
+
+ 关于
+ Super12138
+ 检查更新
+
+ 应用程序出现错误
+ 别担心,下方是错误日志,请全选复制然后把它发送给我,我会尽快解决问题
+ 没有日志传入
+ 退出应用
+
+ 未知
+ 语文
+ 数学
+ 英语
+ 生物
+ 物理
+ 历史
+ 地理
+ 道法
+
+ 取消
+ 确定
+ 添加待办
+ 警告
+ 即将删除全部待办,是否继续?
+ 待办内容
+ 删除
+ 删除全部代办
+ 标记为已完成
+ 无待办事项
+ 撤销
+ 该待办已删除
+ 设置
+ 外观
+ 深色模式
+ 开启或关闭应用深色模式
+ 在 GitHub 上查看源代码
+ 配置
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..4c9caa7
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,24 @@
+
+
+
+ - 字词小故事
+ - 诊断
+ - E 听说
+ - 卷子
+ - 试卷整理
+ - 改错
+ - 每周一练
+ - 作文
+
+
+ - Follow system
+ - On
+ - Off
+
+
+
+ - 0
+ - 1
+ - 2
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..54fe9d3
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,147 @@
+
+
+ #FF000000
+ #FFFFFFFF
+ #0061A4
+ #36618E
+ #FFFFFF
+ #D1E4FF
+ #001D36
+ #535F70
+ #FFFFFF
+ #D7E3F8
+ #101C2B
+ #6B5778
+ #FFFFFF
+ #F3DAFF
+ #251431
+ #BA1A1A
+ #FFFFFF
+ #FFDAD6
+ #410002
+ #F8F9FF
+ #191C20
+ #F8F9FF
+ #191C20
+ #DFE2EB
+ #43474E
+ #73777F
+ #C3C6CF
+ #000000
+ #2E3135
+ #EFF0F7
+ #A0CAFD
+ #D1E4FF
+ #001D36
+ #A0CAFD
+ #1A4975
+ #D7E3F8
+ #101C2B
+ #BBC7DB
+ #3B4858
+ #F3DAFF
+ #251431
+ #D7BEE4
+ #523F5F
+ #D8DAE0
+ #F8F9FF
+ #FFFFFF
+ #F2F3FA
+ #ECEEF4
+ #E6E8EE
+ #E1E2E8
+ #144571
+ #FFFFFF
+ #4E77A6
+ #FFFFFF
+ #374454
+ #FFFFFF
+ #697687
+ #FFFFFF
+ #4E3B5B
+ #FFFFFF
+ #826D8F
+ #FFFFFF
+ #8C0009
+ #FFFFFF
+ #DA342E
+ #FFFFFF
+ #F8F9FF
+ #191C20
+ #F8F9FF
+ #191C20
+ #DFE2EB
+ #3F434A
+ #5B5F67
+ #777B83
+ #000000
+ #2E3135
+ #EFF0F7
+ #A0CAFD
+ #4E77A6
+ #FFFFFF
+ #335E8C
+ #FFFFFF
+ #697687
+ #FFFFFF
+ #515D6E
+ #FFFFFF
+ #826D8F
+ #FFFFFF
+ #685475
+ #FFFFFF
+ #D8DAE0
+ #F8F9FF
+ #FFFFFF
+ #F2F3FA
+ #ECEEF4
+ #E6E8EE
+ #E1E2E8
+ #002341
+ #FFFFFF
+ #144571
+ #FFFFFF
+ #172332
+ #FFFFFF
+ #374454
+ #FFFFFF
+ #2C1B38
+ #FFFFFF
+ #4E3B5B
+ #FFFFFF
+ #4E0002
+ #FFFFFF
+ #8C0009
+ #FFFFFF
+ #F8F9FF
+ #191C20
+ #F8F9FF
+ #000000
+ #DFE2EB
+ #20242B
+ #3F434A
+ #3F434A
+ #000000
+ #2E3135
+ #FFFFFF
+ #E2EDFF
+ #144571
+ #FFFFFF
+ #002E52
+ #FFFFFF
+ #374454
+ #FFFFFF
+ #212E3D
+ #FFFFFF
+ #4E3B5B
+ #FFFFFF
+ #372644
+ #FFFFFF
+ #D8DAE0
+ #F8F9FF
+ #FFFFFF
+ #F2F3FA
+ #ECEEF4
+ #E6E8EE
+ #E1E2E8
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..756a418
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,42 @@
+
+ To Do
+
+ About
+ Super12138
+ Check for updates
+ View source on GitHub
+
+ Oops! App went wrong
+ Don\'t worry, crash logs under there! Please copy them and send to me, I\'ll solve this problem
+ No crash logs
+ Exit app
+
+ Unknown
+ Chinese
+ Math
+ English
+ Biology
+ Physics
+ History
+ Geography
+ Law
+
+ Cancel
+ OK
+ Add Task
+ Warning
+ Are you sure you want to delete all tasks?
+ Tasks
+ Delete
+ Delete all tasks
+ Checked this task
+ No tasks
+ Undo
+ This task has been deleted
+
+ Settings
+ Appearance
+ Dark mode
+ Enable or disable the dark mode for this app
+ Config
+
\ No newline at end of file
diff --git a/app/src/main/res/values/theme_overlays.xml b/app/src/main/res/values/theme_overlays.xml
new file mode 100644
index 0000000..240fb67
--- /dev/null
+++ b/app/src/main/res/values/theme_overlays.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..1684e13
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
new file mode 100644
index 0000000..7b50bf7
--- /dev/null
+++ b/app/src/main/res/xml/preferences.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt b/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt
new file mode 100644
index 0000000..8116211
--- /dev/null
+++ b/app/src/test/kotlin/cn/super12138/todo/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package cn.super12138.todo
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..174c2c7
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id("com.android.application") version "8.2.0" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.10" apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..3c5031e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# 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. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec: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
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# 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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..dfa56cd
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Nov 18 14:21:56 CST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/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/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/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/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..d600cc5
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "ToDo"
+include(":app")