0
0
mirror of https://github.com/mediathekview/zapp.git synced 2024-09-19 20:02:17 +02:00

Merge branch 'feature/android-tv-settings' into develop

# Conflicts:
#	app/src/main/res/raw/changelog.md
This commit is contained in:
Christine Coenen 2024-02-04 14:43:11 +01:00
commit e2bf22186f
16 changed files with 274 additions and 93 deletions

View File

@ -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"
}

View File

@ -64,6 +64,11 @@
android:screenOrientation="landscape"
android:theme="@style/LeanbackAppTheme" />
<activity
android:name=".tv.settings.SettingsActivity"
android:screenOrientation="landscape"
android:theme="@style/LeanbackAppTheme.Transparent" />
<activity
android:name=".tv.faq.FaqActivity"
android:screenOrientation="landscape"

View File

@ -1,105 +1,34 @@
package de.christinecoenen.code.zapp.app.settings.ui
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.navigation.fragment.findNavController
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.google.android.material.color.DynamicColors
import com.jakewharton.processphoenix.ProcessPhoenix
import androidx.preference.PreferenceFragmentCompat
import de.christinecoenen.code.zapp.R
import de.christinecoenen.code.zapp.app.settings.helper.ShortcutPreference
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
import de.christinecoenen.code.zapp.utils.system.LanguageHelper
import de.christinecoenen.code.zapp.utils.system.PreferenceFragmentHelper
import org.koin.android.ext.android.inject
class SettingsFragment : BaseSettingsFragment() {
companion object {
private val settingsRepository: SettingsRepository by inject()
private val preferenceFragmentHelper = PreferenceFragmentHelper(this, settingsRepository)
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 lateinit var settingsRepository: SettingsRepository
private lateinit var shortcutPreference: ShortcutPreference
private lateinit var dynamicColorsPreference: SwitchPreferenceCompat
private lateinit var uiModePreference: ListPreference
private lateinit var languagePreference: ListPreference
private lateinit var channelSelectionPreference: Preference
private val uiModeChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
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()
}
}

View File

@ -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
}

View File

@ -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<AboutViewViewHolder>() {
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,

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="de.christinecoenen.code.zapp.tv.settings.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid"
@ -12,7 +13,7 @@
app:focusOutFront="true"
app:layout_constraintBottom_toTopOf="@id/app_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toEndOf="@id/app_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
@ -22,17 +23,15 @@
android:id="@+id/app_icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="64dp"
android:alpha="0.05"
android:contentDescription="@null"
android:padding="64dp"
android:src="@drawable/ic_zapp_tv"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintEnd_toStartOf="@id/grid"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.5" />
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/app_description"

View File

@ -1,4 +1,5 @@
# v-next
* Einstellungen zu Android-TV-version hinzugefügt
* Angeschnittener Text im Vorspulen-Button gefixt
* Sender "Deutsche Welle" und "Deutsche Welle +" entfernt, weil sie ihren deutschen Sendebetrieb eingestellt haben

View File

@ -41,6 +41,16 @@
<item>personal</item>
</string-array>
<string-array name="available_tabs_names_tv">
<item>@string/activity_main_tab_live</item>
<item>@string/activity_main_tab_mediathek</item>
</string-array>
<string-array name="available_tabs_values_tv">
<item>live</item>
<item>mediathek</item>
</string-array>
<array name="mediathek_filter_show_length_slider_initial_values">
<item>@fraction/mediathek_filter_min_duration</item>
<item>@fraction/mediathek_filter_max_duration</item>

View File

@ -22,6 +22,10 @@
<item name="android:colorControlActivated">@color/colorPrimary</item>
</style>
<style name="LeanbackAppTheme.Transparent" parent="LeanbackAppTheme">
<item name="android:windowBackground">@color/black_overlay</item>
</style>
<style name="TvCardStyle">
<item name="android:background">?colorSurface</item>
<item name="android:foreground">?android:attr/selectableItemBackground</item>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/activity_settings_title">
<ListPreference
android:defaultValue="live"
android:entries="@array/available_tabs_names_tv"
android:key="@string/pref_key_start_tab"
android:title="@string/pref_start_tab_title"
app:entryValues="@array/available_tabs_values_tv"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceScreen>