diff --git a/app/build.gradle b/app/build.gradle
index 743c59a7..2184ab6d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -189,4 +189,5 @@ dependencies {
// android tv
implementation 'androidx.leanback:leanback:1.1.0-rc02'
implementation "androidx.leanback:leanback-tab:1.1.0-beta01"
+ implementation "androidx.leanback:leanback-preference:1.2.0-alpha02"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 21281e75..7a85ed8a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -64,6 +64,11 @@
android:screenOrientation="landscape"
android:theme="@style/LeanbackAppTheme" />
+
+
- val uiMode = settingsRepository.prefValueToUiMode(newValue as String?)
- AppCompatDelegate.setDefaultNightMode(uiMode)
+ private val channelSelectionClickListener = Preference.OnPreferenceClickListener {
+ val direction =
+ SettingsFragmentDirections.toChannelSelectionFragment()
+ findNavController().navigate(direction)
true
}
- private val languageChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
- val languageTag = newValue as String
- val appLocale = LocaleListCompat.forLanguageTags(languageTag)
- AppCompatDelegate.setApplicationLocales(appLocale)
-
- true
- }
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
-
- settingsRepository = SettingsRepository(context)
- }
-
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences)
- shortcutPreference = preferenceScreen.findPreference(PREF_SHORTCUTS)!!
- dynamicColorsPreference = preferenceScreen.findPreference(PREF_DYNAMIC_COLORS)!!
- uiModePreference = preferenceScreen.findPreference(PREF_UI_MODE)!!
- languagePreference = preferenceScreen.findPreference(PREF_LANGUAGE)!!
- channelSelectionPreference = preferenceScreen.findPreference(PREF_CHANNEL_SELECTION)!!
-
- val languages = LanguageHelper.getAvailableLanguages(requireContext())
- languagePreference.value = LanguageHelper.getCurrentLanguageTag()
- languagePreference.entries = languages.values.toTypedArray()
- languagePreference.entryValues = languages.keys.toTypedArray()
-
- // only show the preference for dynamic colors when available (Android 12 and up)
- dynamicColorsPreference.isVisible = DynamicColors.isDynamicColorAvailable()
- dynamicColorsPreference.setOnPreferenceChangeListener { _, useDynamicColors ->
- // save explicitly to persist before app restart
- settingsRepository.dynamicColors = useDynamicColors as Boolean
- // app restart
- ProcessPhoenix.triggerRebirth(context)
- true
- }
-
- channelSelectionPreference.setOnPreferenceClickListener {
- val direction =
- SettingsFragmentDirections.toChannelSelectionFragment()
- findNavController().navigate(direction)
- true
- }
+ preferenceFragmentHelper.initPreferences(channelSelectionClickListener)
}
- override fun onResume() {
- super.onResume()
-
- shortcutPreference.onPreferenceChangeListener = shortcutPreference
- uiModePreference.onPreferenceChangeListener = uiModeChangeListener
- languagePreference.onPreferenceChangeListener = languageChangeListener
- }
-
- override fun onPause() {
- super.onPause()
-
- shortcutPreference.onPreferenceChangeListener = null
- uiModePreference.onPreferenceChangeListener = null
- languagePreference.onPreferenceChangeListener = null
+ override fun onDestroy() {
+ super.onDestroy()
+ preferenceFragmentHelper.destroy()
}
}
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutFragment.kt b/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutFragment.kt
index 8d3dc6f9..31a6585e 100644
--- a/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutFragment.kt
+++ b/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutFragment.kt
@@ -19,7 +19,7 @@ class AboutFragment : Fragment(), AboutItemListener {
val binding = TvFragmentAboutBinding.inflate(inflater, container, false)
binding.grid.adapter = AboutListAdapter(this)
- binding.grid.layoutManager = GridLayoutManager(requireContext(), 2)
+ binding.grid.layoutManager = GridLayoutManager(requireContext(), 3)
return binding.root
}
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutListAdapter.kt b/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutListAdapter.kt
index 4846aa33..2f3a8763 100644
--- a/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutListAdapter.kt
+++ b/app/src/main/java/de/christinecoenen/code/zapp/tv/about/AboutListAdapter.kt
@@ -7,12 +7,18 @@ import de.christinecoenen.code.zapp.R
import de.christinecoenen.code.zapp.databinding.TvAboutItemBinding
import de.christinecoenen.code.zapp.tv.changelog.ChangelogActivity
import de.christinecoenen.code.zapp.tv.faq.FaqActivity
+import de.christinecoenen.code.zapp.tv.settings.SettingsActivity
class AboutListAdapter(
private val listener: AboutItemListener
) : RecyclerView.Adapter() {
private val aboutItems = listOf(
+ AboutItem(
+ R.string.activity_settings_title,
+ R.drawable.ic_outline_settings_24,
+ SettingsActivity
+ ),
AboutItem(
R.string.changelog_title,
R.drawable.ic_sharp_format_list_bulleted_24,
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/tv/main/MainFragment.kt b/app/src/main/java/de/christinecoenen/code/zapp/tv/main/MainFragment.kt
index 0c17e205..c39f168c 100644
--- a/app/src/main/java/de/christinecoenen/code/zapp/tv/main/MainFragment.kt
+++ b/app/src/main/java/de/christinecoenen/code/zapp/tv/main/MainFragment.kt
@@ -5,7 +5,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import de.christinecoenen.code.zapp.R
+import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
import de.christinecoenen.code.zapp.databinding.TvFragmentMainBinding
+import org.koin.android.ext.android.inject
class MainFragment : Fragment() {
@@ -13,6 +16,8 @@ class MainFragment : Fragment() {
private var _binding: TvFragmentMainBinding? = null
private val binding: TvFragmentMainBinding get() = _binding!!
+ private val settingsRepository: SettingsRepository by inject()
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -23,7 +28,13 @@ class MainFragment : Fragment() {
binding.viewpager.adapter = MainNavPagerAdapter(requireContext(), parentFragmentManager)
binding.tabs.setupWithViewPager(binding.viewpager)
- binding.tabs.getTabAt(0)?.view?.requestFocus()
+ val selectedTabIndex = when (settingsRepository.startFragment) {
+ R.id.mediathekListFragment -> 1
+ else -> 0
+ }
+ val selectedTab = binding.tabs.getTabAt(selectedTabIndex)
+ binding.tabs.selectTab(selectedTab)
+ selectedTab?.view?.requestFocus()
return binding.root
}
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/PreferenceFragment.kt b/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/PreferenceFragment.kt
new file mode 100644
index 00000000..50671b96
--- /dev/null
+++ b/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/PreferenceFragment.kt
@@ -0,0 +1,25 @@
+package de.christinecoenen.code.zapp.tv.settings
+
+import android.os.Bundle
+import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
+import de.christinecoenen.code.zapp.R
+import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
+import de.christinecoenen.code.zapp.utils.system.PreferenceFragmentHelper
+import org.koin.android.ext.android.inject
+
+class PreferenceFragment : LeanbackPreferenceFragmentCompat() {
+
+ private val settingsRepository: SettingsRepository by inject()
+ private val preferenceFragmentHelper = PreferenceFragmentHelper(this, settingsRepository)
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.tv_preferences, rootKey)
+
+ preferenceFragmentHelper.initPreferences()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ preferenceFragmentHelper.destroy()
+ }
+}
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/SettingsActivity.kt b/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/SettingsActivity.kt
new file mode 100644
index 00000000..988ad947
--- /dev/null
+++ b/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/SettingsActivity.kt
@@ -0,0 +1,24 @@
+package de.christinecoenen.code.zapp.tv.settings
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.fragment.app.FragmentActivity
+import de.christinecoenen.code.zapp.databinding.TvActivitySettingsBinding
+import de.christinecoenen.code.zapp.utils.system.IStartableActivity
+
+class SettingsActivity : FragmentActivity() {
+
+ companion object : IStartableActivity {
+ override fun getStartIntent(context: Context?): Intent =
+ Intent(context, SettingsActivity::class.java)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val binding = TvActivitySettingsBinding.inflate(layoutInflater)
+
+ setContentView(binding.root)
+ }
+}
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/SettingsFragment.kt b/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/SettingsFragment.kt
new file mode 100644
index 00000000..0cd9420d
--- /dev/null
+++ b/app/src/main/java/de/christinecoenen/code/zapp/tv/settings/SettingsFragment.kt
@@ -0,0 +1,29 @@
+package de.christinecoenen.code.zapp.tv.settings
+
+import androidx.leanback.preference.LeanbackSettingsFragmentCompat
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceScreen
+
+
+class SettingsFragment : LeanbackSettingsFragmentCompat() {
+
+ override fun onPreferenceStartFragment(
+ caller: PreferenceFragmentCompat,
+ pref: Preference
+ ): Boolean {
+ return false
+ }
+
+ override fun onPreferenceStartScreen(
+ caller: PreferenceFragmentCompat,
+ pref: PreferenceScreen
+ ): Boolean {
+ return false
+ }
+
+ override fun onPreferenceStartInitialScreen() {
+ startPreferenceFragment(PreferenceFragment())
+ }
+
+}
diff --git a/app/src/main/java/de/christinecoenen/code/zapp/utils/system/PreferenceFragmentHelper.kt b/app/src/main/java/de/christinecoenen/code/zapp/utils/system/PreferenceFragmentHelper.kt
new file mode 100644
index 00000000..70b630e9
--- /dev/null
+++ b/app/src/main/java/de/christinecoenen/code/zapp/utils/system/PreferenceFragmentHelper.kt
@@ -0,0 +1,110 @@
+package de.christinecoenen.code.zapp.utils.system
+
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.os.LocaleListCompat
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.ListPreference
+import androidx.preference.Preference
+import androidx.preference.Preference.OnPreferenceClickListener
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreferenceCompat
+import com.google.android.material.color.DynamicColors
+import com.jakewharton.processphoenix.ProcessPhoenix
+import de.christinecoenen.code.zapp.app.settings.helper.ShortcutPreference
+import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
+
+class PreferenceFragmentHelper(
+ private val preferenceFragment: PreferenceFragmentCompat,
+ private val settingsRepository: SettingsRepository,
+) : DefaultLifecycleObserver {
+
+ companion object {
+
+ private const val PREF_SHORTCUTS = "pref_shortcuts"
+ private const val PREF_DYNAMIC_COLORS = "dynamic_colors"
+ private const val PREF_UI_MODE = "pref_ui_mode"
+ private const val PREF_LANGUAGE = "pref_key_language"
+ private const val PREF_CHANNEL_SELECTION = "pref_key_channel_selection"
+
+ }
+
+ private var shortcutPreference: ShortcutPreference? = null
+ private var dynamicColorsPreference: SwitchPreferenceCompat? = null
+ private var uiModePreference: ListPreference? = null
+ private var languagePreference: ListPreference? = null
+ private var channelSelectionPreference: Preference? = null
+
+ private var channelSelectionClickListener: OnPreferenceClickListener? = null
+
+ private val dynamicColorChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
+ ProcessPhoenix.triggerRebirth(preferenceFragment.requireContext())
+ true
+ }
+
+ private val uiModeChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
+ val uiMode = settingsRepository.prefValueToUiMode(newValue as String?)
+ AppCompatDelegate.setDefaultNightMode(uiMode)
+ true
+ }
+
+ private val languageChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
+ val languageTag = newValue as String
+ val appLocale = LocaleListCompat.forLanguageTags(languageTag)
+ AppCompatDelegate.setApplicationLocales(appLocale)
+ true
+ }
+
+ init {
+ preferenceFragment.lifecycle.addObserver(this)
+ }
+
+ fun initPreferences(channelSelectionClickListener: OnPreferenceClickListener? = null) {
+ val preferenceScreen = preferenceFragment.preferenceScreen
+
+ shortcutPreference = preferenceScreen.findPreference(PREF_SHORTCUTS)
+ dynamicColorsPreference = preferenceScreen.findPreference(PREF_DYNAMIC_COLORS)
+ uiModePreference = preferenceScreen.findPreference(PREF_UI_MODE)
+ languagePreference = preferenceScreen.findPreference(PREF_LANGUAGE)
+ channelSelectionPreference = preferenceScreen.findPreference(PREF_CHANNEL_SELECTION)
+
+ languagePreference?.let {
+ val languages =
+ LanguageHelper.getAvailableLanguages(preferenceFragment.requireContext())
+ it.value = LanguageHelper.getCurrentLanguageTag()
+ it.entries = languages.values.toTypedArray()
+ it.entryValues = languages.keys.toTypedArray()
+ }
+
+ // only show the preference for dynamic colors when available (Android 12 and up)
+ dynamicColorsPreference?.let {
+ it.isVisible = DynamicColors.isDynamicColorAvailable()
+ }
+
+ this.channelSelectionClickListener = channelSelectionClickListener
+ }
+
+ fun destroy() {
+ preferenceFragment.lifecycle.removeObserver(this)
+ }
+
+ override fun onStart(owner: LifecycleOwner) {
+ super.onStart(owner)
+
+ shortcutPreference?.onPreferenceChangeListener = shortcutPreference
+ dynamicColorsPreference?.onPreferenceChangeListener = dynamicColorChangeListener
+ uiModePreference?.onPreferenceChangeListener = uiModeChangeListener
+ languagePreference?.onPreferenceChangeListener = languageChangeListener
+ channelSelectionPreference?.onPreferenceClickListener = channelSelectionClickListener
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ super.onDestroy(owner)
+
+ shortcutPreference?.onPreferenceChangeListener = null
+ dynamicColorsPreference?.onPreferenceChangeListener = null
+ uiModePreference?.onPreferenceChangeListener = null
+ languagePreference?.onPreferenceChangeListener = null
+ channelSelectionPreference?.onPreferenceClickListener = null
+ }
+}
diff --git a/app/src/main/res/layout/tv_activity_settings.xml b/app/src/main/res/layout/tv_activity_settings.xml
new file mode 100644
index 00000000..9031e63e
--- /dev/null
+++ b/app/src/main/res/layout/tv_activity_settings.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/tv_fragment_about.xml b/app/src/main/res/layout/tv_fragment_about.xml
index bf7015df..55d149ae 100644
--- a/app/src/main/res/layout/tv_fragment_about.xml
+++ b/app/src/main/res/layout/tv_fragment_about.xml
@@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:padding="64dp">
+ app:layout_constraintTop_toTopOf="parent" />
personal
+
+ - @string/activity_main_tab_live
+ - @string/activity_main_tab_mediathek
+
+
+
+ - live
+ - mediathek
+
+
- @fraction/mediathek_filter_min_duration
- @fraction/mediathek_filter_max_duration
diff --git a/app/src/main/res/values/styles_tv.xml b/app/src/main/res/values/styles_tv.xml
index e3e4eec3..abc0d0ea 100644
--- a/app/src/main/res/values/styles_tv.xml
+++ b/app/src/main/res/values/styles_tv.xml
@@ -22,6 +22,10 @@
- @color/colorPrimary
+
+