diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fa695dda45..768c7730e4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -88,6 +88,7 @@ val versionTxt by tasks.registering { dependencies { // Jellyfin implementation(projects.playback) + implementation(projects.preference) implementation(libs.jellyfin.apiclient) implementation(libs.jellyfin.sdk) { // Change version if desired diff --git a/app/src/main/java/org/jellyfin/androidtv/constant/HomeSectionType.kt b/app/src/main/java/org/jellyfin/androidtv/constant/HomeSectionType.kt index 5fafc4e9fc..6436ebaec6 100644 --- a/app/src/main/java/org/jellyfin/androidtv/constant/HomeSectionType.kt +++ b/app/src/main/java/org/jellyfin/androidtv/constant/HomeSectionType.kt @@ -1,8 +1,8 @@ package org.jellyfin.androidtv.constant import org.jellyfin.androidtv.R -import org.jellyfin.androidtv.preference.PreferenceEnum import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions +import org.jellyfin.preference.PreferenceEnum /** * All possible homesections, "synced" with jellyfin-web. diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/AuthenticationPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/AuthenticationPreferences.kt index 68c12c9256..6a099d1d69 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/AuthenticationPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/AuthenticationPreferences.kt @@ -3,6 +3,8 @@ package org.jellyfin.androidtv.preference import android.content.Context import org.jellyfin.androidtv.auth.model.AuthenticationSortBy import org.jellyfin.androidtv.preference.constant.UserSelectBehavior +import org.jellyfin.preference.Preference +import org.jellyfin.preference.store.SharedPreferenceStore class AuthenticationPreferences(context: Context) : SharedPreferenceStore( sharedPreferences = context.getSharedPreferences("authentication", Context.MODE_PRIVATE) diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/LibraryPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/LibraryPreferences.kt index a6f486e7f9..20050bf5c8 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/LibraryPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/LibraryPreferences.kt @@ -3,8 +3,10 @@ package org.jellyfin.androidtv.preference import org.jellyfin.androidtv.constant.GridDirection import org.jellyfin.androidtv.constant.ImageType import org.jellyfin.androidtv.constant.PosterSize +import org.jellyfin.androidtv.preference.store.DisplayPreferencesStore import org.jellyfin.apiclient.model.entities.SortOrder import org.jellyfin.apiclient.model.querying.ItemSortBy +import org.jellyfin.preference.Preference import org.jellyfin.sdk.api.client.ApiClient class LibraryPreferences( diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/LiveTvPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/LiveTvPreferences.kt index 36b59090a7..51430bbbda 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/LiveTvPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/LiveTvPreferences.kt @@ -1,6 +1,8 @@ package org.jellyfin.androidtv.preference +import org.jellyfin.androidtv.preference.store.DisplayPreferencesStore import org.jellyfin.apiclient.model.querying.ItemSortBy +import org.jellyfin.preference.Preference import org.jellyfin.sdk.api.client.ApiClient class LiveTvPreferences( diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/SystemPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/SystemPreferences.kt index 1fd7b25fb6..cac858b0ca 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/SystemPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/SystemPreferences.kt @@ -2,6 +2,8 @@ package org.jellyfin.androidtv.preference import android.content.Context import org.jellyfin.androidtv.preference.constant.PreferredVideoPlayer +import org.jellyfin.preference.Preference +import org.jellyfin.preference.store.SharedPreferenceStore /** * System preferences are not possible to modify by the user. diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt index 02e7fea0d4..e2d668c488 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt @@ -13,6 +13,9 @@ import org.jellyfin.androidtv.preference.constant.RatingType import org.jellyfin.androidtv.preference.constant.WatchedIndicatorBehavior import org.jellyfin.androidtv.preference.constant.defaultAudioBehavior import org.jellyfin.androidtv.util.DeviceUtils +import org.jellyfin.preference.Preference +import org.jellyfin.preference.migration.putEnum +import org.jellyfin.preference.store.SharedPreferenceStore /** * User preferences are configurable by the user and change behavior of the application. diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/UserSettingPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/UserSettingPreferences.kt index 8f5a7090e5..9b9a56c88b 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/UserSettingPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/UserSettingPreferences.kt @@ -1,6 +1,8 @@ package org.jellyfin.androidtv.preference import org.jellyfin.androidtv.constant.HomeSectionType +import org.jellyfin.androidtv.preference.store.DisplayPreferencesStore +import org.jellyfin.preference.Preference import org.jellyfin.sdk.api.client.ApiClient class UserSettingPreferences( diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/DisplayPreferencesStore.kt b/app/src/main/java/org/jellyfin/androidtv/preference/store/DisplayPreferencesStore.kt similarity index 95% rename from app/src/main/java/org/jellyfin/androidtv/preference/DisplayPreferencesStore.kt rename to app/src/main/java/org/jellyfin/androidtv/preference/store/DisplayPreferencesStore.kt index 8979d301ec..dcf85b4a60 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/DisplayPreferencesStore.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/store/DisplayPreferencesStore.kt @@ -1,5 +1,8 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.androidtv.preference.store +import org.jellyfin.preference.Preference +import org.jellyfin.preference.PreferenceEnum +import org.jellyfin.preference.store.AsyncPreferenceStore import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.exception.ApiClientException import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DisplayPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DisplayPreferencesScreen.kt index 22f5ba8aee..41411a4898 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DisplayPreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DisplayPreferencesScreen.kt @@ -5,12 +5,12 @@ import org.jellyfin.androidtv.constant.GridDirection import org.jellyfin.androidtv.constant.ImageType import org.jellyfin.androidtv.constant.PosterSize import org.jellyfin.androidtv.preference.LibraryPreferences -import org.jellyfin.androidtv.preference.PreferenceStore import org.jellyfin.androidtv.preference.PreferencesRepository import org.jellyfin.androidtv.ui.preference.dsl.OptionsFragment import org.jellyfin.androidtv.ui.preference.dsl.checkbox import org.jellyfin.androidtv.ui.preference.dsl.enum import org.jellyfin.androidtv.ui.preference.dsl.optionsScreen +import org.jellyfin.preference.store.PreferenceStore import org.koin.android.ext.android.inject class DisplayPreferencesScreen : OptionsFragment() { diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsFragment.kt index 2d844c1bc8..08462cda4d 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsFragment.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsFragment.kt @@ -5,8 +5,8 @@ import androidx.leanback.preference.LeanbackPreferenceFragmentCompat import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking -import org.jellyfin.androidtv.preference.AsyncPreferenceStore -import org.jellyfin.androidtv.preference.PreferenceStore +import org.jellyfin.preference.store.AsyncPreferenceStore +import org.jellyfin.preference.store.PreferenceStore abstract class OptionsFragment : LeanbackPreferenceFragmentCompat() { abstract val screen: OptionsScreen diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemEnum.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemEnum.kt index f07491ec0f..195831ce4a 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemEnum.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemEnum.kt @@ -3,9 +3,9 @@ package org.jellyfin.androidtv.ui.preference.dsl import android.content.Context import androidx.annotation.StringRes import androidx.preference.PreferenceCategory -import org.jellyfin.androidtv.preference.Preference -import org.jellyfin.androidtv.preference.PreferenceStore import org.jellyfin.androidtv.ui.preference.custom.RichListPreference +import org.jellyfin.preference.Preference +import org.jellyfin.preference.store.PreferenceStore import java.util.UUID class OptionsItemEnum>( diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemMutable.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemMutable.kt index f87cd9d96b..bdf1065ea5 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemMutable.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemMutable.kt @@ -1,7 +1,7 @@ package org.jellyfin.androidtv.ui.preference.dsl -import org.jellyfin.androidtv.preference.Preference -import org.jellyfin.androidtv.preference.PreferenceStore +import org.jellyfin.preference.Preference +import org.jellyfin.preference.store.PreferenceStore abstract class OptionsItemMutable : OptionsItem { var title: String? = null diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt index 3098b12bdc..7f771135b2 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt @@ -3,10 +3,9 @@ package org.jellyfin.androidtv.ui.preference.screen import androidx.core.os.bundleOf import org.jellyfin.androidtv.R import org.jellyfin.androidtv.auth.AuthenticationRepository -import org.jellyfin.androidtv.auth.model.AuthenticationSortBy import org.jellyfin.androidtv.auth.SessionRepository +import org.jellyfin.androidtv.auth.model.AuthenticationSortBy import org.jellyfin.androidtv.preference.AuthenticationPreferences -import org.jellyfin.androidtv.preference.Preference import org.jellyfin.androidtv.preference.constant.UserSelectBehavior import org.jellyfin.androidtv.ui.preference.category.aboutCategory import org.jellyfin.androidtv.ui.preference.dsl.OptionsBinder @@ -18,6 +17,7 @@ import org.jellyfin.androidtv.ui.preference.dsl.link import org.jellyfin.androidtv.ui.preference.dsl.optionsScreen import org.jellyfin.androidtv.ui.preference.dsl.userPicker import org.jellyfin.androidtv.ui.startup.preference.EditServerScreen +import org.jellyfin.preference.Preference import org.jellyfin.sdk.model.serializer.toUUIDOrNull import org.koin.android.ext.android.inject diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/HomePreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/HomePreferencesScreen.kt index 25f09e9253..a693fb5910 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/HomePreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/HomePreferencesScreen.kt @@ -2,11 +2,11 @@ package org.jellyfin.androidtv.ui.preference.screen import org.jellyfin.androidtv.R import org.jellyfin.androidtv.constant.HomeSectionType -import org.jellyfin.androidtv.preference.PreferenceStore import org.jellyfin.androidtv.preference.UserSettingPreferences import org.jellyfin.androidtv.ui.preference.dsl.OptionsFragment import org.jellyfin.androidtv.ui.preference.dsl.enum import org.jellyfin.androidtv.ui.preference.dsl.optionsScreen +import org.jellyfin.preference.store.PreferenceStore import org.koin.android.ext.android.inject class HomePreferencesScreen : OptionsFragment() { diff --git a/playback/build.gradle.kts b/playback/build.gradle.kts index 50c099c46b..3a5a9b5d98 100644 --- a/playback/build.gradle.kts +++ b/playback/build.gradle.kts @@ -25,5 +25,5 @@ android { } dependencies { - + } diff --git a/preference/build.gradle.kts b/preference/build.gradle.kts new file mode 100644 index 0000000000..3822a9a26e --- /dev/null +++ b/preference/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + id("com.android.library") + kotlin("android") +} + +android { + compileSdk = 31 + + defaultConfig { + minSdk = 21 + targetSdk = 31 + } + + buildFeatures { + viewBinding = true + } + + sourceSets["main"].java.srcDirs("src/main/kotlin") + sourceSets["test"].java.srcDirs("src/test/kotlin") + + lint { + lintConfig = file("$rootDir/android-lint.xml") + abortOnError = false + } + + testOptions.unitTests.all { + it.useJUnitPlatform() + } +} + +dependencies { + // Kotlin + implementation(libs.kotlinx.coroutines) + + // Logging + implementation(libs.timber) + + // Testing + testImplementation(libs.kotest.runner.junit5) + testImplementation(libs.kotest.assertions) + testImplementation(libs.mockk) +} diff --git a/preference/src/main/AndroidManifest.xml b/preference/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..bbce2acd88 --- /dev/null +++ b/preference/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/Preference.kt b/preference/src/main/kotlin/Preference.kt similarity index 93% rename from app/src/main/java/org/jellyfin/androidtv/preference/Preference.kt rename to preference/src/main/kotlin/Preference.kt index eb2d1cd034..1bdf369aa5 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/Preference.kt +++ b/preference/src/main/kotlin/Preference.kt @@ -1,4 +1,4 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference import kotlin.reflect.KClass diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/PreferenceEnum.kt b/preference/src/main/kotlin/PreferenceEnum.kt similarity index 58% rename from app/src/main/java/org/jellyfin/androidtv/preference/PreferenceEnum.kt rename to preference/src/main/kotlin/PreferenceEnum.kt index 67e2be5d6a..9a2b4ea222 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/PreferenceEnum.kt +++ b/preference/src/main/kotlin/PreferenceEnum.kt @@ -1,4 +1,4 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference interface PreferenceEnum { val serializedName: String diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/migrations/MigrationContext.kt b/preference/src/main/kotlin/migration/MigrationContext.kt similarity index 95% rename from app/src/main/java/org/jellyfin/androidtv/preference/migrations/MigrationContext.kt rename to preference/src/main/kotlin/migration/MigrationContext.kt index 50d019821f..d451fa9bc5 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/migrations/MigrationContext.kt +++ b/preference/src/main/kotlin/migration/MigrationContext.kt @@ -1,7 +1,7 @@ -package org.jellyfin.androidtv.preference.migrations +package org.jellyfin.preference.migration import timber.log.Timber -import java.lang.Integer.max +import kotlin.math.max class MigrationContext { private val migrations = mutableListOf>() diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/MigrationEditor.kt b/preference/src/main/kotlin/migration/MigrationEditor.kt similarity index 82% rename from app/src/main/java/org/jellyfin/androidtv/preference/MigrationEditor.kt rename to preference/src/main/kotlin/migration/MigrationEditor.kt index fe89df0a45..d0abd25cc7 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/MigrationEditor.kt +++ b/preference/src/main/kotlin/migration/MigrationEditor.kt @@ -1,4 +1,4 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference.migration import android.content.SharedPreferences diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/AsyncPreferenceStore.kt b/preference/src/main/kotlin/store/AsyncPreferenceStore.kt similarity index 96% rename from app/src/main/java/org/jellyfin/androidtv/preference/AsyncPreferenceStore.kt rename to preference/src/main/kotlin/store/AsyncPreferenceStore.kt index 32131cce9a..30b614dbdb 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/AsyncPreferenceStore.kt +++ b/preference/src/main/kotlin/store/AsyncPreferenceStore.kt @@ -1,4 +1,4 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference.store import kotlinx.coroutines.runBlocking diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/PreferenceStore.kt b/preference/src/main/kotlin/store/PreferenceStore.kt similarity index 97% rename from app/src/main/java/org/jellyfin/androidtv/preference/PreferenceStore.kt rename to preference/src/main/kotlin/store/PreferenceStore.kt index 7e0a1e3283..5e43430c4f 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/PreferenceStore.kt +++ b/preference/src/main/kotlin/store/PreferenceStore.kt @@ -1,4 +1,6 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference.store + +import org.jellyfin.preference.Preference /** * Abstract class defining the required functions for a preference store. diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/SharedPreferenceStore.kt b/preference/src/main/kotlin/store/SharedPreferenceStore.kt similarity index 92% rename from app/src/main/java/org/jellyfin/androidtv/preference/SharedPreferenceStore.kt rename to preference/src/main/kotlin/store/SharedPreferenceStore.kt index fe533447ba..dc82fd04f8 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/SharedPreferenceStore.kt +++ b/preference/src/main/kotlin/store/SharedPreferenceStore.kt @@ -1,7 +1,10 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference.store import android.content.SharedPreferences -import org.jellyfin.androidtv.preference.migrations.MigrationContext +import org.jellyfin.preference.Preference +import org.jellyfin.preference.PreferenceEnum +import org.jellyfin.preference.migration.MigrationContext +import org.jellyfin.preference.migration.MigrationEditor import timber.log.Timber /** diff --git a/app/src/test/kotlin/preference/PreferenceStoreTests.kt b/preference/src/test/kotlin/PreferenceStoreTests.kt similarity index 85% rename from app/src/test/kotlin/preference/PreferenceStoreTests.kt rename to preference/src/test/kotlin/PreferenceStoreTests.kt index cc35d7264e..35e97a11ec 100644 --- a/app/src/test/kotlin/preference/PreferenceStoreTests.kt +++ b/preference/src/test/kotlin/PreferenceStoreTests.kt @@ -1,7 +1,37 @@ -package org.jellyfin.androidtv.preference +package org.jellyfin.preference import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe +import org.jellyfin.preference.store.PreferenceStore + +/** + * Tests that the generic set/get methods dispatch to the correct + * internal getT/setT methods + */ +class PreferenceStoreTests : FunSpec({ + fun verifySimpleType(expectedVal: T, preference: Preference) { + val instance = TestStub() + instance[preference] = expectedVal + instance[preference] shouldBe expectedVal + instance.key shouldBe preference.key + } + + test("Reading and writing primitives works correctly") { + verifySimpleType(1, Preference.int("key", 0)) + verifySimpleType(1L, Preference.long("key", 0L)) + verifySimpleType(true, Preference.boolean("key", false)) + verifySimpleType("string", Preference.string("key", "")) + } + + test("Reading and writing enums works correctly") { + val pref = Preference.enum("key", TestEnum.NOT_SET) + val expectedVal = TestEnum.SET + val instance = TestStub() + instance[pref] = expectedVal + instance[pref] shouldBe expectedVal + pref.key shouldBe instance.key + } +}) private class TestStub : PreferenceStore() { var key: String? = null @@ -52,36 +82,3 @@ private class TestStub : PreferenceStore() { } private enum class TestEnum { NOT_SET, SET } - -/** - * Tests that the generic set/get methods dispatch to the correct - * internal getT/setT methods - */ -class PreferenceStoreTests : FunSpec({ - fun verifySimpleType(expectedVal: T, preference: Preference) { - val instance = TestStub() - instance[preference] = expectedVal - instance[preference] shouldBe expectedVal - instance.key shouldBe preference.key - } - - test("get and setting native types") { - // Unfortunately KoTest will try to serialise to a base Serializable using rows - // So manually use generics for the same effect - verifySimpleType(1, Preference.int("key", 0)) - verifySimpleType(1L, Preference.long("key", 0L)) - verifySimpleType(true, Preference.boolean("key", false)) - verifySimpleType("string", Preference.string("key", "")) - } - - test("get and set with enum types") { - // Generics removes too much type info for us to use verifySimpleType - // we could be clever, but I prefer duplication and to KISS - val pref = Preference.enum("key", TestEnum.NOT_SET) - val expectedVal = TestEnum.SET - val instance = TestStub() - instance[pref] = expectedVal - instance[pref] shouldBe expectedVal - pref.key shouldBe instance.key - } -}) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7070c6b818..388038cccc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,7 @@ include(":app") // Modules include(":playback") +include(":preference") pluginManagement { repositories {