diff --git a/app/build.gradle.kts b/app/build.gradle.kts index afedb45..74d65da 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ android { minSdk = 24 targetSdk = 35 versionCode = 67 - versionName = "2.3-beta01" + versionName = "2.3-beta02" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -146,16 +146,16 @@ tasks.withType(KotlinCompile::class.java).configureEach { } dependencies { - implementation("androidx.core:core-ktx:1.13.1") - implementation("androidx.compose.ui:ui:1.7.4") - implementation("androidx.compose.material3:material3:1.3.0") - implementation("androidx.compose.material3:material3-window-size-class:1.3.0") - implementation("androidx.compose.material:material:1.7.4") - implementation("androidx.compose.material:material-icons-extended:1.7.4") - implementation("androidx.compose.ui:ui-tooling-preview:1.7.4") + implementation("androidx.core:core-ktx:1.15.0") + implementation("androidx.compose.ui:ui:1.7.5") + implementation("androidx.compose.material3:material3:1.3.1") + implementation("androidx.compose.material3:material3-window-size-class:1.3.1") + implementation("androidx.compose.material:material:1.7.5") + implementation("androidx.compose.material:material-icons-extended:1.7.5") + implementation("androidx.compose.ui:ui-tooling-preview:1.7.5") implementation("com.google.android.material:material:1.12.0") - implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.6") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.activity:activity-compose:1.9.3") implementation("androidx.palette:palette-ktx:1.0.0") implementation("com.google.dagger:hilt-android:2.52") @@ -176,7 +176,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("com.materialkolor:material-kolor:2.0.0") implementation("androidx.datastore:datastore-preferences:1.1.1") - implementation("com.airbnb.android:lottie-compose:6.5.2") + implementation("com.airbnb.android:lottie-compose:6.6.0") implementation("com.squareup.retrofit2:retrofit:2.11.0") implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") @@ -195,6 +195,6 @@ dependencies { implementation("com.github.penfeizhou.android.animation:apng:3.0.1") - debugImplementation("androidx.compose.ui:ui-tooling:1.7.4") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.4") + debugImplementation("androidx.compose.ui:ui-tooling:1.7.5") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.5") } \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ext/SharedPreferencesExt.kt b/app/src/main/java/com/skyd/rays/ext/SharedPreferencesExt.kt index ab59e82..26c0c3b 100644 --- a/app/src/main/java/com/skyd/rays/ext/SharedPreferencesExt.kt +++ b/app/src/main/java/com/skyd/rays/ext/SharedPreferencesExt.kt @@ -4,16 +4,17 @@ import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import com.skyd.rays.appContext +import com.skyd.rays.model.AniVuEncryptedSharedPreferences fun SharedPreferences.editor(editorBuilder: SharedPreferences.Editor.() -> Unit) = edit().apply(editorBuilder).apply() -fun secretSharedPreferences(name: String = "Secret"): SharedPreferences { +fun secretSharedPreferences(name: String = "Secret"): AniVuEncryptedSharedPreferences { val masterKey = MasterKey.Builder(appContext) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() - return EncryptedSharedPreferences.create( + return AniVuEncryptedSharedPreferences.create( appContext, name, masterKey, diff --git a/app/src/main/java/com/skyd/rays/model/AniVuEncryptedSharedPreferences.kt b/app/src/main/java/com/skyd/rays/model/AniVuEncryptedSharedPreferences.kt new file mode 100644 index 0000000..a29ff29 --- /dev/null +++ b/app/src/main/java/com/skyd/rays/model/AniVuEncryptedSharedPreferences.kt @@ -0,0 +1,90 @@ +package com.skyd.rays.model + +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme +import androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme +import androidx.security.crypto.MasterKey +import javax.crypto.AEADBadTagException + +class AniVuEncryptedSharedPreferences( + private val encryptedSharedPreferences: EncryptedSharedPreferences, +) : SharedPreferences { + + companion object { + fun create( + context: Context, + fileName: String, + masterKey: MasterKey, + prefKeyEncryptionScheme: PrefKeyEncryptionScheme, + prefValueEncryptionScheme: PrefValueEncryptionScheme, + ): AniVuEncryptedSharedPreferences { + val creator: () -> EncryptedSharedPreferences = { + EncryptedSharedPreferences.create( + context, + fileName, + masterKey, + prefKeyEncryptionScheme, + prefValueEncryptionScheme + ) as EncryptedSharedPreferences + } + return AniVuEncryptedSharedPreferences( + safeBlock(creator) + ?: context.deleteSharedPreferences(fileName).run { creator() } + ) + } + + private fun safeBlock(block: () -> T): T? { + return try { + block() + } catch (e: AEADBadTagException) { + e.printStackTrace() + null + } + } + } + + override fun getAll() = safeBlock { encryptedSharedPreferences.all }.orEmpty().toMutableMap() + + override fun getString(key: String?, defValue: String?): String? = safeBlock { + encryptedSharedPreferences.getString(key, defValue) + } ?: defValue + + override fun getStringSet( + key: String?, + defValues: MutableSet?, + ): MutableSet? = safeBlock { + encryptedSharedPreferences.getStringSet(key, defValues) + } ?: defValues + + override fun getInt(key: String?, defValue: Int): Int = safeBlock { + encryptedSharedPreferences.getInt(key, defValue) + } ?: defValue + + override fun getLong(key: String?, defValue: Long): Long = safeBlock { + encryptedSharedPreferences.getLong(key, defValue) + } ?: defValue + + override fun getFloat(key: String?, defValue: Float): Float = safeBlock { + encryptedSharedPreferences.getFloat(key, defValue) + } ?: defValue + + override fun getBoolean(key: String?, defValue: Boolean): Boolean = safeBlock { + encryptedSharedPreferences.getBoolean(key, defValue) + } ?: defValue + + override fun contains(key: String?): Boolean = safeBlock { + encryptedSharedPreferences.contains(key) + } ?: false + + override fun edit(): SharedPreferences.Editor = encryptedSharedPreferences.edit() + + override fun registerOnSharedPreferenceChangeListener( + listener: SharedPreferences.OnSharedPreferenceChangeListener, + ) = encryptedSharedPreferences.registerOnSharedPreferenceChangeListener(listener) + + override fun unregisterOnSharedPreferenceChangeListener( + listener: SharedPreferences.OnSharedPreferenceChangeListener, + ) = encryptedSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/rays/ui/component/dialog/TextFieldDialog.kt b/app/src/main/java/com/skyd/rays/ui/component/dialog/TextFieldDialog.kt index 578a0b0..ca63b95 100644 --- a/app/src/main/java/com/skyd/rays/ui/component/dialog/TextFieldDialog.kt +++ b/app/src/main/java/com/skyd/rays/ui/component/dialog/TextFieldDialog.kt @@ -33,6 +33,7 @@ fun TextFieldDialog( errorText: String = "", dismissText: String = stringResource(R.string.cancel), confirmText: String = stringResource(R.string.dialog_ok), + disableConfirmWhenBlank: Boolean = true, onValueChange: (String) -> Unit = {}, onDismissRequest: () -> Unit = {}, onConfirm: (String) -> Unit = {}, @@ -64,7 +65,7 @@ fun TextFieldDialog( }, confirmButton = { TextButton( - enabled = value.isNotBlank(), + enabled = if (disableConfirmWhenBlank) value.isNotBlank() else true, onClick = { focusManager.clearFocus() onConfirm(value) @@ -72,7 +73,7 @@ fun TextFieldDialog( ) { Text( text = confirmText, - color = if (value.isNotBlank()) { + color = if (!disableConfirmWhenBlank || value.isNotBlank()) { Color.Unspecified } else { MaterialTheme.colorScheme.outline.copy(alpha = 0.7f) diff --git a/app/src/main/java/com/skyd/rays/ui/screen/settings/data/importexport/cloud/webdav/WebDavScreen.kt b/app/src/main/java/com/skyd/rays/ui/screen/settings/data/importexport/cloud/webdav/WebDavScreen.kt index d021d91..2bb5042 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/settings/data/importexport/cloud/webdav/WebDavScreen.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/settings/data/importexport/cloud/webdav/WebDavScreen.kt @@ -73,6 +73,9 @@ import kotlinx.coroutines.launch const val WEBDAV_SCREEN_ROUTE = "webDavScreen" +private const val WEBDAV_ACCOUNT_KEY = "webDavAccount" +private const val WEBDAV_PASSWORD_KEY = "webDavPassword" + @Composable fun WebDavScreen(viewModel: WebDavViewModel = hiltViewModel()) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -146,7 +149,11 @@ fun WebDavScreen(viewModel: WebDavViewModel = hiltViewModel()) { WebDavServerPreference.put( context = context, scope = scope, - value = if (!it.endsWith("/")) "$it/" else it + value = if (it.isBlank()) { + WebDavServerPreference.default + } else { + if (it.endsWith("/")) it else "$it/" + } ) } inputDialogIsPassword = false @@ -156,9 +163,14 @@ fun WebDavScreen(viewModel: WebDavViewModel = hiltViewModel()) { inputDialogInfo = Triple( context.getString(R.string.webdav_screen_input_account), account ) { - account = it openInputDialog = false - secretSharedPreferences().editor { putString("webDavAccount", it) } + if (it.isBlank()) { + account = "" + secretSharedPreferences().editor { remove(WEBDAV_ACCOUNT_KEY) } + } else { + account = it + secretSharedPreferences().editor { putString(WEBDAV_ACCOUNT_KEY, it) } + } } inputDialogIsPassword = false openInputDialog = true @@ -167,9 +179,14 @@ fun WebDavScreen(viewModel: WebDavViewModel = hiltViewModel()) { inputDialogInfo = Triple( context.getString(R.string.webdav_screen_input_password), password ) { - password = it openInputDialog = false - secretSharedPreferences().editor { putString("webDavPassword", it) } + if (it.isBlank()) { + password = "" + secretSharedPreferences().editor { remove(WEBDAV_PASSWORD_KEY) } + } else { + password = it + secretSharedPreferences().editor { putString(WEBDAV_PASSWORD_KEY, it) } + } } inputDialogIsPassword = true openInputDialog = true @@ -284,6 +301,7 @@ fun WebDavScreen(viewModel: WebDavViewModel = hiltViewModel()) { isPassword = inputDialogIsPassword, onDismissRequest = { openInputDialog = false }, onConfirm = inputDialogInfo.third, + disableConfirmWhenBlank = false, onValueChange = { inputDialogInfo = inputDialogInfo.copy(second = it) },