开始使用 Jetpack Compose 对待办重构
This commit is contained in:
parent
0b2384fb88
commit
2d7b3edf15
197 changed files with 478 additions and 8746 deletions
3
app/.gitignore
vendored
3
app/.gitignore
vendored
|
|
@ -1,2 +1 @@
|
||||||
/build
|
/build
|
||||||
/release
|
|
||||||
|
|
@ -1,51 +1,28 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.compose)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
|
alias(libs.plugins.aboutlibraries)
|
||||||
}
|
}
|
||||||
ksp {
|
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseVersionName = "1.0.6"
|
|
||||||
val commitHash by lazy { "git rev-parse --short HEAD".exec() }
|
|
||||||
val verCode by lazy { "git rev-list --count HEAD".exec().toInt() }
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "cn.super12138.todo"
|
namespace = "cn.super12138.todo"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
||||||
// 获取 Release 签名
|
|
||||||
val releaseSigning = if (project.hasProperty("releaseStoreFile")) {
|
|
||||||
signingConfigs.create("release") {
|
|
||||||
storeFile = File(project.properties["releaseStoreFile"] as String)
|
|
||||||
storePassword = project.properties["releaseStorePassword"] as String
|
|
||||||
keyAlias = project.properties["releaseKeyAlias"] as String
|
|
||||||
keyPassword = project.properties["releaseKeyPassword"] as String
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
signingConfigs.getByName("debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "cn.super12138.todo"
|
applicationId = "cn.super12138.todo"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = verCode
|
versionCode = 1
|
||||||
versionName = "${baseVersionName}-${commitHash}"
|
versionName = "1.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
base.archivesName.set("todo-${baseVersionName}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
all {
|
|
||||||
signingConfig = releaseSigning
|
|
||||||
}
|
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
|
|
@ -53,52 +30,54 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_18
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_18
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "18"
|
jvmTarget = "21"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
compose = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// Android X
|
// Android X
|
||||||
implementation(libs.androidx.core)
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.core.core.splashscreen)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.compose.runtime.livedata)
|
||||||
implementation(libs.androidx.activity.ktx)
|
// implementation(libs.androidx.security)
|
||||||
implementation(libs.androidx.constraintlayout)
|
// Compose
|
||||||
implementation(libs.androidx.fragment)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.fragment.ktx)
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.recyclerview)
|
implementation(libs.androidx.animation)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel)
|
implementation(libs.androidx.navigation)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.preference)
|
implementation(libs.androidx.ui.android)
|
||||||
implementation(libs.androidx.preference.ktx)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.security)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
// Material Design
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.material)
|
implementation(libs.androidx.material.icon.core)
|
||||||
|
implementation(libs.androidx.material.icon.extended)
|
||||||
|
// About Libraries
|
||||||
|
implementation(libs.aboutlibraries.core)
|
||||||
|
implementation(libs.aboutlibraries.compose)
|
||||||
|
// Kotlin Coroutines
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
// Room
|
// Room
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
annotationProcessor(libs.androidx.room.compiler)
|
annotationProcessor(libs.androidx.room.compiler)
|
||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
// FastScroll
|
|
||||||
implementation(project(":fastscroll"))
|
|
||||||
// Test
|
// Test
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
}
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
fun String.exec(): String = exec(this)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
fun Project.exec(command: String): String = providers.exec {
|
}
|
||||||
commandLine(command.split(" "))
|
|
||||||
}.standardOutput.asText.get().trim()
|
|
||||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
|
|
@ -18,6 +18,4 @@
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
-dontwarn javax.annotation.Nullable
|
|
||||||
-dontwarn javax.annotation.concurrent.GuardedBy
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 1,
|
|
||||||
"identityHash": "bf23f9a3f857e7cfe28dce9eb1657534",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "todo",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `state` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "uuid",
|
|
||||||
"columnName": "uuid",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "state",
|
|
||||||
"columnName": "state",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "subject",
|
|
||||||
"columnName": "subject",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "content",
|
|
||||||
"columnName": "content",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"uuid"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bf23f9a3f857e7cfe28dce9eb1657534')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package cn.super12138.todo
|
package cn.super12138.todo
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import org.junit.Assert.assertEquals
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,26 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".ToDoApp"
|
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:appCategory="productivity"
|
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.ToDo.Starting"
|
android:theme="@style/Theme.ToDo"
|
||||||
tools:targetApi="35">
|
tools:targetApi="31">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".views.activities.MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask">
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.ToDo">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".views.activities.CrashActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:theme="@style/Theme.ToDo" />
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
47
app/src/main/kotlin/cn/super12138/todo/MainActivity.kt
Normal file
47
app/src/main/kotlin/cn/super12138/todo/MainActivity.kt
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package cn.super12138.todo
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import cn.super12138.todo.ui.theme.ToDoTheme
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
ToDoTheme {
|
||||||
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
|
Greeting(
|
||||||
|
name = "Android",
|
||||||
|
modifier = Modifier.padding(innerPadding)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||||
|
Text(
|
||||||
|
text = "Hello $name!",
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun GreetingPreview() {
|
||||||
|
ToDoTheme {
|
||||||
|
Greeting("Android")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package cn.super12138.todo
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import cn.super12138.todo.logic.dao.ToDoRoomDB
|
|
||||||
import cn.super12138.todo.views.activities.CrashHandler
|
|
||||||
import com.google.android.material.color.DynamicColors
|
|
||||||
|
|
||||||
class ToDoApp : Application() {
|
|
||||||
private val database by lazy { ToDoRoomDB.getDatabase(this) }
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
lateinit var context: Context
|
|
||||||
lateinit var db: ToDoRoomDB
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
|
||||||
context = applicationContext
|
|
||||||
|
|
||||||
val crashHandler = CrashHandler(this)
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
|
|
||||||
|
|
||||||
db = database
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package cn.super12138.todo.constant
|
|
||||||
|
|
||||||
object Constants {
|
|
||||||
const val WELCOME_PAGE= "welcome_page"
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
const val SP_NAME = "cn.super12138.todo_preferences"
|
|
||||||
|
|
||||||
const val PREF_DARK_MODE = "dark_mode"
|
|
||||||
const val PREF_SECURE_MODE = "secure_mode"
|
|
||||||
const val PREF_HAPTIC_FEEDBACK = "haptic_feedback"
|
|
||||||
const val PREF_ALL_TASKS = "all_tasks"
|
|
||||||
const val PREF_REENTER_WELCOME_ACTIVITY = "reenter_welcome_activity"
|
|
||||||
const val PREF_ABOUT = "about"
|
|
||||||
const val PREF_DEV_MODE = "dev_mode"
|
|
||||||
// const val PREF_SPRING_FESTIVAL_THEME = "spring_festival_theme"
|
|
||||||
const val PREF_BACKUP_DB = "backup_db"
|
|
||||||
const val PREF_RESTORE_DB = "restore_db"
|
|
||||||
|
|
||||||
|
|
||||||
const val STRING_DEV_MODE = "/DEV_MODE"
|
|
||||||
|
|
||||||
const val TAG_INFO_BOTTOM_SHEET = "InfoBottomSheet"
|
|
||||||
const val TAG_TODO_BOTTOM_SHEET = "ToDoBottomSheet"
|
|
||||||
|
|
||||||
const val BUNDLE_EDIT_MODE = "editMode"
|
|
||||||
const val BUNDLE_POSITION = "todoPosition"
|
|
||||||
const val BUNDLE_TODO_CONTENT = "todoContent"
|
|
||||||
const val BUNDLE_TODO_SUBJECT = "todoSubject"
|
|
||||||
const val BUNDLE_TODO_STATE = "todoState"
|
|
||||||
const val BUNDLE_TODO_UUID = "todoUUID"
|
|
||||||
|
|
||||||
const val EMPTY_VIEW_TYPE = 0
|
|
||||||
const val DEFAULT_VIEW_TYPE = 1
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package cn.super12138.todo.constant
|
|
||||||
|
|
||||||
import cn.super12138.todo.utils.sp.SPDelegates
|
|
||||||
|
|
||||||
object GlobalValues {
|
|
||||||
var welcomePage: Boolean by SPDelegates(Constants.WELCOME_PAGE, false)
|
|
||||||
var darkMode: String by SPDelegates(Constants.PREF_DARK_MODE, "0")
|
|
||||||
var devMode: Boolean by SPDelegates(Constants.PREF_DEV_MODE, false)
|
|
||||||
// var springFestivalTheme: Boolean by SPDelegates(Constants.PREF_SPRING_FESTIVAL_THEME, false)
|
|
||||||
var secureMode: Boolean by SPDelegates(Constants.PREF_SECURE_MODE, false)
|
|
||||||
var hapticFeedback: Boolean by SPDelegates(Constants.PREF_HAPTIC_FEEDBACK, true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
package cn.super12138.todo.logic
|
|
||||||
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
import cn.super12138.todo.logic.dao.ToDoRoom
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
object Repository {
|
|
||||||
|
|
||||||
// Room
|
|
||||||
private val db get() = ToDoApp.db
|
|
||||||
private val todoDao = db.toDoRoomDao()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param toDoRoom 要插入的数据
|
|
||||||
*/
|
|
||||||
suspend fun insert(toDoRoom: ToDoRoom) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
todoDao.insert(toDoRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取全部未完成的待办
|
|
||||||
* @return List<ToDoRoom>
|
|
||||||
*/
|
|
||||||
suspend fun getAllIncomplete(): List<ToDoRoom> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
todoDao.getAllUnfinished()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取全部已完成的待办
|
|
||||||
* @return List<ToDoRoom>
|
|
||||||
*/
|
|
||||||
suspend fun getAllComplete(): List<ToDoRoom> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
todoDao.getAllComplete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取全部待办
|
|
||||||
* @return List<ToDoRoom>
|
|
||||||
*/
|
|
||||||
suspend fun getAll(): List<ToDoRoom> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
todoDao.getAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据待办的UUID删除指定待办
|
|
||||||
* @param uuid 待办的UUID
|
|
||||||
*/
|
|
||||||
suspend fun deleteByUUID(uuid: String) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
todoDao.deleteByUUID(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除全部代办
|
|
||||||
*/
|
|
||||||
suspend fun deleteAll() {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
todoDao.deleteAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据代办的UUID来把待办状态更新为“已完成”
|
|
||||||
* @param uuid 待办的UUID
|
|
||||||
*/
|
|
||||||
suspend fun updateStateByUUID(uuid: String) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
todoDao.updateStateByUUID(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun update(toDoRoom: ToDoRoom) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
todoDao.update(toDoRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package cn.super12138.todo.logic.dao
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uuid String 待办的uuid
|
|
||||||
* @param state Int 待办的完成状态,0表示未完成,1表示完成
|
|
||||||
* @param subject String 待办的学科
|
|
||||||
* @param content String 待办的内容
|
|
||||||
*/
|
|
||||||
@Entity(tableName = "todo")
|
|
||||||
data class ToDoRoom(
|
|
||||||
@PrimaryKey @ColumnInfo(name = "uuid") val uuid: String,
|
|
||||||
@ColumnInfo(name = "state") val state: Int,
|
|
||||||
@ColumnInfo(name = "subject") val subject: String,
|
|
||||||
@ColumnInfo(name = "content") val content: String
|
|
||||||
)
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package cn.super12138.todo.logic.dao
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.Database
|
|
||||||
import androidx.room.Room
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
|
|
||||||
@Database(entities = [ToDoRoom::class], version = 1)
|
|
||||||
abstract class ToDoRoomDB : RoomDatabase() {
|
|
||||||
abstract fun toDoRoomDao(): ToDoRoomDao
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Volatile
|
|
||||||
private var INSTANCE: ToDoRoomDB? = null
|
|
||||||
fun getDatabase(context: Context): ToDoRoomDB {
|
|
||||||
return INSTANCE ?: synchronized(this) {
|
|
||||||
val instance = Room.databaseBuilder(
|
|
||||||
context.applicationContext,
|
|
||||||
ToDoRoomDB::class.java,
|
|
||||||
"todo"
|
|
||||||
).build()
|
|
||||||
|
|
||||||
INSTANCE = instance
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package cn.super12138.todo.logic.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Update
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface ToDoRoomDao {
|
|
||||||
@Insert
|
|
||||||
suspend fun insert(toDoRoom: ToDoRoom)
|
|
||||||
|
|
||||||
@Query("SELECT * FROM todo")
|
|
||||||
suspend fun getAll(): List<ToDoRoom>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM todo WHERE state = 0")
|
|
||||||
suspend fun getAllUnfinished(): List<ToDoRoom>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM todo WHERE state = 1")
|
|
||||||
suspend fun getAllComplete(): List<ToDoRoom>
|
|
||||||
|
|
||||||
@Query("DELETE FROM todo")
|
|
||||||
suspend fun deleteAll()
|
|
||||||
|
|
||||||
@Query("DELETE FROM todo WHERE uuid = :uuid")
|
|
||||||
suspend fun deleteByUUID(uuid: String)
|
|
||||||
|
|
||||||
@Query("UPDATE todo SET state = 1 WHERE uuid = :uuid")
|
|
||||||
suspend fun updateStateByUUID(uuid: String)
|
|
||||||
|
|
||||||
@Update
|
|
||||||
suspend fun update(toDoRoom: ToDoRoom)
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package cn.super12138.todo.logic.model
|
|
||||||
|
|
||||||
data class ToDo(val uuid: String, val state: Int, val content: String, val subject: String) {
|
|
||||||
// var isAnimated = false
|
|
||||||
}
|
|
||||||
11
app/src/main/kotlin/cn/super12138/todo/ui/theme/Color.kt
Normal file
11
app/src/main/kotlin/cn/super12138/todo/ui/theme/Color.kt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cn.super12138.todo.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
|
val Purple40 = Color(0xFF6650a4)
|
||||||
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
val Pink40 = Color(0xFF7D5260)
|
||||||
58
app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt
Normal file
58
app/src/main/kotlin/cn/super12138/todo/ui/theme/Theme.kt
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package cn.super12138.todo.ui.theme
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Purple40,
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
background = Color(0xFFFFFBFE),
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ToDoTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
34
app/src/main/kotlin/cn/super12138/todo/ui/theme/Type.kt
Normal file
34
app/src/main/kotlin/cn/super12138/todo/ui/theme/Type.kt
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package cn.super12138.todo.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
/* Other default text styles to override
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
lineHeight = 28.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package cn.super12138.todo.utils
|
|
||||||
|
|
||||||
import android.text.Editable
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
|
|
||||||
object TextUtils {
|
|
||||||
// 更换为id匹配(数据库)
|
|
||||||
private val subjectMap = mapOf(
|
|
||||||
globalGetString(R.string.subject_chinese) to R.id.subject_chinese,
|
|
||||||
globalGetString(R.string.subject_math) to R.id.subject_math,
|
|
||||||
globalGetString(R.string.subject_english) to R.id.subject_english,
|
|
||||||
globalGetString(R.string.subject_biology) to R.id.subject_biology,
|
|
||||||
globalGetString(R.string.subject_geography) to R.id.subject_geography,
|
|
||||||
globalGetString(R.string.subject_history) to R.id.subject_history,
|
|
||||||
globalGetString(R.string.subject_physics) to R.id.subject_physics,
|
|
||||||
globalGetString(R.string.subject_chemistry) to R.id.subject_chemistry,
|
|
||||||
globalGetString(R.string.subject_law) to R.id.subject_law,
|
|
||||||
globalGetString(R.string.subject_other) to R.id.subject_other
|
|
||||||
)
|
|
||||||
|
|
||||||
fun getSubjectName(id: Int): String {
|
|
||||||
return when (id) {
|
|
||||||
R.id.subject_chinese -> globalGetString(R.string.subject_chinese)
|
|
||||||
R.id.subject_math -> globalGetString(R.string.subject_math)
|
|
||||||
R.id.subject_english -> globalGetString(R.string.subject_english)
|
|
||||||
R.id.subject_biology -> globalGetString(R.string.subject_biology)
|
|
||||||
R.id.subject_geography -> globalGetString(R.string.subject_geography)
|
|
||||||
R.id.subject_history -> globalGetString(R.string.subject_history)
|
|
||||||
R.id.subject_physics -> globalGetString(R.string.subject_physics)
|
|
||||||
R.id.subject_chemistry -> globalGetString(R.string.subject_chemistry)
|
|
||||||
R.id.subject_law -> globalGetString(R.string.subject_law)
|
|
||||||
R.id.subject_other -> globalGetString(R.string.subject_other)
|
|
||||||
else -> globalGetString(R.string.subject_unknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSubjectID(name: String): Int? {
|
|
||||||
return subjectMap[name]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun globalGetString(resID: Int): String {
|
|
||||||
return ToDoApp.context.resources.getString(resID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.toEditable(): Editable? {
|
|
||||||
return Editable.Factory.getInstance().newEditable(this)
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package cn.super12138.todo.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.SystemClock
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
|
|
||||||
private var clickInterval = 380L
|
|
||||||
private var lastTime = 0L
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟点击
|
|
||||||
*/
|
|
||||||
fun View.setOnIntervalClickListener(onIntervalClickListener: (View) -> Unit) {
|
|
||||||
this.setOnClickListener {
|
|
||||||
if (SystemClock.elapsedRealtime() - lastTime > clickInterval) {
|
|
||||||
lastTime = SystemClock.elapsedRealtime()
|
|
||||||
onIntervalClickListener.invoke(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toast
|
|
||||||
*/
|
|
||||||
fun String.showToast(
|
|
||||||
context: Context = ToDoApp.context,
|
|
||||||
duration: Int = Toast.LENGTH_SHORT
|
|
||||||
) {
|
|
||||||
Toast.makeText(context, this, duration).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Int.showToast(context: Context = ToDoApp.context, duration: Int = Toast.LENGTH_SHORT) {
|
|
||||||
Toast.makeText(context, this, duration).show()
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package cn.super12138.todo.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
|
|
||||||
object VersionUtils {
|
|
||||||
/**
|
|
||||||
* 获取应用版本号
|
|
||||||
* @return 版本名称(版本代码)
|
|
||||||
*/
|
|
||||||
fun getAppVersion(context: Context): String {
|
|
||||||
val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
||||||
val verName = pkgInfo.versionName
|
|
||||||
val verCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
pkgInfo.longVersionCode.toInt()
|
|
||||||
} else {
|
|
||||||
pkgInfo.versionCode
|
|
||||||
}
|
|
||||||
return "$verName ($verCode)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package cn.super12138.todo.utils
|
|
||||||
|
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import android.view.View
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
|
|
||||||
object VibrationUtils {
|
|
||||||
fun performHapticFeedback(
|
|
||||||
view: View?,
|
|
||||||
hapticFeedbackConstants: Int = HapticFeedbackConstants.KEYBOARD_TAP
|
|
||||||
) {
|
|
||||||
if (GlobalValues.hapticFeedback) {
|
|
||||||
view?.performHapticFeedback(hapticFeedbackConstants)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package cn.super12138.todo.utils.sp
|
|
||||||
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
class SPDelegates<T>(private val key: String, private val default: T) : ReadWriteProperty<Any?, T> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
|
||||||
return SPUtils.getValue(key, default)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
|
||||||
SPUtils.putValue(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package cn.super12138.todo.utils.sp
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
import cn.super12138.todo.constant.Constants
|
|
||||||
|
|
||||||
object SPUtils {
|
|
||||||
private val sp: SharedPreferences by lazy {
|
|
||||||
ToDoApp.context.getSharedPreferences(Constants.SP_NAME, Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> getValue(name: String, default: T): T = with(sp) {
|
|
||||||
val res: Any = when (default) {
|
|
||||||
is Long -> getLong(name, default)
|
|
||||||
is String -> getString(name, default).orEmpty()
|
|
||||||
is Int -> getInt(name, default)
|
|
||||||
is Boolean -> getBoolean(name, default)
|
|
||||||
is Float -> getFloat(name, default)
|
|
||||||
else -> throw java.lang.IllegalArgumentException()
|
|
||||||
}
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
res as T
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> putValue(name: String, value: T) = with(sp.edit()) {
|
|
||||||
when (value) {
|
|
||||||
is Long -> putLong(name, value)
|
|
||||||
is String -> putString(name, value)
|
|
||||||
is Int -> putInt(name, value)
|
|
||||||
is Boolean -> putBoolean(name, value)
|
|
||||||
is Float -> putFloat(name, value)
|
|
||||||
else -> throw IllegalArgumentException("This type can't be saved into Preferences")
|
|
||||||
}.apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package cn.super12138.todo.views
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.viewbinding.ViewBinding
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
|
|
||||||
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
|
|
||||||
lateinit var binding: T
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
/*if (GlobalValues.springFestivalTheme) {
|
|
||||||
setTheme(R.style.Theme_SpringFestival)
|
|
||||||
}*/
|
|
||||||
// enableEdgeToEdge(navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT))
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
// 确保 Navigation Bar 区域会被显示
|
|
||||||
// WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
|
|
||||||
binding = getViewBinding()
|
|
||||||
setContentView(binding.root)
|
|
||||||
|
|
||||||
// 深色模式
|
|
||||||
when (GlobalValues.darkMode) {
|
|
||||||
"0" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
|
||||||
|
|
||||||
"1" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
|
||||||
|
|
||||||
"2" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 适配刘海屏
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun getViewBinding(): T
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package cn.super12138.todo.views
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.viewbinding.ViewBinding
|
|
||||||
import com.google.android.material.color.MaterialColors
|
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
|
||||||
|
|
||||||
abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
|
||||||
private var _binding: T? = null
|
|
||||||
protected val binding get() = _binding!!
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true)
|
|
||||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false)
|
|
||||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true)
|
|
||||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
_binding = getViewBinding(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/material-components/material-components-android/issues/1984#issuecomment-1089710991
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
// Overlap colors.
|
|
||||||
view.setBackgroundColor(MaterialColors.getColor(view, android.R.attr.colorBackground))
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): T
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
package cn.super12138.todo.views.activities
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import cn.super12138.todo.databinding.ActivityCrashBinding
|
|
||||||
import cn.super12138.todo.utils.VersionUtils
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.BaseActivity
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class CrashActivity : BaseActivity<ActivityCrashBinding>() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val crashLogs = intent.getStringExtra("crash_logs")
|
|
||||||
|
|
||||||
val deviceBrand = Build.BRAND
|
|
||||||
val deviceModel = Build.MODEL
|
|
||||||
val sdkLevel = Build.VERSION.SDK_INT
|
|
||||||
val currentDateTime = Calendar.getInstance().time
|
|
||||||
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
|
|
||||||
val formattedDateTime = formatter.format(currentDateTime)
|
|
||||||
|
|
||||||
val deviceInfo = StringBuilder().apply {
|
|
||||||
append("ToDo version: ").append(VersionUtils.getAppVersion(this@CrashActivity))
|
|
||||||
.append('\n')
|
|
||||||
append("Brand: ").append("").append(deviceBrand).append('\n')
|
|
||||||
append("Model: ").append(deviceModel).append('\n')
|
|
||||||
append("Device SDK: ").append(sdkLevel).append('\n').append('\n')
|
|
||||||
append("Crash time: ").append(formattedDateTime).append('\n').append('\n')
|
|
||||||
append("======beginning of crash======").append('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.crashLog.text = StringBuilder().apply {
|
|
||||||
append(deviceInfo)
|
|
||||||
append(crashLogs)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.exitApp.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
this.finishAffinity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(): ActivityCrashBinding {
|
|
||||||
return ActivityCrashBinding.inflate(layoutInflater)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package cn.super12138.todo.views.activities
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Process
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
// 传递异常给默认的异常处理器
|
|
||||||
defaultUEH?.uncaughtException(thread, ex)
|
|
||||||
|
|
||||||
// 杀掉崩溃的应用程序进程
|
|
||||||
Process.killProcess(Process.myPid())
|
|
||||||
exitProcess(10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
package cn.super12138.todo.views.activities
|
|
||||||
// 2023.11.18立项
|
|
||||||
import android.app.ComponentCaller
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.commit
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
import cn.super12138.todo.databinding.ActivityMainBinding
|
|
||||||
import cn.super12138.todo.views.BaseActivity
|
|
||||||
import cn.super12138.todo.views.fragments.SettingsParentFragment
|
|
||||||
import cn.super12138.todo.views.fragments.welcome.WelcomeFragment
|
|
||||||
import de.raphaelebner.roomdatabasebackup.core.RoomBackup
|
|
||||||
|
|
||||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|
||||||
lateinit var roomBackup: RoomBackup
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
installSplashScreen()
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
if (!GlobalValues.welcomePage) {
|
|
||||||
startFragment(WelcomeFragment())
|
|
||||||
}
|
|
||||||
|
|
||||||
when (GlobalValues.secureMode) {
|
|
||||||
true -> window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE
|
|
||||||
)
|
|
||||||
|
|
||||||
false -> window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
}
|
|
||||||
handleIntent(intent)
|
|
||||||
|
|
||||||
roomBackup = RoomBackup(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent, caller: ComponentCaller) {
|
|
||||||
super.onNewIntent(intent, caller)
|
|
||||||
handleIntent(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(): ActivityMainBinding {
|
|
||||||
return ActivityMainBinding.inflate(layoutInflater)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startFragment(fragment: Fragment, args: (Bundle.() -> Unit)? = null) {
|
|
||||||
supportFragmentManager.commit {
|
|
||||||
addToBackStack(System.currentTimeMillis().toString())
|
|
||||||
hide(supportFragmentManager.fragments.last())
|
|
||||||
add(
|
|
||||||
R.id.app_container,
|
|
||||||
fragment.apply { args?.let { arguments = Bundle().apply(it) } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIntent(intent: Intent) {
|
|
||||||
when (intent.action) {
|
|
||||||
Intent.ACTION_APPLICATION_PREFERENCES -> startFragment(SettingsParentFragment())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package cn.super12138.todo.views.adapters
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
import cn.super12138.todo.constant.Constants.DEFAULT_VIEW_TYPE
|
|
||||||
import cn.super12138.todo.constant.Constants.EMPTY_VIEW_TYPE
|
|
||||||
import cn.super12138.todo.logic.model.ToDo
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.fragments.InfoBottomSheet
|
|
||||||
|
|
||||||
class AllTasksAdapter(
|
|
||||||
private val todoList: MutableList<ToDo>,
|
|
||||||
private val fragmentManager: FragmentManager
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
// 默认待办项
|
|
||||||
inner class DefaultViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val todoContext: TextView = view.findViewById(R.id.todo_content)
|
|
||||||
val todoSubject: TextView = view.findViewById(R.id.todo_subject)
|
|
||||||
val itemBackground: LinearLayout = view.findViewById(R.id.item_background)
|
|
||||||
val checkBtn: Button = view.findViewById(R.id.check_item_btn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 空项目提示
|
|
||||||
inner class EmptyViewHolder(view: View) : RecyclerView.ViewHolder(view)
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return if (viewType == EMPTY_VIEW_TYPE) { // 如果列表是空的
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_empty, parent, false)
|
|
||||||
EmptyViewHolder(view)
|
|
||||||
} else {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_todo, parent, false)
|
|
||||||
DefaultViewHolder(view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return if (todoList.isEmpty()) EMPTY_VIEW_TYPE else DEFAULT_VIEW_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
// 判断当前的holder是不是待办项目的holder
|
|
||||||
if (holder is DefaultViewHolder) {
|
|
||||||
val todo = todoList[position]
|
|
||||||
holder.apply {
|
|
||||||
checkBtn.visibility = View.GONE
|
|
||||||
todoContext.text = todo.content
|
|
||||||
todoSubject.text = todo.subject
|
|
||||||
}
|
|
||||||
|
|
||||||
if (todo.state == 1) {
|
|
||||||
holder.itemBackground.background =
|
|
||||||
ContextCompat.getDrawable(ToDoApp.context, R.drawable.bg_item_complete)
|
|
||||||
} else {
|
|
||||||
holder.itemBackground.background = null
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.itemView.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
val infoBottomSheet = InfoBottomSheet.newInstance(
|
|
||||||
todo.content,
|
|
||||||
todo.subject,
|
|
||||||
todo.state,
|
|
||||||
todo.uuid
|
|
||||||
)
|
|
||||||
infoBottomSheet.show(fragmentManager, InfoBottomSheet.TAG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return if (todoList.isEmpty()) 1 else todoList.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
package cn.super12138.todo.views.adapters
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.ViewModelStoreOwner
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.constant.Constants.DEFAULT_VIEW_TYPE
|
|
||||||
import cn.super12138.todo.constant.Constants.EMPTY_VIEW_TYPE
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
import cn.super12138.todo.logic.model.ToDo
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.utils.showToast
|
|
||||||
import cn.super12138.todo.views.fragments.ToDoBottomSheet
|
|
||||||
import cn.super12138.todo.views.viewmodels.ProgressViewModel
|
|
||||||
import cn.super12138.todo.views.viewmodels.ToDoViewModel
|
|
||||||
|
|
||||||
class ToDoAdapter(
|
|
||||||
private val todoList: MutableList<ToDo>,
|
|
||||||
private val viewModelStoreOwner: ViewModelStoreOwner,
|
|
||||||
private val fragmentManager: FragmentManager
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
// 默认待办项
|
|
||||||
inner class DefaultViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val todoContext: TextView = view.findViewById(R.id.todo_content)
|
|
||||||
val todoSubject: TextView = view.findViewById(R.id.todo_subject)
|
|
||||||
val checkToDoBtn: Button = view.findViewById(R.id.check_item_btn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 空项目提示
|
|
||||||
inner class EmptyViewHolder(view: View) : RecyclerView.ViewHolder(view)
|
|
||||||
|
|
||||||
// 判断列表是否为空,为空显示空项目提示
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return if (todoList.isEmpty()) EMPTY_VIEW_TYPE else DEFAULT_VIEW_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return if (viewType == EMPTY_VIEW_TYPE) { // 如果列表是空的
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_empty, parent, false)
|
|
||||||
EmptyViewHolder(view)
|
|
||||||
} else {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_todo, parent, false)
|
|
||||||
DefaultViewHolder(view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
// 判断当前的holder是不是待办项目的holder
|
|
||||||
if (holder is DefaultViewHolder) {
|
|
||||||
val todo = todoList[position]
|
|
||||||
holder.todoContext.text = todo.content
|
|
||||||
holder.todoSubject.text = todo.subject
|
|
||||||
|
|
||||||
val progressViewModel =
|
|
||||||
ViewModelProvider(viewModelStoreOwner)[ProgressViewModel::class.java]
|
|
||||||
val todoViewModel =
|
|
||||||
ViewModelProvider(viewModelStoreOwner)[ToDoViewModel::class.java]
|
|
||||||
|
|
||||||
holder.checkToDoBtn.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
if (position >= todoList.size) {
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
todoList.removeAt(position)
|
|
||||||
notifyItemRemoved(position)
|
|
||||||
notifyItemRangeChanged(position, todoList.size)
|
|
||||||
|
|
||||||
todoViewModel.updateTaskState(todo.uuid)
|
|
||||||
|
|
||||||
progressViewModel.updateProgress()
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.itemView.setOnClickListener {
|
|
||||||
if (GlobalValues.devMode) {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
"Current position: $position".showToast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.itemView.setOnLongClickListener {
|
|
||||||
val toDoBottomSheet = ToDoBottomSheet.newInstance(
|
|
||||||
true,
|
|
||||||
position,
|
|
||||||
todo.uuid,
|
|
||||||
todo.state,
|
|
||||||
todo.subject,
|
|
||||||
todo.content
|
|
||||||
)
|
|
||||||
toDoBottomSheet.show(fragmentManager, ToDoBottomSheet.TAG)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return if (todoList.isEmpty()) 1 else todoList.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.constant.Constants
|
|
||||||
import cn.super12138.todo.databinding.FragmentAboutBinding
|
|
||||||
import cn.super12138.todo.utils.VersionUtils
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.utils.showToast
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
|
|
||||||
class AboutFragment : BaseFragment<FragmentAboutBinding>() {
|
|
||||||
private var clickCount = 0
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
binding.appVersion.text = VersionUtils.getAppVersion(requireActivity())
|
|
||||||
|
|
||||||
binding.toolBar.setNavigationOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
requireActivity().supportFragmentManager.popBackStack()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.checkUpdate.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
||||||
data = Uri.parse(Constants.UPDATE_URL)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.openSource.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
||||||
data = Uri.parse(Constants.REPO_GITHUB_URL)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
binding.developerInfo.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
||||||
data = Uri.parse(Constants.AUTHOR_GITHUB_URL)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.appVersion.setOnClickListener {
|
|
||||||
clickCount++
|
|
||||||
when (clickCount) {
|
|
||||||
5 -> {
|
|
||||||
clickCount = 0
|
|
||||||
// GlobalValues.springFestivalTheme = !GlobalValues.springFestivalTheme
|
|
||||||
"🍂".showToast()
|
|
||||||
/*when (java.util.Calendar.getInstance(Locale.getDefault())
|
|
||||||
.get(java.util.Calendar.MONTH) + 1) {
|
|
||||||
3, 4, 5 ->
|
|
||||||
6, 7, 8 -> SUMMER
|
|
||||||
9, 10, 11 -> AUTUMN
|
|
||||||
12, 1, 2 -> WINTER
|
|
||||||
else -> -12
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentAboutBinding {
|
|
||||||
return FragmentAboutBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
import cn.super12138.todo.databinding.FragmentAllTasksBinding
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
import cn.super12138.todo.views.adapters.AllTasksAdapter
|
|
||||||
import cn.super12138.todo.views.viewmodels.AllTasksViewModel
|
|
||||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
|
||||||
|
|
||||||
class AllTasksFragment : BaseFragment<FragmentAllTasksBinding>() {
|
|
||||||
private val viewModel by viewModels<AllTasksViewModel>()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
val todoListAll = viewModel.todoListAll
|
|
||||||
val layoutManager = LinearLayoutManager(ToDoApp.context)
|
|
||||||
binding.allTasksList.layoutManager = layoutManager
|
|
||||||
val adapter = AllTasksAdapter(todoListAll, childFragmentManager)
|
|
||||||
binding.allTasksList.adapter = adapter
|
|
||||||
|
|
||||||
FastScrollerBuilder(binding.allTasksList).apply {
|
|
||||||
useMd2Style()
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.toolBar.setNavigationOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
requireActivity().supportFragmentManager.popBackStack()
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.refreshData.observe(viewLifecycleOwner, Observer {
|
|
||||||
binding.allTasksList.adapter?.notifyItemRangeChanged(0, todoListAll.size + 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentAllTasksBinding {
|
|
||||||
return FragmentAllTasksBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.constant.Constants
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
import cn.super12138.todo.databinding.BottomSheetInfoBinding
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|
||||||
|
|
||||||
class InfoBottomSheet : BottomSheetDialogFragment() {
|
|
||||||
|
|
||||||
private lateinit var binding: BottomSheetInfoBinding
|
|
||||||
private lateinit var todoContent: String
|
|
||||||
private lateinit var todoSubject: String
|
|
||||||
private var todoState: Int = 0
|
|
||||||
private lateinit var todoUUID: String
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = Constants.TAG_INFO_BOTTOM_SHEET
|
|
||||||
fun newInstance(
|
|
||||||
todoContent: String,
|
|
||||||
todoSubject: String,
|
|
||||||
todoState: Int,
|
|
||||||
todoUUID: String
|
|
||||||
) = InfoBottomSheet().apply {
|
|
||||||
arguments = Bundle().apply {
|
|
||||||
putString(Constants.BUNDLE_TODO_CONTENT, todoContent)
|
|
||||||
putString(Constants.BUNDLE_TODO_SUBJECT, todoSubject)
|
|
||||||
putInt(Constants.BUNDLE_TODO_STATE, todoState)
|
|
||||||
putString(Constants.BUNDLE_TODO_UUID, todoUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
arguments?.let {
|
|
||||||
todoContent = it.getString(Constants.BUNDLE_TODO_CONTENT, "")
|
|
||||||
todoSubject = it.getString(Constants.BUNDLE_TODO_SUBJECT, "")
|
|
||||||
todoState = it.getInt(Constants.BUNDLE_TODO_STATE, 0)
|
|
||||||
todoUUID = it.getString(Constants.BUNDLE_TODO_UUID, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
binding = BottomSheetInfoBinding.inflate(inflater, container, false)
|
|
||||||
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
val bottomSheetDialog = dialog as BottomSheetDialog
|
|
||||||
bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
|
||||||
bottomSheetDialog.dismissWithAnimation = true
|
|
||||||
|
|
||||||
binding.todoContentInfo.text = todoContent
|
|
||||||
binding.todoSubjectInfo.text = String.format(getString(R.string.info_subject), todoSubject)
|
|
||||||
if (todoState == 0) {
|
|
||||||
binding.todoState.text = getString(R.string.info_state_incomplete)
|
|
||||||
} else {
|
|
||||||
binding.todoState.text = getString(R.string.info_state_complete)
|
|
||||||
}
|
|
||||||
if (GlobalValues.devMode) {
|
|
||||||
binding.todoUuid.apply {
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
text = String.format(getString(R.string.info_uuid), todoUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.databinding.FragmentMainBinding
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
import cn.super12138.todo.views.activities.MainActivity
|
|
||||||
|
|
||||||
class MainFragment : BaseFragment<FragmentMainBinding>() {
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
binding.toolBar.setOnMenuItemClickListener { menuItem ->
|
|
||||||
when (menuItem.itemId) {
|
|
||||||
R.id.item_settings -> {
|
|
||||||
(requireActivity() as MainActivity).startFragment(SettingsParentFragment())
|
|
||||||
|
|
||||||
VibrationUtils.performHapticFeedback(binding.toolBar)
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setSupportActionBar(binding.toolbar)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentMainBinding {
|
|
||||||
return FragmentMainBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.databinding.FragmentProgressBinding
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
import cn.super12138.todo.views.viewmodels.ProgressViewModel
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressFragment : BaseFragment<FragmentProgressBinding>() {
|
|
||||||
private val viewModel: ProgressViewModel by viewModels({ requireActivity() })
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
viewModel.progress.observe(viewLifecycleOwner, Observer { value ->
|
|
||||||
binding.progressBar.setProgressCompat(value, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
viewModel.totalCount.observe(viewLifecycleOwner, Observer { total ->
|
|
||||||
binding.totalCount.text = total.toString()
|
|
||||||
})
|
|
||||||
|
|
||||||
viewModel.completeCount.observe(viewLifecycleOwner, Observer { complete ->
|
|
||||||
binding.completeCount.text = complete.toString()
|
|
||||||
})
|
|
||||||
|
|
||||||
viewModel.remainCount.observe(viewLifecycleOwner, Observer { remain ->
|
|
||||||
if (remain == 0) {
|
|
||||||
binding.remainCount.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
binding.remainCount.visibility = View.VISIBLE
|
|
||||||
binding.remainCount.text =
|
|
||||||
String.format(getString(R.string.remain_text), remain.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
viewModel.updateProgress()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentProgressBinding {
|
|
||||||
return FragmentProgressBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,195 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.constant.Constants
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
import cn.super12138.todo.logic.dao.ToDoRoomDB
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.activities.MainActivity
|
|
||||||
import cn.super12138.todo.views.fragments.welcome.WelcomeFragment
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import de.raphaelebner.roomdatabasebackup.core.RoomBackup
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
|
||||||
val mainActivity = requireActivity() as MainActivity
|
|
||||||
val roomBackup = mainActivity.roomBackup
|
|
||||||
|
|
||||||
findPreference<ListPreference>(Constants.PREF_DARK_MODE)?.apply {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
setOnPreferenceChangeListener { _, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<SwitchPreferenceCompat>(Constants.PREF_SECURE_MODE)?.apply {
|
|
||||||
setOnPreferenceChangeListener { _, _ ->
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
|
|
||||||
|
|
||||||
view?.let {
|
|
||||||
Snackbar.make(it, R.string.need_restart_app, Snackbar.LENGTH_LONG)
|
|
||||||
.setAction(R.string.restart_app_now) {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
restartApp(context)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<SwitchPreferenceCompat>(Constants.PREF_HAPTIC_FEEDBACK)?.apply {
|
|
||||||
setOnPreferenceChangeListener { _, _ ->
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>(Constants.PREF_BACKUP_DB)?.apply {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
val simpleDateFormat =
|
|
||||||
SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())
|
|
||||||
val formattedDate = simpleDateFormat.format(Date())
|
|
||||||
roomBackup
|
|
||||||
.database(ToDoRoomDB.getDatabase(requireContext()))
|
|
||||||
.enableLogDebug(GlobalValues.devMode)
|
|
||||||
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
|
||||||
.customBackupFileName("ToDo-DataBase-$formattedDate.sqlite3")
|
|
||||||
.apply {
|
|
||||||
onCompleteListener { success, _, exitCode ->
|
|
||||||
if (success) {
|
|
||||||
view?.let { it1 ->
|
|
||||||
Snackbar.make(
|
|
||||||
it1, R.string.tips_backup_success, Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view?.let { it1 ->
|
|
||||||
Snackbar.make(
|
|
||||||
it1, getString(
|
|
||||||
R.string.tips_backup_failed,
|
|
||||||
exitCode
|
|
||||||
), Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.backup()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>(Constants.PREF_RESTORE_DB)?.apply {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
|
|
||||||
roomBackup
|
|
||||||
.database(ToDoRoomDB.getDatabase(requireContext()))
|
|
||||||
.enableLogDebug(GlobalValues.devMode)
|
|
||||||
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
|
||||||
.apply {
|
|
||||||
onCompleteListener { success, _, exitCode ->
|
|
||||||
if (success) {
|
|
||||||
view?.let { it1 ->
|
|
||||||
Snackbar.make(
|
|
||||||
it1,
|
|
||||||
R.string.tips_restore_success,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
)
|
|
||||||
.setAction(R.string.restart_app_now) {
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
restartApp(context)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
view?.let { it1 ->
|
|
||||||
Snackbar.make(
|
|
||||||
it1, getString(
|
|
||||||
R.string.tips_restore_failed,
|
|
||||||
exitCode
|
|
||||||
), Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.restore()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>(Constants.PREF_ALL_TASKS)?.apply {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
mainActivity.startFragment(AllTasksFragment())
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>(Constants.PREF_REENTER_WELCOME_ACTIVITY)?.apply {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
mainActivity.startFragment(WelcomeFragment())
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>(Constants.PREF_ABOUT)?.apply {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
mainActivity.startFragment(AboutFragment())
|
|
||||||
VibrationUtils.performHapticFeedback(view)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setDivider(divider: Drawable?) {
|
|
||||||
super.setDivider(ColorDrawable(Color.TRANSPARENT))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setDividerHeight(height: Int) {
|
|
||||||
super.setDividerHeight(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restartApp(restartContext: Context) {
|
|
||||||
val intent = Intent(restartContext, MainActivity::class.java).apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
}
|
|
||||||
restartContext.startActivity(intent)
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.databinding.FragmentSettingsBinding
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
|
|
||||||
class SettingsParentFragment : BaseFragment<FragmentSettingsBinding>() {
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
binding.toolBar.setNavigationOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
requireActivity().supportFragmentManager.popBackStack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentSettingsBinding {
|
|
||||||
return FragmentSettingsBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.constant.Constants
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
import cn.super12138.todo.databinding.BottomSheetTodoBinding
|
|
||||||
import cn.super12138.todo.logic.dao.ToDoRoom
|
|
||||||
import cn.super12138.todo.logic.model.ToDo
|
|
||||||
import cn.super12138.todo.utils.TextUtils
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.utils.showToast
|
|
||||||
import cn.super12138.todo.utils.toEditable
|
|
||||||
import cn.super12138.todo.views.viewmodels.ProgressViewModel
|
|
||||||
import cn.super12138.todo.views.viewmodels.ToDoViewModel
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
class ToDoBottomSheet : BottomSheetDialogFragment() {
|
|
||||||
private lateinit var binding: BottomSheetTodoBinding
|
|
||||||
|
|
||||||
private val progressViewModel: ProgressViewModel by viewModels({ requireActivity() })
|
|
||||||
private val todoViewModel: ToDoViewModel by viewModels({ requireActivity() })
|
|
||||||
|
|
||||||
private var editMode: Boolean = false
|
|
||||||
private var todoState: Int = 0
|
|
||||||
private var todoPosition: Int = 0
|
|
||||||
private lateinit var todoUUID: String
|
|
||||||
private lateinit var todoOrigSubject: String
|
|
||||||
private lateinit var todoOrigContent: String
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = Constants.TAG_TODO_BOTTOM_SHEET
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
editMode: Boolean,
|
|
||||||
todoPosition: Int,
|
|
||||||
todoUUID: String,
|
|
||||||
todoState: Int,
|
|
||||||
todoSubject: String,
|
|
||||||
todoContent: String,
|
|
||||||
) = ToDoBottomSheet().apply {
|
|
||||||
arguments = Bundle().apply {
|
|
||||||
putBoolean(Constants.BUNDLE_EDIT_MODE, editMode)
|
|
||||||
putInt(Constants.BUNDLE_POSITION, todoPosition)
|
|
||||||
putString(Constants.BUNDLE_TODO_UUID, todoUUID)
|
|
||||||
putInt(Constants.BUNDLE_TODO_STATE, todoState)
|
|
||||||
putString(Constants.BUNDLE_TODO_SUBJECT, todoSubject)
|
|
||||||
putString(Constants.BUNDLE_TODO_CONTENT, todoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
arguments?.let {
|
|
||||||
editMode = it.getBoolean(Constants.BUNDLE_EDIT_MODE, false)
|
|
||||||
todoPosition = it.getInt(Constants.BUNDLE_POSITION, 0)
|
|
||||||
todoOrigContent = it.getString(Constants.BUNDLE_TODO_CONTENT, "")
|
|
||||||
todoOrigSubject = it.getString(Constants.BUNDLE_TODO_SUBJECT, "")
|
|
||||||
todoState = it.getInt(Constants.BUNDLE_TODO_STATE, 0)
|
|
||||||
todoUUID = it.getString(Constants.BUNDLE_TODO_UUID, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
binding = BottomSheetTodoBinding.inflate(inflater, container, false)
|
|
||||||
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
// BottomSheet 基本参数设置
|
|
||||||
val bottomSheetDialog = dialog as BottomSheetDialog
|
|
||||||
bottomSheetDialog.behavior.apply {
|
|
||||||
state = BottomSheetBehavior.STATE_EXPANDED
|
|
||||||
saveFlags = BottomSheetBehavior.SAVE_ALL
|
|
||||||
}
|
|
||||||
bottomSheetDialog.dismissWithAnimation = true
|
|
||||||
|
|
||||||
val todoList = todoViewModel.todoList
|
|
||||||
|
|
||||||
binding.todoSubject.setOnCheckedStateChangeListener { _, _ ->
|
|
||||||
VibrationUtils.performHapticFeedback(binding.todoSubject)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑模式
|
|
||||||
if (editMode) {
|
|
||||||
binding.btnCancel.visibility = View.GONE
|
|
||||||
binding.btnDelete.visibility = View.VISIBLE
|
|
||||||
binding.todoSheetTitle.text = getString(R.string.update_task)
|
|
||||||
binding.todoContent.editText?.text = todoOrigContent.toEditable()
|
|
||||||
binding.btnSave.text = getString(R.string.update)
|
|
||||||
|
|
||||||
TextUtils.getSubjectID(todoOrigSubject)?.let { binding.todoSubject.check(it) }
|
|
||||||
|
|
||||||
if (GlobalValues.devMode) {
|
|
||||||
binding.todoUuid.apply {
|
|
||||||
text = "UUID: $todoUUID"
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.btnSave.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
val todoContent = binding.todoContent.editText?.text.toString()
|
|
||||||
// 内容判空
|
|
||||||
if (todoContent.isEmpty()) {
|
|
||||||
binding.todoContent.error =
|
|
||||||
getString(R.string.content_cannot_be_empty)
|
|
||||||
return@setOnClickListener
|
|
||||||
} else {
|
|
||||||
// 开发者模式
|
|
||||||
if (todoContent == Constants.STRING_DEV_MODE) {
|
|
||||||
if (GlobalValues.devMode) {
|
|
||||||
GlobalValues.devMode = false
|
|
||||||
} else {
|
|
||||||
GlobalValues.devMode = true
|
|
||||||
"Dev Mode".showToast()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 随机 UUID
|
|
||||||
val randomUUID = UUID.randomUUID().toString()
|
|
||||||
// 待办学科
|
|
||||||
val todoSubject = TextUtils.getSubjectName(binding.todoSubject.checkedChipId)
|
|
||||||
|
|
||||||
// 更新待办
|
|
||||||
if (editMode) {
|
|
||||||
todoViewModel.updateTask(
|
|
||||||
todoPosition,
|
|
||||||
ToDoRoom(
|
|
||||||
todoUUID,
|
|
||||||
todoState,
|
|
||||||
todoSubject,
|
|
||||||
todoContent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
todoViewModel.refreshData.value = 1
|
|
||||||
} else {
|
|
||||||
// 添加到 RecyclerView
|
|
||||||
todoList.add(
|
|
||||||
ToDo(randomUUID, 0, todoContent, todoSubject)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 插入数据库
|
|
||||||
todoViewModel.insertTask(
|
|
||||||
ToDoRoom(
|
|
||||||
randomUUID,
|
|
||||||
0,
|
|
||||||
todoSubject,
|
|
||||||
todoContent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
progressViewModel.updateProgress()
|
|
||||||
todoViewModel.addData.value = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.btnCancel.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.btnDelete.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
todoViewModel.deleteTask(todoPosition, todoUUID)
|
|
||||||
progressViewModel.updateProgress()
|
|
||||||
todoViewModel.removeData.value = 1
|
|
||||||
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.ToDoApp
|
|
||||||
import cn.super12138.todo.databinding.FragmentTodoBinding
|
|
||||||
import cn.super12138.todo.logic.Repository
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.adapters.ToDoAdapter
|
|
||||||
import cn.super12138.todo.views.viewmodels.ProgressViewModel
|
|
||||||
import cn.super12138.todo.views.viewmodels.ToDoViewModel
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
|
||||||
|
|
||||||
class ToDoFragment : Fragment() {
|
|
||||||
private val progressViewModel: ProgressViewModel by viewModels({ requireActivity() })
|
|
||||||
private val todoViewModel: ToDoViewModel by viewModels({ requireActivity() })
|
|
||||||
private lateinit var binding: FragmentTodoBinding
|
|
||||||
|
|
||||||
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)
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.addItem) { v, windowInsets ->
|
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
leftMargin = if (insets.left == 0) 16 else insets.left
|
|
||||||
bottomMargin = if (insets.bottom == 0) 48 else insets.bottom + 32
|
|
||||||
rightMargin = if (insets.right == 0) 48 else insets.right + 32
|
|
||||||
}
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
|
|
||||||
FastScrollerBuilder(binding.todoList).apply {
|
|
||||||
useMd2Style()
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val layoutManager = LinearLayoutManager(ToDoApp.context)
|
|
||||||
binding.todoList.layoutManager = layoutManager
|
|
||||||
val todoList = todoViewModel.todoList
|
|
||||||
val adapter = ToDoAdapter(todoList, requireActivity(), parentFragmentManager)
|
|
||||||
binding.todoList.adapter = adapter
|
|
||||||
|
|
||||||
binding.addItem.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
val toDoBottomSheet = ToDoBottomSheet()
|
|
||||||
toDoBottomSheet.show(parentFragmentManager, ToDoBottomSheet.TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.addItem.setOnLongClickListener {
|
|
||||||
activity?.let { it1 ->
|
|
||||||
MaterialAlertDialogBuilder(it1)
|
|
||||||
.setTitle(R.string.warning)
|
|
||||||
.setMessage(R.string.delete_confirm)
|
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
// 清除 Recycler View
|
|
||||||
todoList.clear()
|
|
||||||
// 清除数据库
|
|
||||||
lifecycleScope.launch {
|
|
||||||
Repository.deleteAll()
|
|
||||||
progressViewModel.updateProgress()
|
|
||||||
}
|
|
||||||
// 通知数据更改
|
|
||||||
binding.todoList.adapter?.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
.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) {
|
|
||||||
// 列表下滑,隐藏FAB
|
|
||||||
if (dy > 0 && fab.isShown) {
|
|
||||||
fab.hide()
|
|
||||||
}
|
|
||||||
// 列表上滑,显示FAB
|
|
||||||
if (dy < 0 && !fab.isShown) {
|
|
||||||
fab.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
todoViewModel.addData.observe(viewLifecycleOwner) {
|
|
||||||
binding.todoList.adapter?.notifyItemInserted(todoList.size + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
todoViewModel.removeData.observe(viewLifecycleOwner) {
|
|
||||||
binding.todoList.adapter?.notifyItemRemoved(todoList.size + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
todoViewModel.refreshData.observe(viewLifecycleOwner) {
|
|
||||||
binding.todoList.adapter?.notifyItemRangeChanged(0, todoList.size + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments.welcome
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.commit
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import cn.super12138.todo.R
|
|
||||||
import cn.super12138.todo.constant.GlobalValues
|
|
||||||
import cn.super12138.todo.databinding.FragmentWelcomeBinding
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.utils.setOnIntervalClickListener
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
import cn.super12138.todo.views.activities.MainActivity
|
|
||||||
import cn.super12138.todo.views.fragments.MainFragment
|
|
||||||
import cn.super12138.todo.views.fragments.welcome.pages.IntroPage
|
|
||||||
import cn.super12138.todo.views.fragments.welcome.pages.ProgressPage
|
|
||||||
import cn.super12138.todo.views.fragments.welcome.pages.ToDoBtnPage
|
|
||||||
import cn.super12138.todo.views.fragments.welcome.pages.ToDoItemPage
|
|
||||||
import cn.super12138.todo.views.viewmodels.WelcomeViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class WelcomeFragment : BaseFragment<FragmentWelcomeBinding>() {
|
|
||||||
private val viewModel by viewModels<WelcomeViewModel>()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
val previousBtn = binding.previousBtn
|
|
||||||
val nextBtn = binding.nextBtn
|
|
||||||
val centerBtn = binding.centerBtn
|
|
||||||
|
|
||||||
val currentPage = viewModel.currentPage // 0: Intro 1: Progress 2: ToDo Btn 3: ToDo Item
|
|
||||||
|
|
||||||
// 返回回调
|
|
||||||
val callback = object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
if (currentPage.value == 0) {
|
|
||||||
requireActivity().finishAffinity()
|
|
||||||
} else {
|
|
||||||
childFragmentManager.popBackStack()
|
|
||||||
viewModel.decreasePage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
centerBtn.setOnIntervalClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
centerBtn.hide()
|
|
||||||
nextBtn.show()
|
|
||||||
previousBtn.show()
|
|
||||||
|
|
||||||
if (currentPage.value == 3) {
|
|
||||||
GlobalValues.welcomePage = true
|
|
||||||
(requireActivity() as MainActivity).startFragment(MainFragment())
|
|
||||||
} else {
|
|
||||||
viewModel.setCurrentPage(1)
|
|
||||||
nextPage(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previousBtn.setOnIntervalClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
childFragmentManager.popBackStack()
|
|
||||||
|
|
||||||
viewModel.decreasePage()
|
|
||||||
}
|
|
||||||
|
|
||||||
nextBtn.setOnIntervalClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
|
|
||||||
nextPage(currentPage.value + 1)
|
|
||||||
|
|
||||||
callback.isEnabled = false
|
|
||||||
viewModel.increasePage()
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
viewModel.currentPage.collect { page ->
|
|
||||||
when (page) {
|
|
||||||
0 -> {
|
|
||||||
centerBtn.apply {
|
|
||||||
text = getString(R.string.start)
|
|
||||||
icon = AppCompatResources.getDrawable(
|
|
||||||
requireContext(),
|
|
||||||
R.drawable.ic_arrow_forward
|
|
||||||
)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
nextBtn.hide()
|
|
||||||
previousBtn.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
in 1..2 -> {
|
|
||||||
centerBtn.hide()
|
|
||||||
previousBtn.show()
|
|
||||||
nextBtn.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
3 -> {
|
|
||||||
centerBtn.apply {
|
|
||||||
text = getString(R.string.enter_app)
|
|
||||||
icon = AppCompatResources.getDrawable(
|
|
||||||
requireContext(),
|
|
||||||
R.drawable.ic_focus
|
|
||||||
)
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
previousBtn.show()
|
|
||||||
nextBtn.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
if (page > 3) viewModel.setCurrentPage(3)
|
|
||||||
|
|
||||||
if (page < 0) viewModel.setCurrentPage(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCurrentPage(pageIndex: Int): Fragment {
|
|
||||||
return when (pageIndex) {
|
|
||||||
0 -> IntroPage()
|
|
||||||
1 -> ProgressPage()
|
|
||||||
2 -> ToDoBtnPage()
|
|
||||||
3 -> ToDoItemPage()
|
|
||||||
else -> IntroPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nextPage(page: Int) {
|
|
||||||
childFragmentManager.commit {
|
|
||||||
addToBackStack(System.currentTimeMillis().toString())
|
|
||||||
hide(childFragmentManager.fragments.last())
|
|
||||||
add(R.id.welcome_page_container, getCurrentPage(page))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentWelcomeBinding {
|
|
||||||
return FragmentWelcomeBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments.welcome.pages
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.databinding.FragmentWelcomeIntroBinding
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
|
|
||||||
class IntroPage : BaseFragment<FragmentWelcomeIntroBinding>() {
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentWelcomeIntroBinding {
|
|
||||||
return FragmentWelcomeIntroBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments.welcome.pages
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.databinding.FragmentWelcomeProgressBinding
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
|
|
||||||
class ProgressPage : BaseFragment<FragmentWelcomeProgressBinding>() {
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentWelcomeProgressBinding {
|
|
||||||
return FragmentWelcomeProgressBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments.welcome.pages
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.databinding.FragmentWelcomeTodoBtnBinding
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
|
|
||||||
class ToDoBtnPage : BaseFragment<FragmentWelcomeTodoBtnBinding>() {
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentWelcomeTodoBtnBinding {
|
|
||||||
return FragmentWelcomeTodoBtnBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
|
|
||||||
binding.addItem.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.addItem.setOnLongClickListener {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package cn.super12138.todo.views.fragments.welcome.pages
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import cn.super12138.todo.databinding.FragmentWelcomeTodoItemBinding
|
|
||||||
import cn.super12138.todo.utils.VibrationUtils
|
|
||||||
import cn.super12138.todo.views.BaseFragment
|
|
||||||
|
|
||||||
class ToDoItemPage : BaseFragment<FragmentWelcomeTodoItemBinding>() {
|
|
||||||
override fun getViewBinding(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
attachToRoot: Boolean
|
|
||||||
): FragmentWelcomeTodoItemBinding {
|
|
||||||
return FragmentWelcomeTodoItemBinding.inflate(inflater, container, attachToRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
binding.checkItemBtn.setOnClickListener {
|
|
||||||
VibrationUtils.performHapticFeedback(it)
|
|
||||||
}
|
|
||||||
binding.todoItem.setOnLongClickListener {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package cn.super12138.todo.views.viewmodels
|
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import cn.super12138.todo.logic.Repository
|
|
||||||
import cn.super12138.todo.logic.model.ToDo
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AllTasksViewModel : ViewModel() {
|
|
||||||
val todoListAll = ArrayList<ToDo>()
|
|
||||||
val refreshData = MutableLiveData<Int>(0)
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadToDos()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadToDos() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
val todos = Repository.getAll()
|
|
||||||
for (todo in todos) {
|
|
||||||
todoListAll.add(ToDo(todo.uuid, todo.state, todo.content, todo.subject))
|
|
||||||
}
|
|
||||||
if (todoListAll.size > 0) {
|
|
||||||
refreshData.value = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package cn.super12138.todo.views.viewmodels
|
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import cn.super12138.todo.logic.Repository
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ProgressViewModel : ViewModel() {
|
|
||||||
val totalCount: MutableLiveData<Int> = MutableLiveData()
|
|
||||||
val completeCount: MutableLiveData<Int> = MutableLiveData()
|
|
||||||
val remainCount: MutableLiveData<Int> = MutableLiveData()
|
|
||||||
val progress: MutableLiveData<Int> = MutableLiveData()
|
|
||||||
|
|
||||||
fun updateProgress() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
delay(30)
|
|
||||||
val total = Repository.getAll().size
|
|
||||||
val complete = Repository.getAllComplete().size
|
|
||||||
|
|
||||||
val calcProgress = (complete.toDouble() / total.toDouble()) * 100
|
|
||||||
progress.postValue(calcProgress.toInt())
|
|
||||||
completeCount.postValue(complete)
|
|
||||||
totalCount.postValue(total)
|
|
||||||
remainCount.postValue(total - complete)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
package cn.super12138.todo.views.viewmodels
|
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import cn.super12138.todo.logic.Repository
|
|
||||||
import cn.super12138.todo.logic.dao.ToDoRoom
|
|
||||||
import cn.super12138.todo.logic.model.ToDo
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ToDoViewModel : ViewModel() {
|
|
||||||
val addData = MutableLiveData<Int>(0)
|
|
||||||
val removeData = MutableLiveData<Int>(0)
|
|
||||||
val refreshData = MutableLiveData<Int>(0)
|
|
||||||
|
|
||||||
val todoList = ArrayList<ToDo>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
loadTasks()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadTasks() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
val todos = Repository.getAllIncomplete()
|
|
||||||
for (todo in todos) {
|
|
||||||
todoList.add(ToDo(todo.uuid, todo.state, todo.content, todo.subject))
|
|
||||||
}
|
|
||||||
if (todoList.size > 0) {
|
|
||||||
refreshData.value = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteTask(position: Int, uuid: String) {
|
|
||||||
todoList.removeAt(position)
|
|
||||||
viewModelScope.launch {
|
|
||||||
Repository.deleteByUUID(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun insertTask(todo: ToDoRoom) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
Repository.insert(todo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTaskState(uuid: String) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
Repository.updateStateByUUID(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTask(position: Int, todo: ToDoRoom) {
|
|
||||||
todoList.removeAt(position)
|
|
||||||
todoList.add(
|
|
||||||
position,
|
|
||||||
ToDo(
|
|
||||||
todo.uuid,
|
|
||||||
todo.state,
|
|
||||||
todo.content,
|
|
||||||
todo.subject
|
|
||||||
)
|
|
||||||
)
|
|
||||||
viewModelScope.launch {
|
|
||||||
Repository.update(todo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package cn.super12138.todo.views.viewmodels
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
|
|
||||||
class WelcomeViewModel : ViewModel() {
|
|
||||||
private val _currentPage = MutableStateFlow(0)
|
|
||||||
val currentPage = _currentPage.asStateFlow()
|
|
||||||
|
|
||||||
fun setCurrentPage(page: Int) {
|
|
||||||
_currentPage.value = page
|
|
||||||
}
|
|
||||||
|
|
||||||
fun increasePage() {
|
|
||||||
_currentPage.value += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decreasePage() {
|
|
||||||
_currentPage.value -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
package de.raphaelebner.roomdatabasebackup.core
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import java.io.*
|
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
import java.security.spec.InvalidKeySpecException
|
|
||||||
import java.security.spec.KeySpec
|
|
||||||
import javax.crypto.SecretKey
|
|
||||||
import javax.crypto.SecretKeyFactory
|
|
||||||
import javax.crypto.spec.PBEKeySpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2024 Raphael Ebner
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
* associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or
|
|
||||||
* substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
class AESEncryptionHelper {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val BACKUP_SECRET_KEY = "backupsecretkey"
|
|
||||||
private const val TAG = "debug_AESEncryptionHelper"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will convert a file to ByteArray
|
|
||||||
* @param file : the Path where the file is located
|
|
||||||
* @return ByteArray of the file
|
|
||||||
*/
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun readFile(file: File): ByteArray {
|
|
||||||
val fileContents = file.readBytes()
|
|
||||||
val inputBuffer = BufferedInputStream(FileInputStream(file))
|
|
||||||
inputBuffer.read(fileContents)
|
|
||||||
inputBuffer.close()
|
|
||||||
return fileContents
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will convert a ByteArray to a file, and saves it to the path
|
|
||||||
* @param fileData : the ByteArray
|
|
||||||
* @param file : the path where the ByteArray should be saved
|
|
||||||
*/
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun saveFile(fileData: ByteArray, file: File) {
|
|
||||||
val bos = BufferedOutputStream(FileOutputStream(file, false))
|
|
||||||
bos.write(fileData)
|
|
||||||
bos.flush()
|
|
||||||
bos.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will convert a random password, saved in sharedPreferences to a SecretKey
|
|
||||||
* @param sharedPref : the sharedPref, to fetch / save the key
|
|
||||||
* @param iv : the encryption nonce
|
|
||||||
* @return SecretKey
|
|
||||||
*/
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
fun getSecretKey(sharedPref: SharedPreferences, iv: ByteArray): SecretKey {
|
|
||||||
|
|
||||||
// get key: String from sharedpref
|
|
||||||
var password = sharedPref.getString(BACKUP_SECRET_KEY, null)
|
|
||||||
|
|
||||||
// If no key is stored in shared pref, create one and save it
|
|
||||||
if (password == null) {
|
|
||||||
// generate random string
|
|
||||||
val stringLength = 15
|
|
||||||
val charset = ('a'..'z') + ('A'..'Z') + ('1'..'9')
|
|
||||||
password = (1..stringLength).map { charset.random() }.joinToString("")
|
|
||||||
|
|
||||||
val secretKey = generateSecretKey(password, iv)
|
|
||||||
// the key can be saved plain, because i am using EncryptedSharedPreferences
|
|
||||||
val editor = sharedPref.edit()
|
|
||||||
editor.putString(BACKUP_SECRET_KEY, password)
|
|
||||||
// I use .commit because when using .apply the needed app restart is faster then apply
|
|
||||||
// and the preferences wont be saved
|
|
||||||
editor.commit()
|
|
||||||
|
|
||||||
return secretKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate secretKey, and return it
|
|
||||||
return generateSecretKey(password, iv)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will convert a custom password to a SecretKey
|
|
||||||
* @param encryptPassword : the custom user password as String
|
|
||||||
* @param iv : the encryption nonce
|
|
||||||
* @return SecretKey
|
|
||||||
*/
|
|
||||||
fun getSecretKeyWithCustomPw(encryptPassword: String, iv: ByteArray): SecretKey {
|
|
||||||
// generate secretKey, and return it
|
|
||||||
return generateSecretKey(encryptPassword, iv)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to generate a 128 bit key from the given password and iv
|
|
||||||
* @param password
|
|
||||||
* @param iv
|
|
||||||
* @return Secret key
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
* @throws InvalidKeySpecException
|
|
||||||
*/
|
|
||||||
@Throws(NoSuchAlgorithmException::class, InvalidKeySpecException::class)
|
|
||||||
private fun generateSecretKey(password: String, iv: ByteArray?): SecretKey {
|
|
||||||
// convert random string to secretKey
|
|
||||||
val spec: KeySpec = PBEKeySpec(password.toCharArray(), iv, 65536, 128) // AES-128
|
|
||||||
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
|
||||||
val key = secretKeyFactory.generateSecret(spec).encoded
|
|
||||||
return SecretKeySpec(key, "AES")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
package de.raphaelebner.roomdatabasebackup.core
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.security.InvalidAlgorithmParameterException
|
|
||||||
import java.security.InvalidKeyException
|
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.spec.InvalidKeySpecException
|
|
||||||
import javax.crypto.BadPaddingException
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.IllegalBlockSizeException
|
|
||||||
import javax.crypto.NoSuchPaddingException
|
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encryption / Decryption service using the AES algorithm
|
|
||||||
* example for nullbeans.com
|
|
||||||
* https://nullbeans.com/how-to-encrypt-decrypt-files-byte-arrays-in-java-using-aes-gcm/#Generating_an_AES_key
|
|
||||||
*/
|
|
||||||
class AESEncryptionManager {
|
|
||||||
|
|
||||||
private val aesEncryptionHelper = AESEncryptionHelper()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will encrypt the given data
|
|
||||||
* @param sharedPref : the sharedPref, to fetch the key
|
|
||||||
* @param data : the data that will be encrypted
|
|
||||||
* @return Encrypted data in a byte array
|
|
||||||
*/
|
|
||||||
@Throws(
|
|
||||||
NoSuchPaddingException::class,
|
|
||||||
NoSuchAlgorithmException::class,
|
|
||||||
InvalidAlgorithmParameterException::class,
|
|
||||||
InvalidKeyException::class,
|
|
||||||
BadPaddingException::class,
|
|
||||||
IllegalBlockSizeException::class,
|
|
||||||
InvalidKeySpecException::class
|
|
||||||
)
|
|
||||||
fun encryptData(sharedPref: SharedPreferences, encryptPassword: String?, data: ByteArray): ByteArray {
|
|
||||||
|
|
||||||
//Prepare the nonce
|
|
||||||
val secureRandom = SecureRandom()
|
|
||||||
|
|
||||||
//Noonce should be 12 bytes
|
|
||||||
val iv = ByteArray(12)
|
|
||||||
secureRandom.nextBytes(iv)
|
|
||||||
|
|
||||||
//Prepare your key/password
|
|
||||||
val secretKey = if (encryptPassword != null) { aesEncryptionHelper.getSecretKeyWithCustomPw(encryptPassword, iv)
|
|
||||||
} else aesEncryptionHelper.getSecretKey(sharedPref, iv)
|
|
||||||
|
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
|
||||||
val parameterSpec = GCMParameterSpec(128, iv)
|
|
||||||
|
|
||||||
//Encryption mode on!
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec)
|
|
||||||
|
|
||||||
//Encrypt the data
|
|
||||||
val encryptedData = cipher.doFinal(data)
|
|
||||||
|
|
||||||
//Concatenate everything and return the final data
|
|
||||||
val byteBuffer = ByteBuffer.allocate(4 + iv.size + encryptedData.size)
|
|
||||||
byteBuffer.putInt(iv.size)
|
|
||||||
byteBuffer.put(iv)
|
|
||||||
byteBuffer.put(encryptedData)
|
|
||||||
return byteBuffer.array()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will decrypt the given data
|
|
||||||
* @param sharedPref : the sharedPref, to fetch the key
|
|
||||||
* @param encryptedData : the data that will be decrypted
|
|
||||||
* @return decrypted data in a byte array
|
|
||||||
*/
|
|
||||||
@Throws(
|
|
||||||
NoSuchPaddingException::class,
|
|
||||||
NoSuchAlgorithmException::class,
|
|
||||||
InvalidAlgorithmParameterException::class,
|
|
||||||
InvalidKeyException::class,
|
|
||||||
BadPaddingException::class,
|
|
||||||
IllegalBlockSizeException::class,
|
|
||||||
InvalidKeySpecException::class
|
|
||||||
)
|
|
||||||
fun decryptData(sharedPref: SharedPreferences, encryptPassword: String?, encryptedData: ByteArray): ByteArray {
|
|
||||||
|
|
||||||
|
|
||||||
//Wrap the data into a byte buffer to ease the reading process
|
|
||||||
val byteBuffer = ByteBuffer.wrap(encryptedData)
|
|
||||||
val noonceSize = byteBuffer.int
|
|
||||||
|
|
||||||
//Make sure that the file was encrypted properly
|
|
||||||
require(!(noonceSize < 12 || noonceSize >= 16)) { "Nonce size is incorrect. Make sure that the incoming data is an AES encrypted file." }
|
|
||||||
val iv = ByteArray(noonceSize)
|
|
||||||
byteBuffer[iv]
|
|
||||||
|
|
||||||
//Prepare your key/password
|
|
||||||
val secretKey = if (encryptPassword != null) { aesEncryptionHelper.getSecretKeyWithCustomPw(encryptPassword, iv)
|
|
||||||
} else aesEncryptionHelper.getSecretKey(sharedPref, iv)
|
|
||||||
|
|
||||||
//get the rest of encrypted data
|
|
||||||
val cipherBytes = ByteArray(byteBuffer.remaining())
|
|
||||||
byteBuffer[cipherBytes]
|
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
|
||||||
val parameterSpec = GCMParameterSpec(128, iv)
|
|
||||||
|
|
||||||
//Encryption mode on!
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec)
|
|
||||||
|
|
||||||
//Encrypt the data
|
|
||||||
return cipher.doFinal(cipherBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
package de.raphaelebner.roomdatabasebackup.core
|
|
||||||
|
|
||||||
import de.raphaelebner.roomdatabasebackup.core.RoomBackup.Companion.BACKUP_FILE_LOCATION_CUSTOM_FILE
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2024 Raphael Ebner
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
* associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or
|
|
||||||
* substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
interface OnCompleteListener {
|
|
||||||
fun onComplete(success: Boolean, message: String, exitCode: Int)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
/** Other Error */
|
|
||||||
const val EXIT_CODE_ERROR = 1
|
|
||||||
|
|
||||||
/** Error while choosing backup to restore. Maybe no file selected */
|
|
||||||
const val EXIT_CODE_ERROR_BACKUP_FILE_CHOOSER = 2
|
|
||||||
|
|
||||||
/** Error while choosing backup file to create. Maybe no file selected */
|
|
||||||
const val EXIT_CODE_ERROR_BACKUP_FILE_CREATOR = 3
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [BACKUP_FILE_LOCATION_CUSTOM_FILE] is set but [RoomBackup.backupLocationCustomFile] is
|
|
||||||
* not set
|
|
||||||
*/
|
|
||||||
const val EXIT_CODE_ERROR_BACKUP_LOCATION_FILE_MISSING = 4
|
|
||||||
|
|
||||||
/** [RoomBackup.backupLocation] is not set */
|
|
||||||
const val EXIT_CODE_ERROR_BACKUP_LOCATION_MISSING = 5
|
|
||||||
|
|
||||||
/** Restore dialog for internal/external storage was canceled by user */
|
|
||||||
const val EXIT_CODE_ERROR_BY_USER_CANCELED = 6
|
|
||||||
|
|
||||||
/** Cannot decrypt provided backup file */
|
|
||||||
const val EXIT_CODE_ERROR_DECRYPTION_ERROR = 7
|
|
||||||
|
|
||||||
/** Cannot encrypt database backup */
|
|
||||||
const val EXIT_CODE_ERROR_ENCRYPTION_ERROR = 8
|
|
||||||
|
|
||||||
/**
|
|
||||||
* You tried to restore a encrypted backup but [RoomBackup.backupIsEncrypted] is set to
|
|
||||||
* false
|
|
||||||
*/
|
|
||||||
const val EXIT_CODE_ERROR_RESTORE_BACKUP_IS_ENCRYPTED = 9
|
|
||||||
|
|
||||||
/** No backups to restore are available in internal/external sotrage */
|
|
||||||
const val EXIT_CODE_ERROR_RESTORE_NO_BACKUPS_AVAILABLE = 10
|
|
||||||
|
|
||||||
/** No room database to backup is provided */
|
|
||||||
const val EXIT_CODE_ERROR_ROOM_DATABASE_MISSING = 11
|
|
||||||
|
|
||||||
/** Storage permissions not granted for custom dialog */
|
|
||||||
const val EXIT_CODE_ERROR_STORAGE_PERMISSONS_NOT_GRANTED = 12
|
|
||||||
|
|
||||||
/** Cannot decrypt provided backup file because the password is incorrect */
|
|
||||||
const val EXIT_CODE_ERROR_WRONG_DECRYPTION_PASSWORD = 13
|
|
||||||
|
|
||||||
/** No error, action successful */
|
|
||||||
const val EXIT_CODE_SUCCESS = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,853 +0,0 @@
|
||||||
package de.raphaelebner.roomdatabasebackup.core
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.security.crypto.EncryptedSharedPreferences
|
|
||||||
import androidx.security.crypto.MasterKey
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.io.BufferedOutputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Locale
|
|
||||||
import javax.crypto.BadPaddingException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2024 Raphael Ebner
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
||||||
* associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or
|
|
||||||
* substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
class RoomBackup(var context: Context) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val SHARED_PREFS = "de.raphaelebner.roomdatabasebackup"
|
|
||||||
private var TAG = "debug_RoomBackup"
|
|
||||||
private lateinit var INTERNAL_BACKUP_PATH: File
|
|
||||||
private lateinit var TEMP_BACKUP_PATH: File
|
|
||||||
private lateinit var TEMP_BACKUP_FILE: File
|
|
||||||
private lateinit var EXTERNAL_BACKUP_PATH: File
|
|
||||||
private lateinit var DATABASE_FILE: File
|
|
||||||
|
|
||||||
private var currentProcess: Int? = null
|
|
||||||
private const val PROCESS_BACKUP = 1
|
|
||||||
private const val PROCESS_RESTORE = 2
|
|
||||||
private var backupFilename: String? = null
|
|
||||||
|
|
||||||
/** Code for internal backup location, used for [backupLocation] */
|
|
||||||
const val BACKUP_FILE_LOCATION_INTERNAL = 1
|
|
||||||
|
|
||||||
/** Code for external backup location, used for [backupLocation] */
|
|
||||||
const val BACKUP_FILE_LOCATION_EXTERNAL = 2
|
|
||||||
|
|
||||||
/** Code for custom backup location dialog, used for [backupLocation] */
|
|
||||||
const val BACKUP_FILE_LOCATION_CUSTOM_DIALOG = 3
|
|
||||||
|
|
||||||
/** Code for custom backup file location, used for [backupLocation] */
|
|
||||||
const val BACKUP_FILE_LOCATION_CUSTOM_FILE = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
|
||||||
private lateinit var dbName: String
|
|
||||||
|
|
||||||
private var roomDatabase: RoomDatabase? = null
|
|
||||||
private var enableLogDebug: Boolean = false
|
|
||||||
private var restartIntent: Intent? = null
|
|
||||||
private var onCompleteListener: OnCompleteListener? = null
|
|
||||||
private var customRestoreDialogTitle: String = "Choose file to restore"
|
|
||||||
private var customBackupFileName: String? = null
|
|
||||||
private var backupIsEncrypted: Boolean = false
|
|
||||||
private var maxFileCount: Int? = null
|
|
||||||
private var encryptPassword: String? = null
|
|
||||||
private var backupLocation: Int = BACKUP_FILE_LOCATION_INTERNAL
|
|
||||||
private var backupLocationCustomFile: File? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set RoomDatabase instance
|
|
||||||
*
|
|
||||||
* @param roomDatabase RoomDatabase
|
|
||||||
*/
|
|
||||||
fun database(roomDatabase: RoomDatabase): RoomBackup {
|
|
||||||
this.roomDatabase = roomDatabase
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set LogDebug enabled / disabled
|
|
||||||
*
|
|
||||||
* @param enableLogDebug Boolean
|
|
||||||
*/
|
|
||||||
fun enableLogDebug(enableLogDebug: Boolean): RoomBackup {
|
|
||||||
this.enableLogDebug = enableLogDebug
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Intent in which to boot after App restart
|
|
||||||
*
|
|
||||||
* @param restartIntent Intent
|
|
||||||
*/
|
|
||||||
fun restartApp(restartIntent: Intent): RoomBackup {
|
|
||||||
this.restartIntent = restartIntent
|
|
||||||
restartApp()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set onCompleteListener, to run code when tasks completed
|
|
||||||
*
|
|
||||||
* @param onCompleteListener OnCompleteListener
|
|
||||||
*/
|
|
||||||
fun onCompleteListener(onCompleteListener: OnCompleteListener): RoomBackup {
|
|
||||||
this.onCompleteListener = onCompleteListener
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set onCompleteListener, to run code when tasks completed
|
|
||||||
*
|
|
||||||
* @param listener (success: Boolean, message: String) -> Unit
|
|
||||||
*/
|
|
||||||
fun onCompleteListener(
|
|
||||||
listener: (success: Boolean, message: String, exitCode: Int) -> Unit
|
|
||||||
): RoomBackup {
|
|
||||||
this.onCompleteListener =
|
|
||||||
object : OnCompleteListener {
|
|
||||||
override fun onComplete(success: Boolean, message: String, exitCode: Int) {
|
|
||||||
listener(success, message, exitCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set custom log tag, for detailed debugging
|
|
||||||
*
|
|
||||||
* @param customLogTag String
|
|
||||||
*/
|
|
||||||
fun customLogTag(customLogTag: String): RoomBackup {
|
|
||||||
TAG = customLogTag
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set custom Restore Dialog Title, default = "Choose file to restore"
|
|
||||||
*
|
|
||||||
* @param customRestoreDialogTitle String
|
|
||||||
*/
|
|
||||||
fun customRestoreDialogTitle(customRestoreDialogTitle: String): RoomBackup {
|
|
||||||
this.customRestoreDialogTitle = customRestoreDialogTitle
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set custom Backup File Name, default = "$dbName-$currentTime.sqlite3"
|
|
||||||
*
|
|
||||||
* @param customBackupFileName String
|
|
||||||
*/
|
|
||||||
fun customBackupFileName(customBackupFileName: String): RoomBackup {
|
|
||||||
this.customBackupFileName = customBackupFileName
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set you backup location. Available values see: [BACKUP_FILE_LOCATION_INTERNAL],
|
|
||||||
* [BACKUP_FILE_LOCATION_EXTERNAL], [BACKUP_FILE_LOCATION_CUSTOM_DIALOG] or
|
|
||||||
* [BACKUP_FILE_LOCATION_CUSTOM_FILE]
|
|
||||||
*
|
|
||||||
* @param backupLocation Int, default = [BACKUP_FILE_LOCATION_INTERNAL]
|
|
||||||
*/
|
|
||||||
fun backupLocation(backupLocation: Int): RoomBackup {
|
|
||||||
this.backupLocation = backupLocation
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a custom file where to save or restore a backup. can be used for backup and restore
|
|
||||||
*
|
|
||||||
* Only available if [backupLocation] is set to [BACKUP_FILE_LOCATION_CUSTOM_FILE]
|
|
||||||
*
|
|
||||||
* @param backupLocationCustomFile File
|
|
||||||
*/
|
|
||||||
fun backupLocationCustomFile(backupLocationCustomFile: File): RoomBackup {
|
|
||||||
this.backupLocationCustomFile = backupLocationCustomFile
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set file encryption to true / false can be used for backup and restore
|
|
||||||
*
|
|
||||||
* @param backupIsEncrypted Boolean, default = false
|
|
||||||
*/
|
|
||||||
fun backupIsEncrypted(backupIsEncrypted: Boolean): RoomBackup {
|
|
||||||
this.backupIsEncrypted = backupIsEncrypted
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set max backup files count if fileCount is > maxFileCount the oldest backup file will be
|
|
||||||
* deleted is for both internal and external storage
|
|
||||||
*
|
|
||||||
* @param maxFileCount Int, default = null
|
|
||||||
*/
|
|
||||||
fun maxFileCount(maxFileCount: Int): RoomBackup {
|
|
||||||
this.maxFileCount = maxFileCount
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set custom backup encryption password
|
|
||||||
*
|
|
||||||
* @param encryptPassword String
|
|
||||||
*/
|
|
||||||
fun customEncryptPassword(encryptPassword: String): RoomBackup {
|
|
||||||
this.encryptPassword = encryptPassword
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Init vars, and return true if no error occurred */
|
|
||||||
private fun initRoomBackup(): Boolean {
|
|
||||||
if (roomDatabase == null) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "roomDatabase is missing")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"roomDatabase is missing",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_ROOM_DATABASE_MISSING
|
|
||||||
)
|
|
||||||
// throw IllegalArgumentException("roomDatabase is not initialized")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create or retrieve the Master Key for encryption/decryption
|
|
||||||
val masterKeyAlias =
|
|
||||||
MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
|
|
||||||
|
|
||||||
if (backupLocation !in
|
|
||||||
listOf(
|
|
||||||
BACKUP_FILE_LOCATION_INTERNAL,
|
|
||||||
BACKUP_FILE_LOCATION_EXTERNAL,
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_DIALOG,
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_FILE
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "backupLocation is missing")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"backupLocation is missing",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_BACKUP_LOCATION_MISSING
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backupLocation == BACKUP_FILE_LOCATION_CUSTOM_FILE && backupLocationCustomFile == null
|
|
||||||
) {
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"backupLocation is set to custom backup file, but no file is defined"
|
|
||||||
)
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"backupLocation is set to custom backup file, but no file is defined",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_BACKUP_LOCATION_FILE_MISSING
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize/open an instance of EncryptedSharedPreferences
|
|
||||||
// Encryption key is stored in plain text in an EncryptedSharedPreferences --> it is saved
|
|
||||||
// encrypted
|
|
||||||
sharedPreferences =
|
|
||||||
EncryptedSharedPreferences.create(
|
|
||||||
context,
|
|
||||||
SHARED_PREFS,
|
|
||||||
masterKeyAlias,
|
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
||||||
)
|
|
||||||
|
|
||||||
dbName = roomDatabase!!.openHelper.databaseName!!
|
|
||||||
INTERNAL_BACKUP_PATH = File("${context.filesDir}/databasebackup/")
|
|
||||||
TEMP_BACKUP_PATH = File("${context.filesDir}/databasebackup-temp/")
|
|
||||||
TEMP_BACKUP_FILE = File("$TEMP_BACKUP_PATH/tempbackup.sqlite3")
|
|
||||||
EXTERNAL_BACKUP_PATH = File(context.getExternalFilesDir("backup")!!.toURI())
|
|
||||||
DATABASE_FILE = File(context.getDatabasePath(dbName).toURI())
|
|
||||||
|
|
||||||
// Create internal and temp backup directory if does not exist
|
|
||||||
try {
|
|
||||||
INTERNAL_BACKUP_PATH.mkdirs()
|
|
||||||
TEMP_BACKUP_PATH.mkdirs()
|
|
||||||
} catch (_: FileAlreadyExistsException) {
|
|
||||||
} catch (_: IOException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLogDebug) {
|
|
||||||
Log.d(TAG, "DatabaseName: $dbName")
|
|
||||||
Log.d(TAG, "Database Location: $DATABASE_FILE")
|
|
||||||
Log.d(TAG, "INTERNAL_BACKUP_PATH: $INTERNAL_BACKUP_PATH")
|
|
||||||
Log.d(TAG, "EXTERNAL_BACKUP_PATH: $EXTERNAL_BACKUP_PATH")
|
|
||||||
if (backupLocationCustomFile != null)
|
|
||||||
Log.d(TAG, "backupLocationCustomFile: $backupLocationCustomFile")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/** restart App with custom Intent */
|
|
||||||
private fun restartApp() {
|
|
||||||
restartIntent!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
context.startActivity(restartIntent)
|
|
||||||
if (context is Activity) {
|
|
||||||
(context as Activity).finish()
|
|
||||||
}
|
|
||||||
Runtime.getRuntime().exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start Backup process, and set onComplete Listener to success, if no error occurred, else
|
|
||||||
* onComplete Listener success is false and error message is passed
|
|
||||||
*
|
|
||||||
* if custom storage ist selected, the [openBackupfileCreator] will be launched
|
|
||||||
*/
|
|
||||||
fun backup() {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "Starting Backup ...")
|
|
||||||
val success = initRoomBackup()
|
|
||||||
if (!success) return
|
|
||||||
|
|
||||||
// Needed for storage permissions request
|
|
||||||
currentProcess = PROCESS_BACKUP
|
|
||||||
|
|
||||||
// Create name for backup file, if no custom name is set: Database name + currentTime +
|
|
||||||
// .sqlite3
|
|
||||||
var filename =
|
|
||||||
if (customBackupFileName == null) "$dbName-${getTime()}.sqlite3"
|
|
||||||
else customBackupFileName as String
|
|
||||||
// Add .aes extension to filename, if file is encrypted
|
|
||||||
if (backupIsEncrypted) filename += ".aes"
|
|
||||||
if (enableLogDebug) Log.d(TAG, "backupFilename: $filename")
|
|
||||||
|
|
||||||
when (backupLocation) {
|
|
||||||
BACKUP_FILE_LOCATION_INTERNAL -> {
|
|
||||||
val backupFile = File("$INTERNAL_BACKUP_PATH/$filename")
|
|
||||||
doBackup(backupFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_EXTERNAL -> {
|
|
||||||
val backupFile = File("$EXTERNAL_BACKUP_PATH/$filename")
|
|
||||||
doBackup(backupFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_DIALOG -> {
|
|
||||||
backupFilename = filename
|
|
||||||
permissionRequestLauncher.launch(arrayOf())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_FILE -> {
|
|
||||||
doBackup(backupLocationCustomFile!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will do the backup action
|
|
||||||
*
|
|
||||||
* @param destination File
|
|
||||||
*/
|
|
||||||
private fun doBackup(destination: File) {
|
|
||||||
// Close the database
|
|
||||||
roomDatabase!!.close()
|
|
||||||
roomDatabase = null
|
|
||||||
if (backupIsEncrypted) {
|
|
||||||
val encryptedBytes = encryptBackup() ?: return
|
|
||||||
val bos = BufferedOutputStream(FileOutputStream(destination, false))
|
|
||||||
bos.write(encryptedBytes)
|
|
||||||
bos.flush()
|
|
||||||
bos.close()
|
|
||||||
} else {
|
|
||||||
// Copy current database to save location (/files dir)
|
|
||||||
DATABASE_FILE.copyTo(destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If maxFileCount is set and is reached, delete oldest file
|
|
||||||
if (maxFileCount != null) {
|
|
||||||
val deleted = deleteOldBackup()
|
|
||||||
if (!deleted) return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(TAG, "Backup done, encrypted($backupIsEncrypted) and saved to $destination")
|
|
||||||
onCompleteListener?.onComplete(true, "success", OnCompleteListener.EXIT_CODE_SUCCESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will do the backup action
|
|
||||||
*
|
|
||||||
* @param destination OutputStream
|
|
||||||
*/
|
|
||||||
private fun doBackup(destination: OutputStream) {
|
|
||||||
// Close the database
|
|
||||||
roomDatabase!!.close()
|
|
||||||
roomDatabase = null
|
|
||||||
if (backupIsEncrypted) {
|
|
||||||
val encryptedBytes = encryptBackup() ?: return
|
|
||||||
destination.write(encryptedBytes)
|
|
||||||
} else {
|
|
||||||
// Copy current database to save location (/files dir)
|
|
||||||
DATABASE_FILE.inputStream().use { input ->
|
|
||||||
destination.use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If maxFileCount is set and is reached, delete oldest file
|
|
||||||
if (maxFileCount != null) {
|
|
||||||
val deleted = deleteOldBackup()
|
|
||||||
if (!deleted) return
|
|
||||||
}
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(TAG, "Backup done, encrypted($backupIsEncrypted) and saved to $destination")
|
|
||||||
onCompleteListener?.onComplete(true, "success", OnCompleteListener.EXIT_CODE_SUCCESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypts the current Database and return it's content as ByteArray. The original Database is
|
|
||||||
* not encrypted only a current copy of this database
|
|
||||||
*
|
|
||||||
* @return encrypted backup as ByteArray
|
|
||||||
*/
|
|
||||||
private fun encryptBackup(): ByteArray? {
|
|
||||||
try {
|
|
||||||
// Copy database you want to backup to temp directory
|
|
||||||
DATABASE_FILE.copyTo(TEMP_BACKUP_FILE)
|
|
||||||
|
|
||||||
// encrypt temp file, and save it to backup location
|
|
||||||
val encryptDecryptBackup = AESEncryptionHelper()
|
|
||||||
val fileData = encryptDecryptBackup.readFile(TEMP_BACKUP_FILE)
|
|
||||||
|
|
||||||
val aesEncryptionManager = AESEncryptionManager()
|
|
||||||
val encryptedBytes =
|
|
||||||
aesEncryptionManager.encryptData(sharedPreferences, encryptPassword, fileData)
|
|
||||||
|
|
||||||
// Delete temp file
|
|
||||||
TEMP_BACKUP_FILE.delete()
|
|
||||||
|
|
||||||
return encryptedBytes
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "error during encryption: ${e.message}")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"error during encryption",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_ENCRYPTION_ERROR
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start Restore process, and set onComplete Listener to success, if no error occurred, else
|
|
||||||
* onComplete Listener success is false and error message is passed
|
|
||||||
*
|
|
||||||
* if internal or external storage is selected, this function shows a list of all available
|
|
||||||
* backup files in a MaterialAlertDialog and calls [restoreSelectedInternalExternalFile] to
|
|
||||||
* restore selected file
|
|
||||||
*
|
|
||||||
* if custom storage ist selected, the [openBackupfileChooser] will be launched
|
|
||||||
*/
|
|
||||||
fun restore() {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "Starting Restore ...")
|
|
||||||
val success = initRoomBackup()
|
|
||||||
if (!success) return
|
|
||||||
|
|
||||||
// Needed for storage permissions request
|
|
||||||
currentProcess = PROCESS_RESTORE
|
|
||||||
|
|
||||||
// Path of Backup Directory
|
|
||||||
val backupDirectory: File
|
|
||||||
|
|
||||||
when (backupLocation) {
|
|
||||||
BACKUP_FILE_LOCATION_INTERNAL -> {
|
|
||||||
backupDirectory = INTERNAL_BACKUP_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_EXTERNAL -> {
|
|
||||||
backupDirectory = File("$EXTERNAL_BACKUP_PATH/")
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_DIALOG -> {
|
|
||||||
permissionRequestLauncher.launch(arrayOf())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_FILE -> {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"backupLocationCustomFile!!.exists()? : ${backupLocationCustomFile!!.exists()}"
|
|
||||||
)
|
|
||||||
doRestore(backupLocationCustomFile!!)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
|
|
||||||
// All Files in an Array of type File
|
|
||||||
val arrayOfFiles = backupDirectory.listFiles()
|
|
||||||
|
|
||||||
// If array is null or empty show "error" and return
|
|
||||||
if (arrayOfFiles.isNullOrEmpty()) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "No backups available to restore")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"No backups available",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_RESTORE_NO_BACKUPS_AVAILABLE
|
|
||||||
)
|
|
||||||
Toast.makeText(context, "No backups available to restore", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort Array: lastModified
|
|
||||||
arrayOfFiles.sortBy { it.lastModified() }
|
|
||||||
|
|
||||||
// New empty MutableList of String
|
|
||||||
val mutableListOfFilesAsString = mutableListOf<String>()
|
|
||||||
|
|
||||||
// Add each filename to mutablelistOfFilesAsString
|
|
||||||
runBlocking {
|
|
||||||
for (i in arrayOfFiles.indices) {
|
|
||||||
mutableListOfFilesAsString.add(arrayOfFiles[i].name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert MutableList to Array
|
|
||||||
val filesStringArray = mutableListOfFilesAsString.toTypedArray()
|
|
||||||
|
|
||||||
// Show MaterialAlertDialog, with all available files, and on click Listener
|
|
||||||
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle(customRestoreDialogTitle)
|
|
||||||
.setItems(filesStringArray) { _, which ->
|
|
||||||
restoreSelectedInternalExternalFile(filesStringArray[which])
|
|
||||||
}
|
|
||||||
.setOnCancelListener {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "Restore dialog canceled")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"Restore dialog canceled",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_BY_USER_CANCELED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will do the restore action
|
|
||||||
*
|
|
||||||
* @param source File
|
|
||||||
*/
|
|
||||||
private fun doRestore(source: File) {
|
|
||||||
// Close the database
|
|
||||||
roomDatabase!!.close()
|
|
||||||
roomDatabase = null
|
|
||||||
val fileExtension = source.extension
|
|
||||||
if (backupIsEncrypted) {
|
|
||||||
source.copyTo(TEMP_BACKUP_FILE)
|
|
||||||
val decryptedBytes = decryptBackup() ?: return
|
|
||||||
val bos = BufferedOutputStream(FileOutputStream(DATABASE_FILE, false))
|
|
||||||
bos.write(decryptedBytes)
|
|
||||||
bos.flush()
|
|
||||||
bos.close()
|
|
||||||
} else {
|
|
||||||
if (fileExtension == "aes") {
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"Cannot restore database, it is encrypted. Maybe you forgot to add the property .fileIsEncrypted(true)"
|
|
||||||
)
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"cannot restore database, see Log for more details (if enabled)",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_RESTORE_BACKUP_IS_ENCRYPTED
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Copy back database and replace current database
|
|
||||||
source.copyTo(DATABASE_FILE)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(TAG, "Restore done, decrypted($backupIsEncrypted) and restored from $source")
|
|
||||||
onCompleteListener?.onComplete(true, "success", OnCompleteListener.EXIT_CODE_SUCCESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will do the restore action
|
|
||||||
*
|
|
||||||
* @param source InputStream
|
|
||||||
*/
|
|
||||||
private fun doRestore(source: InputStream) {
|
|
||||||
if (backupIsEncrypted) {
|
|
||||||
// Save inputstream to temp file
|
|
||||||
source.use { input ->
|
|
||||||
TEMP_BACKUP_FILE.outputStream().use { output -> input.copyTo(output) }
|
|
||||||
}
|
|
||||||
// Decrypt tempfile and write to database file
|
|
||||||
val decryptedBytes = decryptBackup() ?: return
|
|
||||||
|
|
||||||
// Close the database if decryption is succesfull
|
|
||||||
roomDatabase!!.close()
|
|
||||||
roomDatabase = null
|
|
||||||
|
|
||||||
val bos = BufferedOutputStream(FileOutputStream(DATABASE_FILE, false))
|
|
||||||
bos.write(decryptedBytes)
|
|
||||||
bos.flush()
|
|
||||||
bos.close()
|
|
||||||
} else {
|
|
||||||
// Close the database
|
|
||||||
roomDatabase!!.close()
|
|
||||||
roomDatabase = null
|
|
||||||
|
|
||||||
// Copy back database and replace current database
|
|
||||||
source.use { input ->
|
|
||||||
DATABASE_FILE.outputStream().use { output -> input.copyTo(output) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(TAG, "Restore done, decrypted($backupIsEncrypted) and restored from $source")
|
|
||||||
onCompleteListener?.onComplete(true, "success", OnCompleteListener.EXIT_CODE_SUCCESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores the selected file from internal or external storage
|
|
||||||
*
|
|
||||||
* @param filename String
|
|
||||||
*/
|
|
||||||
private fun restoreSelectedInternalExternalFile(filename: String) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "Restore selected file...")
|
|
||||||
|
|
||||||
when (backupLocation) {
|
|
||||||
BACKUP_FILE_LOCATION_INTERNAL -> {
|
|
||||||
doRestore(File("$INTERNAL_BACKUP_PATH/$filename"))
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_EXTERNAL -> {
|
|
||||||
doRestore(File("$EXTERNAL_BACKUP_PATH/$filename"))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the [TEMP_BACKUP_FILE] and return it's content as ByteArray. A valid encrypted
|
|
||||||
* backup file must be present on [TEMP_BACKUP_FILE]
|
|
||||||
*
|
|
||||||
* @return decrypted backup as ByteArray
|
|
||||||
*/
|
|
||||||
private fun decryptBackup(): ByteArray? {
|
|
||||||
try {
|
|
||||||
// Decrypt temp file, and save it to database location
|
|
||||||
val encryptDecryptBackup = AESEncryptionHelper()
|
|
||||||
val fileData = encryptDecryptBackup.readFile(TEMP_BACKUP_FILE)
|
|
||||||
|
|
||||||
val aesEncryptionManager = AESEncryptionManager()
|
|
||||||
val decryptedBytes =
|
|
||||||
aesEncryptionManager.decryptData(sharedPreferences, encryptPassword, fileData)
|
|
||||||
|
|
||||||
// Delete tem file
|
|
||||||
TEMP_BACKUP_FILE.delete()
|
|
||||||
|
|
||||||
return decryptedBytes
|
|
||||||
} catch (e: BadPaddingException) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "error during decryption (wrong password): ${e.message}")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"error during decryption (wrong password) see Log for more details (if enabled)",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_WRONG_DECRYPTION_PASSWORD
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "error during decryption: ${e.message}")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"error during decryption see Log for more details (if enabled)",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_DECRYPTION_ERROR
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the [ActivityResultContracts.RequestMultiplePermissions] and prompts the user to grant
|
|
||||||
* storage permissions
|
|
||||||
*
|
|
||||||
* If granted backup or restore process starts
|
|
||||||
*/
|
|
||||||
private val permissionRequestLauncher =
|
|
||||||
(context as ComponentActivity).registerForActivityResult(
|
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
|
||||||
) { permissions ->
|
|
||||||
permissions.entries.forEach {
|
|
||||||
if (!it.value) {
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"storage permissions are required, please allow!",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_STORAGE_PERMISSONS_NOT_GRANTED
|
|
||||||
)
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when (currentProcess) {
|
|
||||||
PROCESS_BACKUP -> {
|
|
||||||
backupFilename?.let { openBackupfileCreator.launch(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
PROCESS_RESTORE -> {
|
|
||||||
openBackupfileChooser.launch(arrayOf("application/octet-stream"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the [ActivityResultContracts.OpenDocument] and prompts the user to open a document for
|
|
||||||
* restoring a backup file
|
|
||||||
*/
|
|
||||||
private val openBackupfileChooser =
|
|
||||||
(context as ComponentActivity).registerForActivityResult(
|
|
||||||
ActivityResultContracts.OpenDocument()
|
|
||||||
) { result ->
|
|
||||||
if (result != null) {
|
|
||||||
val inputstream = context.contentResolver.openInputStream(result)!!
|
|
||||||
doRestore(inputstream)
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"failure",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_BACKUP_FILE_CHOOSER
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the [ActivityResultContracts.CreateDocument] and prompts the user to select a path for
|
|
||||||
* creating the new backup file
|
|
||||||
*/
|
|
||||||
private val openBackupfileCreator =
|
|
||||||
(context as ComponentActivity).registerForActivityResult(
|
|
||||||
CreateDocument("application/octet-stream")
|
|
||||||
) { result ->
|
|
||||||
if (result != null) {
|
|
||||||
val out = context.contentResolver.openOutputStream(result)!!
|
|
||||||
doBackup(out)
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"failure",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR_BACKUP_FILE_CREATOR
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return current time formatted as String */
|
|
||||||
private fun getTime(): String {
|
|
||||||
|
|
||||||
val currentTime = Calendar.getInstance().time
|
|
||||||
|
|
||||||
val sdf =
|
|
||||||
if (Build.VERSION.SDK_INT <= 28) {
|
|
||||||
SimpleDateFormat("yyyy-MM-dd-HH_mm_ss", Locale.getDefault())
|
|
||||||
} else {
|
|
||||||
SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.getDefault())
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdf.format(currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If maxFileCount is set, and reached, all old files will be deleted. Only if
|
|
||||||
* [BACKUP_FILE_LOCATION_INTERNAL] or [BACKUP_FILE_LOCATION_EXTERNAL]
|
|
||||||
*
|
|
||||||
* @return true if old files deleted or nothing to do
|
|
||||||
*/
|
|
||||||
private fun deleteOldBackup(): Boolean {
|
|
||||||
// Path of Backup Directory
|
|
||||||
|
|
||||||
val backupDirectory: File =
|
|
||||||
when (backupLocation) {
|
|
||||||
BACKUP_FILE_LOCATION_INTERNAL -> {
|
|
||||||
INTERNAL_BACKUP_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_EXTERNAL -> {
|
|
||||||
File("$EXTERNAL_BACKUP_PATH/")
|
|
||||||
}
|
|
||||||
|
|
||||||
BACKUP_FILE_LOCATION_CUSTOM_DIALOG -> {
|
|
||||||
// In custom backup location no backups will be removed
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// All Files in an Array of type File
|
|
||||||
val arrayOfFiles = backupDirectory.listFiles()
|
|
||||||
|
|
||||||
// If array is null or empty nothing to do and return
|
|
||||||
if (arrayOfFiles.isNullOrEmpty()) {
|
|
||||||
if (enableLogDebug) Log.d(TAG, "")
|
|
||||||
onCompleteListener?.onComplete(
|
|
||||||
false,
|
|
||||||
"maxFileCount: Failed to get list of backups",
|
|
||||||
OnCompleteListener.EXIT_CODE_ERROR
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
} else if (arrayOfFiles.size > maxFileCount!!) {
|
|
||||||
// Sort Array: lastModified
|
|
||||||
arrayOfFiles.sortBy { it.lastModified() }
|
|
||||||
|
|
||||||
// Get count of files to delete
|
|
||||||
val fileCountToDelete = arrayOfFiles.size - maxFileCount!!
|
|
||||||
|
|
||||||
for (i in 1..fileCountToDelete) {
|
|
||||||
// Delete all old files (i-1 because array starts a 0)
|
|
||||||
arrayOfFiles[i - 1].delete()
|
|
||||||
|
|
||||||
if (enableLogDebug)
|
|
||||||
Log.d(TAG, "maxFileCount reached: ${arrayOfFiles[i - 1]} deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<solid android:color="@color/item_complete_color" />
|
|
||||||
</shape>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="#000000"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zM4,16.5c-0.83,0 -1.5,0.68 -1.5,1.5s0.68,1.5 1.5,1.5 1.5,-0.68 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,5v2h14L21,5L7,5z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M16,9h-2.55L13.45,6h-2.9v3L8,9l4,4zM19,3L4.99,3C3.88,3 3,3.9 3,5v14c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19v-3h3.56c0.69,1.19 1.97,2 3.45,2s2.75,-0.81 3.45,-2L19,16v3zM19,14h-4.99c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2L5,14l-0.01,-9L19,5v9z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="#000000"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M9.37,5.51C9.19,6.15 9.1,6.82 9.1,7.5c0,4.08 3.32,7.4 7.4,7.4c0.68,0 1.35,-0.09 1.99,-0.27C17.45,17.19 14.93,19 12,19c-3.86,0 -7,-3.14 -7,-7C5,9.07 6.81,6.55 9.37,5.51zM12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="#FFFFFF"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="#FFFFFF"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?android:attr/textColorPrimary"
|
|
||||||
android:viewportWidth="16"
|
|
||||||
android:viewportHeight="16">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M8,0c4.42,0 8,3.58 8,8a8.013,8.013 0,0 1,-5.45 7.59c-0.4,0.08 -0.55,-0.17 -0.55,-0.38 0,-0.27 0.01,-1.13 0.01,-2.2 0,-0.75 -0.25,-1.23 -0.54,-1.48 1.78,-0.2 3.65,-0.88 3.65,-3.95 0,-0.88 -0.31,-1.59 -0.82,-2.15 0.08,-0.2 0.36,-1.02 -0.08,-2.12 0,0 -0.67,-0.22 -2.2,0.82 -0.64,-0.18 -1.32,-0.27 -2,-0.27 -0.68,0 -1.36,0.09 -2,0.27 -1.53,-1.03 -2.2,-0.82 -2.2,-0.82 -0.44,1.1 -0.16,1.92 -0.08,2.12 -0.51,0.56 -0.82,1.28 -0.82,2.15 0,3.06 1.86,3.75 3.64,3.95 -0.23,0.2 -0.44,0.55 -0.51,1.07 -0.46,0.21 -1.61,0.55 -2.33,-0.66 -0.15,-0.24 -0.6,-0.83 -1.23,-0.82 -0.67,0.01 -0.27,0.38 0.01,0.53 0.34,0.19 0.73,0.9 0.82,1.13 0.16,0.45 0.68,1.31 2.69,0.94 0,0.67 0.01,1.3 0.01,1.49 0,0.21 -0.15,0.45 -0.55,0.38A7.995,7.995 0,0 1,0 8c0,-4.42 3.58,-8 8,-8Z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="70dp"
|
|
||||||
android:height="70dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M21.29,5.89l-10,10c-0.39,0.39 -1.02,0.39 -1.41,0l-2.83,-2.83c-0.39,-0.39 -0.39,-1.02 0,-1.41l0,0c0.39,-0.39 1.02,-0.39 1.41,0l2.12,2.12l9.29,-9.29c0.39,-0.39 1.02,-0.39 1.41,0l0,0C21.68,4.87 21.68,5.5 21.29,5.89zM15.77,2.74c-1.69,-0.69 -3.61,-0.93 -5.61,-0.57C6.09,2.9 2.84,6.18 2.15,10.25C1.01,17 6.63,22.78 13.34,21.91c3.96,-0.51 7.28,-3.46 8.32,-7.31c0.4,-1.47 0.44,-2.89 0.21,-4.22c-0.13,-0.8 -1.12,-1.11 -1.7,-0.54v0c-0.23,0.23 -0.33,0.57 -0.27,0.89c0.22,1.33 0.12,2.75 -0.52,4.26c-1.16,2.71 -3.68,4.7 -6.61,4.97c-5.1,0.47 -9.33,-3.85 -8.7,-8.98c0.43,-3.54 3.28,-6.42 6.81,-6.91c1.73,-0.24 3.37,0.09 4.77,0.81c0.39,0.2 0.86,0.13 1.17,-0.18l0,0c0.48,-0.48 0.36,-1.29 -0.24,-1.6C16.31,2.98 16.04,2.85 15.77,2.74z" />
|
|
||||||
</vector>
|
|
||||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="#FFFFFF"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M10.02,6L8.61,7.41 13.19,12l-4.58,4.59L10.02,18l6,-6 -6,-6z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?android:attr/textColorPrimary"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2m0,10c2.7,0 5.8,1.29 6,2L6,18c0.23,-0.72 3.31,-2 6,-2m0,-12C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="#FFFFFF"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M15.61,7.41L14.2,6l-6,6 6,6 1.41,-1.41L11.03,12l4.58,-4.59z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M11,9.83l0,4.17l2,0l0,-4.17l1.59,1.58l1.41,-1.41l-4,-4l-4,4l1.41,1.41z" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5v-3h3.02c0.91,1.21 2.35,2 3.98,2s3.06,-0.79 3.98,-2H19V19zM19,14h-4.18c-0.41,1.16 -1.51,2 -2.82,2s-2.4,-0.84 -2.82,-2H5V5h14V14z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3C19,1.9 18.1,1.01 17,1.01zM17,21H7v-1h10V21zM17,18H7V6h10V18zM17,4H7V3h10V4zM9.5,8.5H12V7H8v4h1.5V8.5zM12,17h4v-4h-1.5v2.5H12V17z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98 0,-0.34 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.09,-0.16 -0.26,-0.25 -0.44,-0.25 -0.06,0 -0.12,0.01 -0.17,0.03l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.06,-0.02 -0.12,-0.03 -0.18,-0.03 -0.17,0 -0.34,0.09 -0.43,0.25l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98 0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.09,0.16 0.26,0.25 0.44,0.25 0.06,0 0.12,-0.01 0.17,-0.03l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.06,0.02 0.12,0.03 0.18,0.03 0.17,0 0.34,-0.09 0.43,-0.25l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM17.45,11.27c0.04,0.31 0.05,0.52 0.05,0.73 0,0.21 -0.02,0.43 -0.05,0.73l-0.14,1.13 0.89,0.7 1.08,0.84 -0.7,1.21 -1.27,-0.51 -1.04,-0.42 -0.9,0.68c-0.43,0.32 -0.84,0.56 -1.25,0.73l-1.06,0.43 -0.16,1.13 -0.2,1.35h-1.4l-0.19,-1.35 -0.16,-1.13 -1.06,-0.43c-0.43,-0.18 -0.83,-0.41 -1.23,-0.71l-0.91,-0.7 -1.06,0.43 -1.27,0.51 -0.7,-1.21 1.08,-0.84 0.89,-0.7 -0.14,-1.13c-0.03,-0.31 -0.05,-0.54 -0.05,-0.74s0.02,-0.43 0.05,-0.73l0.14,-1.13 -0.89,-0.7 -1.08,-0.84 0.7,-1.21 1.27,0.51 1.04,0.42 0.9,-0.68c0.43,-0.32 0.84,-0.56 1.25,-0.73l1.06,-0.43 0.16,-1.13 0.2,-1.35h1.39l0.19,1.35 0.16,1.13 1.06,0.43c0.43,0.18 0.83,0.41 1.23,0.71l0.91,0.7 1.06,-0.43 1.27,-0.51 0.7,1.21 -1.07,0.85 -0.89,0.7 0.14,1.13zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?android:attr/textColorPrimary"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1c-2.73,2.71 -2.73,7.08 0,9.79s7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29c-3.51,3.48 -9.21,3.48 -12.72,0c-3.5,-3.47 -3.53,-9.11 -0.02,-12.58s9.14,-3.47 12.65,0L21,3V10.12zM12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z" />
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M0,15h2L2,9L0,9v6zM3,17h2L5,7L3,7v10zM22,9v6h2L24,9h-2zM19,17h2L21,7h-2v10zM16.5,3h-9C6.67,3 6,3.67 6,4.5v15c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-15c0,-0.83 -0.67,-1.5 -1.5,-1.5zM16,19L8,19L8,5h8v14z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:width="70dp"
|
|
||||||
android:height="70dp"
|
|
||||||
android:drawable="@drawable/ic_launcher"
|
|
||||||
android:gravity="center" />
|
|
||||||
</layer-list>
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.activities.MainActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:liftOnScroll="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:menu="@menu/main"
|
|
||||||
app:title="@string/app_name" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/progress_frag"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ProgressFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="3"
|
|
||||||
tools:layout="@layout/fragment_progress" />
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/todo_frag"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ToDoFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="2"
|
|
||||||
tools:layout="@layout/fragment_todo" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.activities.MainActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:liftOnScroll="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:menu="@menu/main"
|
|
||||||
app:title="@string/app_name" />
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/small_screen_scroll"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/progress_frag"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ProgressFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="270dp"
|
|
||||||
tools:layout="@layout/fragment_progress" />
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/todo_frag"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ToDoFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:layout="@layout/fragment_todo" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.activities.CrashActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
style="?attr/collapsingToolbarLayoutLargeStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
|
|
||||||
app:collapsedTitleTextAppearance="?textAppearanceTitleLarge"
|
|
||||||
app:expandedTitleTextAppearance="?textAppearanceHeadlineLarge"
|
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
|
||||||
app:titleCollapseMode="scale">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/top_app_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:elevation="0dp"
|
|
||||||
app:layout_collapseMode="pin"
|
|
||||||
app:title="@string/error_title" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<me.zhanghai.android.fastscroll.FastScrollNestedScrollView
|
|
||||||
android:id="@+id/nested_scroll_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:text="@string/error_tips"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/crash_log"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:text="@string/no_crash_logs"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</me.zhanghai.android.fastscroll.FastScrollNestedScrollView>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
android:id="@+id/exit_app"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/exit_app"
|
|
||||||
app:icon="@drawable/ic_exit" />
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.activities.MainActivity">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/app_container"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.MainFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:layout="@layout/fragment_main" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
|
||||||
android:id="@+id/drag_handle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/scroll_content_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginLeft="40dp"
|
|
||||||
android:layout_marginRight="40dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_content_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="25sp" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_uuid"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_subject_info"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_state"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginBottom="45dp"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
|
||||||
android:id="@+id/drag_handle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="20dp"
|
|
||||||
android:paddingRight="20dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_sheet_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/add_task"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="25sp" />
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/todo_content"
|
|
||||||
style="?attr/textInputFilledExposedDropdownMenuStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="25dp"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:hint="@string/tasks_textfield_hint"
|
|
||||||
app:errorEnabled="true"
|
|
||||||
app:helperText=" "
|
|
||||||
app:helperTextEnabled="true">
|
|
||||||
|
|
||||||
<AutoCompleteTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:labelFor="@string/tasks_textfield_hint"
|
|
||||||
app:simpleItems="@array/todo_predicts" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
|
||||||
android:id="@+id/todo_subject"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
app:chipSpacing="5dp"
|
|
||||||
app:selectionRequired="true"
|
|
||||||
app:singleSelection="true">
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/unknown_subject"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:checked="true"
|
|
||||||
android:text="@string/subject_unknown" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_chinese"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_chinese" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_math"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_math" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_english"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_english" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_biology"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_biology" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_physics"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_physics" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_chemistry"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_chemistry" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_history"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_history" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_geography"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_geography" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_law"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_law" />
|
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
android:id="@+id/subject_other"
|
|
||||||
style="@style/Widget.Material3.Chip.Filter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/subject_other" />
|
|
||||||
|
|
||||||
</com.google.android.material.chip.ChipGroup>
|
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
|
||||||
android:id="@+id/todo_checked"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:checked="false"
|
|
||||||
android:text="@string/check_one"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_uuid"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="35dp"
|
|
||||||
android:layout_marginBottom="50dp"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_cancel"
|
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/cancel" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_delete"
|
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/delete_one"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:backgroundTint="?attr/colorErrorContainer" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_save"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="5dp"
|
|
||||||
android:text="@string/save" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.fragments.AboutFragment">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:liftOnScroll="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_back"
|
|
||||||
app:title="@string/about_label" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
style="?attr/materialCardViewElevatedStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="20dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="30dp"
|
|
||||||
android:layout_marginBottom="20dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/app_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
style="@style/Widget.Material3.CardView.Elevated"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:cardCornerRadius="128dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:contentDescription="@string/app_name"
|
|
||||||
android:src="@mipmap/ic_launcher" />
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/app_name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:text="@string/app_name"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/app_version"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="7dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textSize="11sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/check_update"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/check_update_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:contentDescription="@string/check_update"
|
|
||||||
app:srcCompat="@drawable/ic_update" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/check_update_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:text="@string/check_update"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/open_source"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/github_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:contentDescription="@string/view_source"
|
|
||||||
app:srcCompat="@drawable/ic_github" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/open_source_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:text="@string/view_source"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/developer_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center_horizontal|center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/developer_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:contentDescription="@string/author"
|
|
||||||
app:srcCompat="@drawable/ic_person" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/developer_name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:text="@string/author"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="15sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.fragments.AboutFragment">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:liftOnScrollTargetViewId="@+id/all_tasks_list">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_back"
|
|
||||||
app:title="@string/all_tasks_label" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/all_tasks_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.activities.MainActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:liftOnScroll="false"
|
|
||||||
app:liftOnScrollTargetViewId="@id/progress_frag">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:menu="@menu/main"
|
|
||||||
app:title="@string/app_name" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/progress_frag"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ProgressFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:minHeight="160dp"
|
|
||||||
tools:layout="@layout/fragment_progress" />
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/todo_frag"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ToDoFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="3"
|
|
||||||
tools:layout="@layout/fragment_todo" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="270dp"
|
|
||||||
android:animateLayoutChanges="true">
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
|
||||||
android:id="@+id/progress_bar"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="15dp"
|
|
||||||
android:layout_marginRight="15dp"
|
|
||||||
android:max="100"
|
|
||||||
app:indicatorSize="170dp"
|
|
||||||
app:indicatorTrackGapSize="10dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:trackColor="?attr/colorSurfaceContainerHighest"
|
|
||||||
app:trackCornerRadius="25dp"
|
|
||||||
app:trackThickness="10dp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/complete_count"
|
|
||||||
style="?attr/textAppearanceDisplaySmall"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:text="0"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceDisplayMedium"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="/"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/total_count"
|
|
||||||
style="?attr/textAppearanceDisplayMedium"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:text="0"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/remain_count"
|
|
||||||
style="?attr/textAppearanceLabelMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".views.fragments.SettingsParentFragment">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/tool_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_back"
|
|
||||||
app:title="@string/settings_label" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/settings_container"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.SettingsFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/todo_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
android:id="@+id/add_item"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:contentDescription="@string/add_task"
|
|
||||||
android:text="@string/add_task"
|
|
||||||
app:icon="@drawable/ic_add" />
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/welcome_page_container"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.welcome.pages.IntroPage"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/control_area"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:layout="@layout/fragment_welcome_intro" />
|
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:id="@+id/control_area"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/welcome_page_container">
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/previous_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|start"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:contentDescription="@string/previous_page"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:srcCompat="@drawable/ic_previous" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
android:id="@+id/center_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|center"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:text="@string/start"
|
|
||||||
app:icon="@drawable/ic_arrow_forward" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/next_btn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:contentDescription="@string/next_page"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:srcCompat="@drawable/ic_next" />
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
style="@style/Widget.Material3.CardView.Elevated"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:cardCornerRadius="128dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:contentDescription="@string/app_name"
|
|
||||||
android:src="@mipmap/ic_launcher" />
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceHeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:text="@string/intro_title"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceBodyMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/intro_description"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="100dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceHeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/progress_title"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface" />
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/welcome_progress"
|
|
||||||
android:name="cn.super12138.todo.views.fragments.ProgressFragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:layout="@layout/fragment_progress" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceBodyMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/progress_description"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="100dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceHeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/todo_btn_title"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
android:id="@+id/add_item"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="30dp"
|
|
||||||
android:layout_marginBottom="30dp"
|
|
||||||
android:contentDescription="@string/add_task"
|
|
||||||
android:text="@string/add_task"
|
|
||||||
app:icon="@drawable/ic_add" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceBodyMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/todo_btn_description"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="100dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceHeadlineMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/todo_item_title"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurface" />
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/todo_item"
|
|
||||||
style="?attr/materialCardViewElevatedStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="80dp"
|
|
||||||
android:layout_marginTop="30dp"
|
|
||||||
android:layout_marginBottom="30dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_content"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:layout_marginTop="18dp"
|
|
||||||
android:text="@string/todo_item_item_title"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_subject"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:text="@string/todo_item_item_subject"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="11sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/check_item_btn"
|
|
||||||
style="?attr/materialIconButtonStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="15dp"
|
|
||||||
android:tooltipText="@string/check_one"
|
|
||||||
app:icon="@drawable/ic_check" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="?attr/textAppearanceBodyMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/todo_item_description_1"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/empty_tip"
|
|
||||||
style="?attr/textAppearanceLabelLarge"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/no_tasks"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
style="?attr/materialCardViewElevatedStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="80dp"
|
|
||||||
android:layout_marginLeft="10dp"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginRight="10dp"
|
|
||||||
android:layout_marginBottom="5dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/item_background"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_content"
|
|
||||||
style="?attr/textAppearanceTitleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:layout_marginTop="18dp"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
tools:text="Title" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/todo_subject"
|
|
||||||
style="?attr/textAppearanceLabelLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="15dp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textSize="11sp"
|
|
||||||
tools:text="Subject" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/check_item_btn"
|
|
||||||
style="?attr/materialIconButtonStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="15dp"
|
|
||||||
android:tooltipText="@string/check_one"
|
|
||||||
app:icon="@drawable/ic_check" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/switchWidget"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@null"
|
|
||||||
android:clickable="false"
|
|
||||||
android:focusable="false" />
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<item
|
|
||||||
android:id="@+id/item_settings"
|
|
||||||
android:icon="@drawable/ic_settings"
|
|
||||||
android:title="@string/settings_label"
|
|
||||||
android:tooltipText="@string/settings_label"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
</menu>
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@mipmap/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue