diff --git a/app/build.gradle b/app/build.gradle index 1cf6b6814..3e139047c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,7 @@ android { compileSdk = 35 defaultConfig { + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" applicationId = "com.best.deskclock" minSdk = 23 targetSdk = 35 @@ -71,7 +72,9 @@ android { } lintOptions { - abortOnError = false + baseline file("lint-baseline.xml") + abortOnError true + fatal "InconsistentArrays" } compileOptions { @@ -94,6 +97,11 @@ android { } dependencies { + androidTestImplementation "androidx.test.ext:junit:1.2.1" + androidTestImplementation "androidx.test.espresso:espresso-core:3.6.1" + androidTestImplementation "androidx.test:runner:1.6.1" + androidTestImplementation "androidx.test:rules:1.6.1" + androidTestImplementation "androidx.test.espresso:espresso-intents:3.6.1" // Room components implementation "androidx.room:room-runtime:$rootProject.roomVersion" annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion" diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml new file mode 100644 index 000000000..31f97a145 --- /dev/null +++ b/app/lint-baseline.xml @@ -0,0 +1,32794 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/androidTest/java/com/best/deskclock/settings/SettingsSmokeTest.java b/app/src/androidTest/java/com/best/deskclock/settings/SettingsSmokeTest.java new file mode 100644 index 000000000..7d24a6bb4 --- /dev/null +++ b/app/src/androidTest/java/com/best/deskclock/settings/SettingsSmokeTest.java @@ -0,0 +1,32 @@ +package com.best.deskclock.settings; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; + +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import com.best.deskclock.DeskClock; +import com.best.deskclock.R; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class SettingsSmokeTest { + + @Rule + public ActivityScenarioRule mActivityScenarioRule = + new ActivityScenarioRule<>(DeskClock.class); + + @Test + public void testAppLaunches() { + // Just verify DeskClock launches and displays the "Clock" tab or similar + onView(withText(R.string.menu_clock)).check(matches(isDisplayed())); + } +} diff --git a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomListPreference.java b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomListPreference.java index e69de29bb..e20a3a3f8 100644 --- a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomListPreference.java +++ b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomListPreference.java @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only + +package com.best.deskclock.settings.custompreference; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceViewHolder; + +import com.best.deskclock.R; + +/** + * A {@link ListPreference} with a custom Material-style layout. + * + *

This class applies a custom card-based appearance to the preference row + * and updates its typography and visual styling based on the user's settings + * (font, card background, card border, etc.).

+ * + *

The preference behavior remains identical to a standard {@link ListPreference}; + * only its visual presentation is customized.

+ */ +public class CustomListPreference extends ListPreference { + + public CustomListPreference(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.settings_preference_layout); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + PreferenceStyler.apply(holder); + super.onBindViewHolder(holder); + } + +} diff --git a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreference.java b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreference.java index e69de29bb..ddfb7fc4a 100644 --- a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreference.java +++ b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreference.java @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only + +package com.best.deskclock.settings.custompreference; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.best.deskclock.R; + +/** + * A {@link Preference} with a custom Material-style layout. + * + *

This class applies a custom card-based appearance to the preference row + * and updates its typography and visual styling based on the user's settings + * (font, card background, card border, etc.).

+ * + *

The preference behavior remains identical to a standard {@link Preference}; + * only its visual presentation is customized.

+ */ +public class CustomPreference extends Preference { + + public CustomPreference(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.settings_preference_layout); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + PreferenceStyler.apply(holder); + super.onBindViewHolder(holder); + } + +} diff --git a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreferenceCategory.java b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreferenceCategory.java index e69de29bb..a26ce7c3a 100644 --- a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreferenceCategory.java +++ b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomPreferenceCategory.java @@ -0,0 +1,49 @@ +package com.best.deskclock.settings.custompreference; + +import static com.best.deskclock.DeskClockApplication.getDefaultSharedPreferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceViewHolder; + +import com.best.deskclock.R; +import com.best.deskclock.data.SettingsDAO; +import com.best.deskclock.utils.ThemeUtils; + +/** + * A {@link PreferenceCategory} with a custom layout. + * + *

This class updates its typography and visual styling based on the user's settings + * (font, accent color, etc.).

+ * + *

The preference behavior remains identical to a standard {@link PreferenceCategory}; + * only its visual presentation is customized.

+ */ +public class CustomPreferenceCategory extends PreferenceCategory { + + public CustomPreferenceCategory(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.settings_preference_category_layout); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + if (holder.itemView.isInEditMode()) { + // Skip logic during Android Studio preview + return; + } + + super.onBindViewHolder(holder); + + SharedPreferences prefs = getDefaultSharedPreferences(getContext()); + + TextView prefTitle = (TextView) holder.findViewById(android.R.id.title); + prefTitle.setTypeface(ThemeUtils.boldTypeface(SettingsDAO.getGeneralFont(prefs))); + } +} diff --git a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSeekbarPreference.java b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSeekbarPreference.java index e69de29bb..93740b1a0 100644 --- a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSeekbarPreference.java +++ b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSeekbarPreference.java @@ -0,0 +1,975 @@ +// SPDX-License-Identifier: GPL-3.0-only + +package com.best.deskclock.settings.custompreference; + +import static androidx.core.util.TypedValueCompat.dpToPx; +import static com.best.deskclock.DeskClockApplication.getDefaultSharedPreferences; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_ANALOG_CLOCK_SIZE; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_DIGITAL_CLOCK_FONT_SIZE; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_SHADOW_OFFSET; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_BLUR_INTENSITY; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_ALARM_TITLE_FONT_SIZE_PREF; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_BLUETOOTH_VOLUME; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_MATERIAL_YOU_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_SCREENSAVER_BRIGHTNESS; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_SHAKE_INTENSITY; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_TIMER_SHAKE_INTENSITY; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_WIDGETS_FONT_SIZE; +import static com.best.deskclock.settings.PreferencesDefaultValues.DEFAULT_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_ALARM_ANALOG_CLOCK_SIZE; +import static com.best.deskclock.settings.PreferencesKeys.KEY_ALARM_BLUR_INTENSITY; +import static com.best.deskclock.settings.PreferencesKeys.KEY_ALARM_DIGITAL_CLOCK_FONT_SIZE; +import static com.best.deskclock.settings.PreferencesKeys.KEY_ALARM_SHADOW_OFFSET; +import static com.best.deskclock.settings.PreferencesKeys.KEY_ALARM_TITLE_FONT_SIZE_PREF; +import static com.best.deskclock.settings.PreferencesKeys.KEY_ANALOG_CLOCK_SIZE; +import static com.best.deskclock.settings.PreferencesKeys.KEY_BLUETOOTH_VOLUME; +import static com.best.deskclock.settings.PreferencesKeys.KEY_DIGITAL_CLOCK_FONT_SIZE; +import static com.best.deskclock.settings.PreferencesKeys.KEY_DIGITAL_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_MATERIAL_YOU_DIGITAL_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_MATERIAL_YOU_NEXT_ALARM_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_MATERIAL_YOU_VERTICAL_DIGITAL_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_NEXT_ALARM_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_SCREENSAVER_ANALOG_CLOCK_SIZE; +import static com.best.deskclock.settings.PreferencesKeys.KEY_SCREENSAVER_BLUR_INTENSITY; +import static com.best.deskclock.settings.PreferencesKeys.KEY_SCREENSAVER_BRIGHTNESS; +import static com.best.deskclock.settings.PreferencesKeys.KEY_SCREENSAVER_DIGITAL_CLOCK_FONT_SIZE; +import static com.best.deskclock.settings.PreferencesKeys.KEY_SHAKE_INTENSITY; +import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_BLUR_INTENSITY; +import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_SHADOW_OFFSET; +import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_SHAKE_INTENSITY; +import static com.best.deskclock.settings.PreferencesKeys.KEY_VERTICAL_WIDGET_BACKGROUND_CORNER_RADIUS; +import static com.best.deskclock.utils.RingtoneUtils.ALARM_PREVIEW_DURATION_MS; + + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.core.widget.TextViewCompat; +import androidx.preference.PreferenceViewHolder; +import androidx.preference.SeekBarPreference; + +import com.best.deskclock.R; +import com.best.deskclock.data.DataModel; +import com.best.deskclock.data.SettingsDAO; +import com.best.deskclock.ringtone.RingtonePreviewKlaxon; +import com.best.deskclock.utils.RingtoneUtils; +import com.best.deskclock.utils.SdkUtils; +import com.best.deskclock.utils.ThemeUtils; +import com.best.alarmclock.WidgetUtils; +import com.google.android.material.card.MaterialCardView; +import com.google.android.material.color.MaterialColors; + +import java.util.Locale; + +public class CustomSeekbarPreference extends SeekBarPreference { + + private static final int MIN_FONT_SIZE_VALUE = 20; + private static final int MIN_CORNER_RADIUS_VALUE = 0; + private static final int MIN_SHAKE_INTENSITY_VALUE = DEFAULT_SHAKE_INTENSITY; + private static final int MIN_TIMER_SHAKE_INTENSITY_VALUE = DEFAULT_TIMER_SHAKE_INTENSITY; + private static final int MIN_BRIGHTNESS_VALUE = 0; + private static final int MIN_BLUETOOTH_VOLUME = 10; + private static final int MIN_SHADOW_OFFSET_VALUE = 1; + private static final int MIN_BLUR_INTENSITY_VALUE = 1; + private static final int MIN_ANALOG_CLOCK_SIZE_VALUE = 1; + + private Context mContext; + private SharedPreferences mPrefs; + private SeekBar mSeekBar; + private ImageView mSeekBarMinus; + private ImageView mSeekBarPlus; + private TextView mResetSeekBar; + private final Handler mRingtoneHandler = new Handler(Looper.getMainLooper()); + private Runnable mRingtoneStopRunnable; + private boolean mIsPreviewPlaying = false; + + public CustomSeekbarPreference(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.settings_preference_seekbar_layout); + } + + /** + * Binds the preference view, initializes the SeekBar and associated UI elements, + * and sets up listeners for user interactions. + */ + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + if (holder.itemView.isInEditMode()) { + // Skip logic during Android Studio preview + return; + } + + mContext = getContext(); + mPrefs = getDefaultSharedPreferences(mContext); + String fontPath = SettingsDAO.getGeneralFont(mPrefs); + Typeface typeFace = ThemeUtils.loadFont(fontPath); + + final MaterialCardView prefCardView = (MaterialCardView) holder.findViewById(R.id.pref_card_view); + final boolean isCardBackgroundDisplayed = SettingsDAO.isCardBackgroundDisplayed(mPrefs); + final boolean isCardBorderDisplayed = SettingsDAO.isCardBorderDisplayed(mPrefs); + + float strokeWidth = dpToPx(2, mContext.getResources().getDisplayMetrics()); + + if (isCardBackgroundDisplayed) { + prefCardView.setCardBackgroundColor(MaterialColors.getColor(prefCardView, com.google.android.material.R.attr.colorSurface)); + } else { + prefCardView.setCardBackgroundColor(Color.TRANSPARENT); + } + + if (isCardBorderDisplayed) { + prefCardView.setStrokeWidth((int) strokeWidth); + } else { + prefCardView.setStrokeWidth(0); + } + + super.onBindViewHolder(holder); + + holder.itemView.setClickable(false); + + final TextView seekBarTitle = (TextView) holder.findViewById(android.R.id.title); + seekBarTitle.setTypeface(typeFace); + + mSeekBar = (SeekBar) holder.findViewById(R.id.seekbar); + configureSeekBarMinValue(); + + final TextView seekBarSummary = (TextView) holder.findViewById(android.R.id.summary); + seekBarSummary.setTypeface(typeFace); + + setSeekBarProgress(seekBarSummary); + + mSeekBarMinus = (ImageView) holder.findViewById(R.id.seekbar_minus_icon); + mSeekBarPlus = (ImageView) holder.findViewById(R.id.seekbar_plus_icon); + mResetSeekBar = (TextView) holder.findViewById(R.id.reset_seekbar_value); + + configureSeekBarButtonDrawables(); + setupSeekBarButton(mSeekBarMinus, isBluetoothVolumePreference() ? -10 : -5, seekBarSummary); + setupSeekBarButton(mSeekBarPlus, isBluetoothVolumePreference() ? 10 : 5, seekBarSummary); + updateSeekBarButtonStates(); + updateResetButtonStates(); + + mResetSeekBar.setTypeface(ThemeUtils.boldTypeface(fontPath)); + mResetSeekBar.setOnClickListener(v -> { + resetPreference(); + setSeekBarProgress(seekBarSummary); + startRingtonePreviewForBluetoothDevices(); + updateDigitalWidgets(); + updateSeekBarButtonStates(); + updateResetButtonStates(); + }); + + mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + if (SdkUtils.isBeforeAndroid8()) { + if (isShakeIntensityPreference() && progress < MIN_SHAKE_INTENSITY_VALUE) { + seekBar.setProgress(MIN_SHAKE_INTENSITY_VALUE); + } else if (isTimerShakeIntensityPreference() && progress < MIN_TIMER_SHAKE_INTENSITY_VALUE) { + seekBar.setProgress(MIN_TIMER_SHAKE_INTENSITY_VALUE); + } else if ((isTimerShadowOffsetPreference() || isAlarmShadowOffsetPreference()) + && progress < MIN_SHADOW_OFFSET_VALUE) { + seekBar.setProgress(MIN_SHADOW_OFFSET_VALUE); + } else if ((isScreensaverBlurIntensityPreference() + || isTimerBlurIntensityPreference() + || isAlarmBlurIntensityPreference()) + && progress < MIN_BLUR_INTENSITY_VALUE) { + seekBar.setProgress(MIN_BLUR_INTENSITY_VALUE); + } else if (isBluetoothVolumePreference() && progress < MIN_BLUETOOTH_VOLUME) { + seekBar.setProgress(MIN_BLUETOOTH_VOLUME); + } else if ((isAnalogClockSizePreference() + || isScreensaverAnalogClockSizePreference() + || isAlarmAnalogClockSizePreference()) + && progress < MIN_ANALOG_CLOCK_SIZE_VALUE) { + seekBar.setProgress(MIN_ANALOG_CLOCK_SIZE_VALUE); + } else if (!isScreensaverBrightnessPreference() + && !isDigitalWidgetBackgroundCornerRadius() + && !isNextAlarmWidgetBackgroundCornerRadius() + && !isVerticalWidgetBackgroundCornerRadius() + && !isMaterialYouDigitalWidgetBackgroundCornerRadius() + && !isMaterialYouNextAlarmWidgetBackgroundCornerRadius() + && !isMaterialYouVerticalWidgetBackgroundCornerRadius() + && progress < MIN_FONT_SIZE_VALUE) { + seekBar.setProgress(MIN_FONT_SIZE_VALUE); + } + } + + updateSeekBarSummary(seekBarSummary, progress); + updateSeekBarButtonStates(); + updateResetButtonStates(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + startRingtonePreviewForBluetoothDevices(); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + int finalProgress = seekBar.getProgress(); + saveSeekBarValue(finalProgress); + updateDigitalWidgets(); + } + }); + } + + /** + * For Android Oreo and above, sets the minimum value of the SeekBar based on the preference type. + */ + private void configureSeekBarMinValue() { + if (SdkUtils.isAtLeastAndroid8()) { + if (isScreensaverBrightnessPreference()) { + mSeekBar.setMin(MIN_BRIGHTNESS_VALUE); + } else if (isDigitalWidgetBackgroundCornerRadius() + || isNextAlarmWidgetBackgroundCornerRadius() + || isVerticalWidgetBackgroundCornerRadius() + || isMaterialYouDigitalWidgetBackgroundCornerRadius() + || isMaterialYouNextAlarmWidgetBackgroundCornerRadius() + || isMaterialYouVerticalWidgetBackgroundCornerRadius()) { + mSeekBar.setMin(MIN_CORNER_RADIUS_VALUE); + } else if (isShakeIntensityPreference()) { + mSeekBar.setMin(MIN_SHAKE_INTENSITY_VALUE); + } else if (isTimerShakeIntensityPreference()) { + mSeekBar.setMin(MIN_TIMER_SHAKE_INTENSITY_VALUE); + } else if (isTimerShadowOffsetPreference() || isAlarmShadowOffsetPreference()) { + mSeekBar.setMin(MIN_SHADOW_OFFSET_VALUE); + } else if (isScreensaverBlurIntensityPreference() + || isTimerBlurIntensityPreference() + || isAlarmBlurIntensityPreference()) { + mSeekBar.setMin(MIN_BLUR_INTENSITY_VALUE); + } else if (isBluetoothVolumePreference()) { + mSeekBar.setMin(MIN_BLUETOOTH_VOLUME); + } else if (isAnalogClockSizePreference() + || isScreensaverAnalogClockSizePreference() + || isAlarmAnalogClockSizePreference()) { + mSeekBar.setMin(MIN_ANALOG_CLOCK_SIZE_VALUE); + } else { + mSeekBar.setMin(MIN_FONT_SIZE_VALUE); + } + } + } + + /** + * Sets the SeekBar value base on the current preference (screensaver brightness, widget font size, etc.) + * and updates the SeekBar summary to reflect this value. + */ + private void setSeekBarProgress(TextView seekBarSummary) { + int currentProgress; + if (isScreensaverBrightnessPreference()) { + currentProgress = getScreensaverBrightnessPreferenceValue(); + } else if (isScreensaverDigitalClockFontSizePreference()) { + currentProgress = getScreensaverDigitalClockFontSizeValue(); + } else if (isScreensaverAnalogClockSizePreference()) { + currentProgress = getScreensaverAnalogClockSizeValue(); + } else if (isScreensaverBlurIntensityPreference()) { + currentProgress = getScreensaverBlurIntensityValue(); + } else if (isDigitalWidgetBackgroundCornerRadius()) { + currentProgress = getDigitalWidgetBackgroundCornerRadius(); + } else if (isNextAlarmWidgetBackgroundCornerRadius()) { + currentProgress = getNextAlarmWidgetBackgroundCornerRadius(); + } else if (isVerticalWidgetBackgroundCornerRadius()) { + currentProgress = getVerticalWidgetBackgroundCornerRadius(); + } else if (isMaterialYouDigitalWidgetBackgroundCornerRadius()) { + currentProgress = getMaterialYouDigitalWidgetBackgroundCornerRadius(); + } else if (isMaterialYouNextAlarmWidgetBackgroundCornerRadius()) { + currentProgress = getMaterialYouNextAlarmWidgetBackgroundCornerRadius(); + } else if (isMaterialYouVerticalWidgetBackgroundCornerRadius()) { + currentProgress = getMaterialYouVerticalWidgetBackgroundCornerRadius(); + } else if (isShakeIntensityPreference()) { + currentProgress = getShakeIntensityPreferenceValue(); + } else if (isTimerShakeIntensityPreference()) { + currentProgress = getTimerShakeIntensityPreferenceValue(); + } else if (isTimerShadowOffsetPreference()) { + currentProgress = getTimerShadowOffsetValue(); + } else if (isTimerBlurIntensityPreference()) { + currentProgress = getTimerBlurIntensityValue(); + } else if (isAlarmDigitalClockFontSizePreference()) { + currentProgress = getAlarmDigitalClockFontSizeValue(); + } else if (isAlarmTitleFontSizePreference()) { + currentProgress = getAlarmTitleFontSizeValue(); + } else if (isAlarmShadowOffsetPreference()) { + currentProgress = getAlarmShadowOffsetValue(); + } else if (isAlarmBlurIntensityPreference()) { + currentProgress = getAlarmBlurIntensityValue(); + } else if (isBluetoothVolumePreference()) { + currentProgress = getBluetoothVolumeValue(); + } else if (isAnalogClockSizePreference()) { + currentProgress = getAnalogClockSizeValue(); + } else if (isAlarmAnalogClockSizePreference()) { + currentProgress = getAlarmAnalogClockSizeValue(); + } else if (isDigitalClockFontSizePreference()) { + currentProgress = getDigitalClockFontSizeValue(); + } else { + currentProgress = getWidgetPreferenceValue(); + } + + updateSeekBarSummary(seekBarSummary, currentProgress); + mSeekBar.setProgress(currentProgress); + } + + /** + * Updates the SeekBar summary. + */ + private void updateSeekBarSummary(TextView seekBarSummary, int progress) { + if (SdkUtils.isBeforeAndroid8() && progress < getSeekBarMinValue()) { + return; + } + + if (progress == getDefaultSeekBarValue()) { + seekBarSummary.setText(R.string.label_default); + } else if (isScreensaverBrightnessPreference() + || isScreensaverAnalogClockSizePreference() + || isBluetoothVolumePreference() + || isAnalogClockSizePreference() + || isAlarmAnalogClockSizePreference()) { + String formattedText = String.format(Locale.getDefault(), "%d%%", progress); + seekBarSummary.setText(formattedText); + } else { + seekBarSummary.setText(String.valueOf(progress)); + } + } + + /** + * Sets the icons for the minus and plus buttons of the SeekBar based on the current preference type. + */ + private void configureSeekBarButtonDrawables() { + if (isScreensaverBrightnessPreference()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_brightness_decrease)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_brightness_increase)); + } else if (isDigitalWidgetBackgroundCornerRadius() + || isNextAlarmWidgetBackgroundCornerRadius() + || isVerticalWidgetBackgroundCornerRadius() + || isMaterialYouDigitalWidgetBackgroundCornerRadius() + || isMaterialYouNextAlarmWidgetBackgroundCornerRadius() + || isMaterialYouVerticalWidgetBackgroundCornerRadius()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_rounded_corner_decrease)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_rounded_corner_increase)); + } else if (isShakeIntensityPreference() || isTimerShakeIntensityPreference()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_sensor_low)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_sensor_high)); + } else if (isTimerShadowOffsetPreference() || isAlarmShadowOffsetPreference()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_shadow_decrease)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_shadow_increase)); + } else if (isScreensaverBlurIntensityPreference() + || isTimerBlurIntensityPreference() + || isAlarmBlurIntensityPreference()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_blur_decrease)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_blur_increase)); + } else if (isBluetoothVolumePreference()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_volume_down)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_volume_up)); + } else if (isAnalogClockSizePreference() + || isScreensaverAnalogClockSizePreference() + || isAlarmAnalogClockSizePreference()) { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_zoom_in)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_zoom_out)); + } else { + mSeekBarMinus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_text_decrease)); + mSeekBarPlus.setImageDrawable(AppCompatResources.getDrawable(mContext, R.drawable.ic_text_increase)); + } + } + + /** + * Configures the buttons to increase or decrease the value of the SeekBar. + * The interface is updated with the new value and saved in the preferences. + * Sends a broadcast if it concerns buttons related to widget settings (widget font size). + */ + private void setupSeekBarButton(ImageView button, final int delta, final TextView seekBarSummary) { + button.setOnClickListener(v -> { + int newSeekBarValue = getNewSeekBarValue(delta); + mSeekBar.setProgress(newSeekBarValue); + updateSeekBarSummary(seekBarSummary, newSeekBarValue); + saveSeekBarValue(newSeekBarValue); + startRingtonePreviewForBluetoothDevices(); + updateDigitalWidgets(); + updateSeekBarButtonStates(); + updateResetButtonStates(); + }); + } + + /** + * Updates the enabled state of the minus and plus buttons based on the current SeekBar value + * and the enabled state of the preference. + * + *

Disables the minus button if the current value is at the minimum allowed, + * and disables the plus button if the value is at the maximum allowed.

+ */ + private void updateSeekBarButtonStates() { + boolean isPrefEnabled = isEnabled(); + int current = mSeekBar.getProgress(); + int min = getSeekBarMinValue(); + int max = mSeekBar.getMax(); + + ThemeUtils.updateSeekBarButtonEnabledState(mContext, mSeekBarMinus, isPrefEnabled && current > min); + ThemeUtils.updateSeekBarButtonEnabledState(mContext, mSeekBarPlus, isPrefEnabled && current < max); + } + + /** + * Updates the enabled state of the reset button based on the current SeekBar value + * and the enabled state of the preference. + * + *

The reset button is enabled only when the SeekBar's value differs from the default + * value for the current preference type.

+ */ + private void updateResetButtonStates() { + boolean isEnabled = isEnabled() && !isSeekBarAtDefault(mSeekBar.getProgress()); + mResetSeekBar.setEnabled(isEnabled); + + if (isEnabled) { + int enabledColor = MaterialColors.getColor(mContext, androidx.appcompat.R.attr.colorPrimary, Color.BLACK); + + mResetSeekBar.setTextColor(enabledColor); + TextViewCompat.setCompoundDrawableTintList(mResetSeekBar, ColorStateList.valueOf(enabledColor)); + } else { + int disabledColor = mContext.getColor(R.color.colorDisabled); + mResetSeekBar.setTextColor(disabledColor); + TextViewCompat.setCompoundDrawableTintList(mResetSeekBar, ColorStateList.valueOf(disabledColor)); + } + } + + /** + * @return true if the SeekBar is currently set to its default value, depending on the preference type. + */ + private boolean isSeekBarAtDefault(int currentValue) { + return currentValue == getDefaultSeekBarValue(); + } + + /** + * @return a new value for the SeekBar by applying a delta to the current value while respecting + * the minimum and maximum value of the SeekBar. + */ + private int getNewSeekBarValue(int delta) { + int currentSeekBarValue = mSeekBar.getProgress(); + + return Math.min(Math.max(currentSeekBarValue + delta, getSeekBarMinValue()), mSeekBar.getMax()); + } + + /** + * @return the minimum allowed value for the SeekBar depending on the preference type. + */ + private int getSeekBarMinValue() { + if (isScreensaverBrightnessPreference()) { + return MIN_BRIGHTNESS_VALUE; + } else if (isDigitalWidgetBackgroundCornerRadius() + || isNextAlarmWidgetBackgroundCornerRadius() + || isVerticalWidgetBackgroundCornerRadius() + || isMaterialYouDigitalWidgetBackgroundCornerRadius() + || isMaterialYouNextAlarmWidgetBackgroundCornerRadius() + || isMaterialYouVerticalWidgetBackgroundCornerRadius()) { + return MIN_CORNER_RADIUS_VALUE; + } else if (isShakeIntensityPreference()) { + return MIN_SHAKE_INTENSITY_VALUE; + } else if (isTimerShakeIntensityPreference()) { + return MIN_TIMER_SHAKE_INTENSITY_VALUE; + } else if (isTimerShadowOffsetPreference() || isAlarmShadowOffsetPreference()) { + return MIN_SHADOW_OFFSET_VALUE; + } else if (isScreensaverBlurIntensityPreference() + || isTimerBlurIntensityPreference() + || isAlarmBlurIntensityPreference()) { + return MIN_BLUR_INTENSITY_VALUE; + } else if (isBluetoothVolumePreference()) { + return MIN_BLUETOOTH_VOLUME; + } else if (isAnalogClockSizePreference() + || isScreensaverAnalogClockSizePreference() + || isAlarmAnalogClockSizePreference()) { + return MIN_ANALOG_CLOCK_SIZE_VALUE; + } else { + return MIN_FONT_SIZE_VALUE; + } + } + + /** + * @return the default value for the SeekBar depending on the preference type. + */ + private int getDefaultSeekBarValue() { + if (isScreensaverBrightnessPreference()) { + return DEFAULT_SCREENSAVER_BRIGHTNESS; + } else if (isDigitalWidgetBackgroundCornerRadius() + || isNextAlarmWidgetBackgroundCornerRadius() + || isVerticalWidgetBackgroundCornerRadius()) { + return DEFAULT_WIDGET_BACKGROUND_CORNER_RADIUS; + } else if (isMaterialYouDigitalWidgetBackgroundCornerRadius() + || isMaterialYouNextAlarmWidgetBackgroundCornerRadius() + || isMaterialYouVerticalWidgetBackgroundCornerRadius()) { + return DEFAULT_MATERIAL_YOU_WIDGET_BACKGROUND_CORNER_RADIUS; + } else if (isShakeIntensityPreference()) { + return DEFAULT_SHAKE_INTENSITY; + } else if (isTimerShakeIntensityPreference()) { + return DEFAULT_TIMER_SHAKE_INTENSITY; + } else if (isScreensaverDigitalClockFontSizePreference() + || isDigitalClockFontSizePreference() + || isAlarmDigitalClockFontSizePreference()) { + return DEFAULT_DIGITAL_CLOCK_FONT_SIZE; + } else if (isAlarmTitleFontSizePreference()) { + return DEFAULT_ALARM_TITLE_FONT_SIZE_PREF; + } else if (isTimerShadowOffsetPreference() || isAlarmShadowOffsetPreference()) { + return DEFAULT_SHADOW_OFFSET; + } else if (isScreensaverBlurIntensityPreference() + || isTimerBlurIntensityPreference() + || isAlarmBlurIntensityPreference()) { + return DEFAULT_BLUR_INTENSITY; + } else if (isBluetoothVolumePreference()) { + return DEFAULT_BLUETOOTH_VOLUME; + } else if (isAnalogClockSizePreference() + || isScreensaverAnalogClockSizePreference() + || isAlarmAnalogClockSizePreference()) { + return DEFAULT_ANALOG_CLOCK_SIZE; + } else { + return DEFAULT_WIDGETS_FONT_SIZE; + } + } + + /** + * Saves the current value of the SeekBar in SharedPreferences using the appropriate preference key. + */ + private void saveSeekBarValue(int value) { + mPrefs.edit().putInt(getKey(), value).apply(); + } + + /** + * Resets the SeekBar value. + */ + private void resetPreference() { + mPrefs.edit().remove(getKey()).apply(); + } + + /** + * Update digital widgets if the Preference is linked to the widgets one. + */ + private void updateDigitalWidgets() { + if (!isScreensaverBrightnessPreference() + && !isScreensaverDigitalClockFontSizePreference() + && !isScreensaverAnalogClockSizePreference() + && !isScreensaverBlurIntensityPreference() + && !isShakeIntensityPreference() + && !isTimerShakeIntensityPreference() + && !isTimerShadowOffsetPreference() + && !isTimerBlurIntensityPreference() + && !isAlarmDigitalClockFontSizePreference() + && !isAlarmTitleFontSizePreference() + && !isAlarmShadowOffsetPreference() + && !isAlarmBlurIntensityPreference() + && !isBluetoothVolumePreference() + && !isAnalogClockSizePreference() + && !isAlarmAnalogClockSizePreference() + && !isDigitalClockFontSizePreference()) { + + WidgetUtils.updateAllDigitalWidgets(mContext); + } + } + + /** + * Retrieves the current value of the SeekBar related to the widget preference (widget font size) + * from the SharedPreferences. + */ + private int getWidgetPreferenceValue() { + return mPrefs.getInt(getKey(), DEFAULT_WIDGETS_FONT_SIZE); + } + + /** + * Retrieves the current value of the SeekBar related to the digital widget background + * corner radius from the SharedPreferences. + */ + private int getDigitalWidgetBackgroundCornerRadius() { + return mPrefs.getInt(getKey(), DEFAULT_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * Retrieves the current value of the SeekBar related to the Next alarm widget background + * corner radius from the SharedPreferences. + */ + private int getNextAlarmWidgetBackgroundCornerRadius() { + return mPrefs.getInt(getKey(), DEFAULT_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * Retrieves the current value of the SeekBar related to the vertical widget background + * corner radius from the SharedPreferences. + */ + private int getVerticalWidgetBackgroundCornerRadius() { + return mPrefs.getInt(getKey(), DEFAULT_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * Retrieves the current value of the SeekBar related to the Material You digital widget + * background corner radius from the SharedPreferences. + */ + private int getMaterialYouDigitalWidgetBackgroundCornerRadius() { + return mPrefs.getInt(getKey(), DEFAULT_MATERIAL_YOU_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * Retrieves the current value of the SeekBar related to the Material You Next alarm widget + * background corner radius from the SharedPreferences. + */ + private int getMaterialYouNextAlarmWidgetBackgroundCornerRadius() { + return mPrefs.getInt(getKey(), DEFAULT_MATERIAL_YOU_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * Retrieves the current value of the SeekBar related to the Material You vertical digital widget + * background corner radius from the SharedPreferences. + */ + private int getMaterialYouVerticalWidgetBackgroundCornerRadius() { + return mPrefs.getInt(getKey(), DEFAULT_MATERIAL_YOU_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * Retrieves the current value of the SeekBar related to the screensaver brightness from + * the SharedPreferences. + */ + private int getScreensaverBrightnessPreferenceValue() { + return mPrefs.getInt(getKey(), DEFAULT_SCREENSAVER_BRIGHTNESS); + } + + /** + * @return the current value of the SeekBar related to the screensaver analog clock size + * from SharedPreferences. + */ + private int getScreensaverAnalogClockSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_ANALOG_CLOCK_SIZE); + } + + /** + * Retrieves the current value of the SeekBar related to the font size of the screensaver clock + * from SharedPreferences. + */ + private int getScreensaverDigitalClockFontSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_DIGITAL_CLOCK_FONT_SIZE); + } + + /** + * @return the current value of the SeekBar related to screensaver blur effect from SharedPreferences. + */ + private int getScreensaverBlurIntensityValue() { + return mPrefs.getInt(getKey(), DEFAULT_BLUR_INTENSITY); + } + + /** + * Retrieves the current value of the SeekBar related to the shake intensity from + * the SharedPreferences. + */ + private int getShakeIntensityPreferenceValue() { + return mPrefs.getInt(getKey(), DEFAULT_SHAKE_INTENSITY); + } + + /** + * Retrieves the current value of the SeekBar related to the timer shake intensity from + * the SharedPreferences. + */ + private int getTimerShakeIntensityPreferenceValue() { + return mPrefs.getInt(getKey(), DEFAULT_TIMER_SHAKE_INTENSITY); + } + + /** + * @return the current value of the SeekBar related to timer shadow offset from SharedPreferences. + */ + private int getTimerShadowOffsetValue() { + return mPrefs.getInt(getKey(), DEFAULT_SHADOW_OFFSET); + } + + /** + * @return the current value of the SeekBar related to timer blur effect from SharedPreferences. + */ + private int getTimerBlurIntensityValue() { + return mPrefs.getInt(getKey(), DEFAULT_BLUR_INTENSITY); + } + + /** + * @return the current value of the SeekBar related to the analog clock size from SharedPreferences. + */ + private int getAnalogClockSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_ANALOG_CLOCK_SIZE); + } + + /** + * Retrieves the current value of the SeekBar related to the font size of the clock + * from SharedPreferences. + */ + private int getDigitalClockFontSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_DIGITAL_CLOCK_FONT_SIZE); + } + + /** + * @return the current value of the SeekBar related to the alarm analog clock size + * from SharedPreferences. + */ + private int getAlarmAnalogClockSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_ANALOG_CLOCK_SIZE); + } + + /** + * Retrieves the current value of the SeekBar related to the font size of the alarm clock + * from SharedPreferences. + */ + private int getAlarmDigitalClockFontSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_DIGITAL_CLOCK_FONT_SIZE); + } + + /** + * Retrieves the current value of the SeekBar related to the alarm title font size + * from SharedPreferences. + */ + private int getAlarmTitleFontSizeValue() { + return mPrefs.getInt(getKey(), DEFAULT_ALARM_TITLE_FONT_SIZE_PREF); + } + + /** + * @return the current value of the SeekBar related to alarm shadow offset from SharedPreferences. + */ + private int getAlarmShadowOffsetValue() { + return mPrefs.getInt(getKey(), DEFAULT_SHADOW_OFFSET); + } + + /** + * @return the current value of the SeekBar related to alarm blur effect from SharedPreferences. + */ + private int getAlarmBlurIntensityValue() { + return mPrefs.getInt(getKey(), DEFAULT_BLUR_INTENSITY); + } + + /** + * @return the current value of the SeekBar related to volume when a Bluetooth device + * is connected from SharedPreferences. + */ + private int getBluetoothVolumeValue() { + return mPrefs.getInt(getKey(), DEFAULT_BLUETOOTH_VOLUME); + } + + /** + * @return {@code true} if the current preference is related to screensaver brightness. + * {@code false} otherwise. + */ + private boolean isScreensaverBrightnessPreference() { + return getKey().equals(KEY_SCREENSAVER_BRIGHTNESS); + } + + /** + * @return {@code true} if the current preference is related to the screensaver analog clock size. + * {@code false} otherwise. + */ + private boolean isScreensaverAnalogClockSizePreference() { + return getKey().equals(KEY_SCREENSAVER_ANALOG_CLOCK_SIZE); + } + + /** + * @return {@code true} if the current preference is related to the font size of the screensaver + * clock. {@code false} otherwise. + */ + private boolean isScreensaverDigitalClockFontSizePreference() { + return getKey().equals(KEY_SCREENSAVER_DIGITAL_CLOCK_FONT_SIZE); + } + + /** + * @return {@code true} if the current preference is related to blur intensity for screensaver. + * {@code false} otherwise. + */ + private boolean isScreensaverBlurIntensityPreference() { + return getKey().equals(KEY_SCREENSAVER_BLUR_INTENSITY); + } + + /** + * @return {@code true} if the current preference is related to corner radius of + * the digital widget background. {@code false} otherwise. + */ + private boolean isDigitalWidgetBackgroundCornerRadius() { + return getKey().equals(KEY_DIGITAL_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * @return {@code true} if the current preference is related to corner radius of + * the Next alarm widget background. {@code false} otherwise. + */ + private boolean isNextAlarmWidgetBackgroundCornerRadius() { + return getKey().equals(KEY_NEXT_ALARM_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * @return {@code true} if the current preference is related to corner radius of + * the vertical widget background. {@code false} otherwise. + */ + private boolean isVerticalWidgetBackgroundCornerRadius() { + return getKey().equals(KEY_VERTICAL_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * @return {@code true} if the current preference is related to corner radius of + * the Material You digital widget background. {@code false} otherwise. + */ + private boolean isMaterialYouDigitalWidgetBackgroundCornerRadius() { + return getKey().equals(KEY_MATERIAL_YOU_DIGITAL_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * @return {@code true} if the current preference is related to corner radius of + * the Material You Next alarm widget background. {@code false} otherwise. + */ + private boolean isMaterialYouNextAlarmWidgetBackgroundCornerRadius() { + return getKey().equals(KEY_MATERIAL_YOU_NEXT_ALARM_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * @return {@code true} if the current preference is related to corner radius of + * the Material You vertical digital widget background. {@code false} otherwise. + */ + private boolean isMaterialYouVerticalWidgetBackgroundCornerRadius() { + return getKey().equals(KEY_MATERIAL_YOU_VERTICAL_DIGITAL_WIDGET_BACKGROUND_CORNER_RADIUS); + } + + /** + * @return {@code true} if the current preference is related to shake intensity. + * {@code false} otherwise. + */ + private boolean isShakeIntensityPreference() { + return getKey().equals(KEY_SHAKE_INTENSITY); + } + + /** + * @return {@code true} if the current preference is related to timer shake intensity. + * {@code false} otherwise. + */ + private boolean isTimerShakeIntensityPreference() { + return getKey().equals(KEY_TIMER_SHAKE_INTENSITY); + } + + /** + * @return {@code true} if the current preference is related to shadow offset for timers. + * {@code false} otherwise. + */ + private boolean isTimerShadowOffsetPreference() { + return getKey().equals(KEY_TIMER_SHADOW_OFFSET); + } + + /** + * @return {@code true} if the current preference is related to blur intensity for timers. + * {@code false} otherwise. + */ + private boolean isTimerBlurIntensityPreference() { + return getKey().equals(KEY_TIMER_BLUR_INTENSITY); + } + + /** + * @return {@code true} if the current preference is related to the analog clock size. + * {@code false} otherwise. + */ + private boolean isAnalogClockSizePreference() { + return getKey().equals(KEY_ANALOG_CLOCK_SIZE); + } + + /** + * @return {@code true} if the current preference is related to the font size of the clock. + * {@code false} otherwise. + */ + private boolean isDigitalClockFontSizePreference() { + return getKey().equals(KEY_DIGITAL_CLOCK_FONT_SIZE); + } + + /** + * @return {@code true} if the current preference is related to the alarm analog clock size. + * {@code false} otherwise. + */ + private boolean isAlarmAnalogClockSizePreference() { + return getKey().equals(KEY_ALARM_ANALOG_CLOCK_SIZE); + } + + /** + * @return {@code true} if the current preference is related to the font size of the alarm clock. + * {@code false} otherwise. + */ + private boolean isAlarmDigitalClockFontSizePreference() { + return getKey().equals(KEY_ALARM_DIGITAL_CLOCK_FONT_SIZE); + } + + /** + * @return {@code true} if the current preference is related to the alarm title font size. + * {@code false} otherwise. + */ + private boolean isAlarmTitleFontSizePreference() { + return getKey().equals(KEY_ALARM_TITLE_FONT_SIZE_PREF); + } + + /** + * @return {@code true} if the current preference is related to shadow offset for alarms. + * {@code false} otherwise. + */ + private boolean isAlarmShadowOffsetPreference() { + return getKey().equals(KEY_ALARM_SHADOW_OFFSET); + } + + /** + * @return {@code true} if the current preference is related to blur intensity for alarms. + * {@code false} otherwise. + */ + private boolean isAlarmBlurIntensityPreference() { + return getKey().equals(KEY_ALARM_BLUR_INTENSITY); + } + + /** + * @return {@code true} if the current preference is related to volume when a Bluetooth device + * is connected. {@code false} otherwise. + */ + private boolean isBluetoothVolumePreference() { + return getKey().equals(KEY_BLUETOOTH_VOLUME); + } + + /** + * Plays ringtone preview if preference is Bluetooth volume or if there is a Bluetooth device connected. + */ + private void startRingtonePreviewForBluetoothDevices() { + if (!isBluetoothVolumePreference() || !RingtoneUtils.hasBluetoothDeviceConnected(mContext, mPrefs)) { + return; + } + + if (mRingtoneStopRunnable != null) { + mRingtoneHandler.removeCallbacks(mRingtoneStopRunnable); + } + + // If we are not currently playing, start. + Uri ringtoneUri = DataModel.getDataModel().getAlarmRingtoneUriFromSettings(); + if (RingtoneUtils.isRandomRingtone(ringtoneUri)) { + ringtoneUri = RingtoneUtils.getRandomRingtoneUri(); + } else if (RingtoneUtils.isRandomCustomRingtone(ringtoneUri)) { + ringtoneUri = RingtoneUtils.getRandomCustomRingtoneUri(); + } + + RingtonePreviewKlaxon.start(mContext, mPrefs, ringtoneUri); + mIsPreviewPlaying = true; + + mRingtoneStopRunnable = this::stopRingtonePreviewForBluetoothDevices; + + // Stop the preview after 5 seconds + mRingtoneHandler.postDelayed(mRingtoneStopRunnable, ALARM_PREVIEW_DURATION_MS); + } + + /** + * Stops playing the ringtone preview if it is currently playing. + */ + public void stopRingtonePreviewForBluetoothDevices() { + if (!mIsPreviewPlaying) { + return; + } + + if (mRingtoneStopRunnable != null) { + mRingtoneHandler.removeCallbacks(mRingtoneStopRunnable); + } + + RingtonePreviewKlaxon.stop(mContext, mPrefs); + RingtonePreviewKlaxon.stopListeningToPreferences(); + + mIsPreviewPlaying = false; + } + +} diff --git a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSwitchPreference.java b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSwitchPreference.java index e69de29bb..acc71e62d 100644 --- a/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSwitchPreference.java +++ b/app/src/main/java/com/best/deskclock/settings/custompreference/CustomSwitchPreference.java @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only + +package com.best.deskclock.settings.custompreference; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceViewHolder; +import androidx.preference.SwitchPreferenceCompat; + +import com.best.deskclock.R; + +/** + * A {@link SwitchPreferenceCompat} with a custom Material-style layout. + * + *

This class applies a custom card-based appearance to the preference row + * and updates its typography and visual styling based on the user's settings + * (font, card background, card border, etc.).

+ * + *

The preference behavior remains identical to a standard {@link SwitchPreferenceCompat}; + * only its visual presentation is customized.

+ */ +public class CustomSwitchPreference extends SwitchPreferenceCompat { + + public CustomSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.settings_preference_layout); + } + + @Override + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { + PreferenceStyler.apply(holder); + super.onBindViewHolder(holder); + + if (holder.itemView.isInEditMode()) { + ViewGroup widgetFrame = (ViewGroup) holder.findViewById(android.R.id.widget_frame); + widgetFrame.removeAllViews(); + + LayoutInflater inflater = LayoutInflater.from(getContext()); + View switchView = inflater.inflate(R.layout.settings_material_switch, widgetFrame, false); + + widgetFrame.addView(switchView); + } + } + +} diff --git a/app/src/main/java/com/best/deskclock/settings/custompreference/PreferenceStyler.java b/app/src/main/java/com/best/deskclock/settings/custompreference/PreferenceStyler.java index e69de29bb..e5b4c8998 100644 --- a/app/src/main/java/com/best/deskclock/settings/custompreference/PreferenceStyler.java +++ b/app/src/main/java/com/best/deskclock/settings/custompreference/PreferenceStyler.java @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-only + +package com.best.deskclock.settings.custompreference; + +import static androidx.core.util.TypedValueCompat.dpToPx; +import static com.best.deskclock.DeskClockApplication.getDefaultSharedPreferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.graphics.Typeface; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceViewHolder; + +import com.best.deskclock.R; +import com.best.deskclock.data.SettingsDAO; +import com.best.deskclock.utils.ThemeUtils; +import com.google.android.material.card.MaterialCardView; +import com.google.android.material.color.MaterialColors; + +/** + * Utility class that applies custom styling to a preference row. + * + *

This includes:

+ * + * + *

The method is intended to be called from a {@link PreferenceViewHolder} + * inside a custom Preference implementation.

+ */ +public class PreferenceStyler { + + /** + * Applies custom visual styling to the given {@link PreferenceViewHolder}. + * + *

This method:

+ * + * + * @param holder the view holder representing the preference row + */ + public static void apply(@NonNull PreferenceViewHolder holder) { + if (holder.itemView.isInEditMode()) { + return; + } + + Context context = holder.itemView.getContext(); + SharedPreferences prefs = getDefaultSharedPreferences(context); + + MaterialCardView prefCardView = (MaterialCardView) holder.findViewById(R.id.pref_card_view); + boolean isCardBackgroundDisplayed = SettingsDAO.isCardBackgroundDisplayed(prefs); + boolean isCardBorderDisplayed = SettingsDAO.isCardBorderDisplayed(prefs); + + float strokeWidth = dpToPx(2, context.getResources().getDisplayMetrics()); + + if (isCardBackgroundDisplayed) { + prefCardView.setCardBackgroundColor( + MaterialColors.getColor(prefCardView, + com.google.android.material.R.attr.colorSurface) + ); + } else { + prefCardView.setCardBackgroundColor(Color.TRANSPARENT); + } + + prefCardView.setStrokeWidth(isCardBorderDisplayed ? (int) strokeWidth : 0); + + Typeface typeface = ThemeUtils.loadFont(SettingsDAO.getGeneralFont(prefs)); + + TextView title = (TextView) holder.findViewById(android.R.id.title); + if (title != null) { + title.setTypeface(typeface); + } + + TextView summary = (TextView) holder.findViewById(android.R.id.summary); + if (summary != null) { + summary.setTypeface(typeface); + } + } + +} diff --git a/app/src/main/res/drawable/bg_checker.xml b/app/src/main/res/drawable/bg_checker.xml new file mode 100644 index 000000000..ab101a9f7 --- /dev/null +++ b/app/src/main/res/drawable/bg_checker.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/alarm_time_expanded.xml b/app/src/main/res/layout/alarm_time_expanded.xml index 3cc77931f..f50fd31ff 100644 --- a/app/src/main/res/layout/alarm_time_expanded.xml +++ b/app/src/main/res/layout/alarm_time_expanded.xml @@ -75,7 +75,7 @@ app:layout_constraintTop_toBottomOf="@id/edit_label" tools:text="08:30" /> - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_preference_category_layout.xml b/app/src/main/res/layout/settings_preference_category_layout.xml index 559bc0200..66a703f35 100644 --- a/app/src/main/res/layout/settings_preference_category_layout.xml +++ b/app/src/main/res/layout/settings_preference_category_layout.xml @@ -9,8 +9,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingHorizontal="?android:attr/listPreferredItemPaddingStart" android:baselineAligned="false" android:layout_marginHorizontal="10dp" android:layout_marginTop="20dp" @@ -32,9 +31,9 @@ android:paddingVertical="4dp" android:textColor="?android:attr/colorAccent" android:textSize="18sp" - android:textStyle="bold" android:textAlignment="viewStart" - tools:text="Preference category title" /> + tools:text="Preference category title" + tools:textStyle="bold" /> diff --git a/app/src/main/res/layout/settings_preference_color_thumbnail.xml b/app/src/main/res/layout/settings_preference_color_thumbnail.xml index 8e4195644..ff00aff15 100644 --- a/app/src/main/res/layout/settings_preference_color_thumbnail.xml +++ b/app/src/main/res/layout/settings_preference_color_thumbnail.xml @@ -1,25 +1,34 @@ - + android:layout_width="@dimen/preference_thumbnail_size" + android:layout_height="@dimen/preference_thumbnail_size" + tools:background="@drawable/bg_circle_for_preview"> - + + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="1dp" /> + + diff --git a/app/src/main/res/layout/settings_preference_layout.xml b/app/src/main/res/layout/settings_preference_layout.xml index 97ecae45f..60724415e 100644 --- a/app/src/main/res/layout/settings_preference_layout.xml +++ b/app/src/main/res/layout/settings_preference_layout.xml @@ -9,13 +9,15 @@ 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:id="@+id/pref_card_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="80dp" android:layout_marginHorizontal="10dp" android:layout_marginVertical="4dp" app:cardCornerRadius="18dp" - app:strokeWidth="0dp"> + app:strokeWidth="0dp" + app:strokeColor="?attr/colorPrimary"> + app:strokeWidth="0dp" + app:strokeColor="?attr/colorPrimary"> + app:drawableTint="?attr/colorAccent" + tools:textStyle="bold" /> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index dccf29616..87965bdea 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -251,8 +251,6 @@ "斐济" "汤加" "雅加达" - "利雅得" - "雷克雅未克" "新闹钟" 创建新闹钟 diff --git a/app/src/main/res/xml/settings_timer.xml b/app/src/main/res/xml/settings_timer.xml index c0d10e154..02aeb5b81 100644 --- a/app/src/main/res/xml/settings_timer.xml +++ b/app/src/main/res/xml/settings_timer.xml @@ -48,7 +48,7 @@ tools:layout="@layout/settings_preference_layout" />