Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
if (isNavVisible && isLandscape() && isLayout(TV or EMULATOR)) marginPx else 0
}
}
autoHideRail?.destinationChanged(isNavVisible && isLandscape())

/**
* We need to make sure if we return to a sub-fragment,
Expand Down Expand Up @@ -669,8 +670,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}

private var autoHideRail: com.lagradost.cloudstream3.ui.AutoHideNavigationRail? = null

override fun dispatchKeyEvent(event: KeyEvent): Boolean =
CommonActivity.dispatchKeyEvent(this, event) ?: super.dispatchKeyEvent(event)
if (autoHideRail?.dispatch(event, currentFocus) == true) true
else CommonActivity.dispatchKeyEvent(this, event) ?: super.dispatchKeyEvent(event)

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean =
CommonActivity.onKeyDown(this, keyCode, event) ?: super.onKeyDown(keyCode, event)
Expand Down Expand Up @@ -732,10 +736,32 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}

override fun onNewIntent(intent: Intent) {
resetHomeOnResume = shouldResetLauncherEntry(intent)
handleAppIntent(intent)
super.onNewIntent(intent)
}

private var resetHomeOnResume = false

private fun shouldResetLauncherEntry(entry: Intent?): Boolean {
if (entry?.action != Intent.ACTION_MAIN || entry.data != null) return false
if (!entry.hasCategory(Intent.CATEGORY_LAUNCHER) &&
!entry.hasCategory(Intent.CATEGORY_LEANBACK_LAUNCHER)) return false
return !PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("restore_navigation", false)
}

override fun onPostResume() {
super.onPostResume()
if (!resetHomeOnResume) return
resetHomeOnResume = false
val host = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment
?: return
host.navController.navigate(R.id.navigation_home, null,
NavOptions.Builder().setPopUpTo(host.navController.graph.id, false)
.setLaunchSingleTop(true).build())
}

private fun handleAppIntent(intent: Intent?) {
if (intent == null) return
val str = intent.dataString
Expand All @@ -757,7 +783,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
val destinationId = item.itemId

// Check if we are already at the selected destination
if (navController.currentDestination?.id == destinationId) return false
val restoreNavigation = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("restore_navigation", false)
if (navController.currentDestination?.id == destinationId && restoreNavigation) return false

// Make all nav buttons focus on this specific view when nextFocusRightId
val targetView = when (destinationId) {
Expand Down Expand Up @@ -788,7 +816,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}

val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(restoreNavigation)
.setEnterAnim(R.anim.enter_anim)
.setExitAnim(R.anim.exit_anim)
.setPopEnterAnim(R.anim.pop_enter)
Expand All @@ -797,7 +825,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
builder.setPopUpTo(
navController.graph.findStartDestination().id,
inclusive = false,
saveState = true
saveState = restoreNavigation
)
}
return try {
Expand Down Expand Up @@ -1183,6 +1211,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}

override fun onCreate(savedInstanceState: Bundle?) {
resetHomeOnResume = savedInstanceState != null && lastNonConfigurationInstance == null && shouldResetLauncherEntry(intent)
app.initClient(this, ignoreSSL = false)
@OptIn(UnsafeSSL::class)
insecureApp.initClient(this, ignoreSSL = true)
Expand Down Expand Up @@ -1678,6 +1707,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

if (isLayout(TV or EMULATOR)) binding?.let {
autoHideRail = com.lagradost.cloudstream3.ui.AutoHideNavigationRail(it.root, it.navRailView, it.navHostFragment)
}
navController.addOnDestinationChangedListener { _: NavController, navDestination: NavDestination, bundle: Bundle? ->
// Intercept search and add a query
updateNavBar(navDestination)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.lagradost.cloudstream3.ui

import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.navigationrail.NavigationRailView
import com.lagradost.cloudstream3.R
import java.lang.ref.WeakReference

internal class AutoHideNavigationRail(private val root: View, private val rail: NavigationRailView, private val content: View) {
private var available = rail.visibility == View.VISIBLE
private var hidden = false
private var lastContent = WeakReference<View>(null)
private val enabled get() = PreferenceManager.getDefaultSharedPreferences(root.context).getBoolean("auto_hide_navigation_rail", true)
init {
root.viewTreeObserver.addOnGlobalFocusChangeListener { _, focus ->
if (focus != null && !inside(focus, rail) && inside(focus, content)) {
lastContent = WeakReference(focus)
if (available && enabled && posterRow(focus) != null) show(false)
}
}
}
fun destinationChanged(visible: Boolean) { available = visible; if (!enabled) hidden = false; applyVisibility() }
fun dispatch(event: KeyEvent, focus: View?): Boolean {
if (!available || !enabled || event.action != KeyEvent.ACTION_DOWN || event.repeatCount != 0) return false
if (event.keyCode == KeyEvent.KEYCODE_MENU) {
if (hidden || !rail.hasFocus()) reveal() else restoreContent()
return true
}
if (event.keyCode == KeyEvent.KEYCODE_BACK && rail.hasFocus() && lastContent.get()?.isAttachedToWindow == true) {
restoreContent(); return true
}
val leadingKey = if (root.layoutDirection == View.LAYOUT_DIRECTION_RTL) KeyEvent.KEYCODE_DPAD_RIGHT else KeyEvent.KEYCODE_DPAD_LEFT
if (hidden && event.keyCode == leadingKey && focus != null) {
val row = posterRow(focus) ?: return false
val item = row.findContainingItemView(focus) ?: return false
val manager = row.layoutManager as? LinearLayoutManager ?: return false
val horizontal = manager.orientation == RecyclerView.HORIZONTAL
val span = (manager as? GridLayoutManager)?.spanCount ?: 1
if (RailNavigationPolicy.isLeadingItem(row.getChildAdapterPosition(item), horizontal, span)) { reveal(); return true }
}
return false
}
private fun reveal() { show(true); (rail.findViewById<View>(rail.selectedItemId) ?: rail).requestFocus() }
private fun restoreContent() { show(false); lastContent.get()?.takeIf { it.isAttachedToWindow }?.requestFocus() ?: content.requestFocus() }
private fun show(visible: Boolean) { hidden = !visible; applyVisibility() }
private fun applyVisibility() {
rail.visibility = if (!available || hidden) View.INVISIBLE else View.VISIBLE
val margin = if (available && !hidden) root.resources.getDimensionPixelSize(R.dimen.nav_rail_view_width) else 0
(content.layoutParams as? ViewGroup.MarginLayoutParams)?.let { if (it.marginStart != margin) { it.marginStart = margin; content.layoutParams = it } }
}
private fun posterRow(focus: View): RecyclerView? {
var node: View? = focus
var poster = false
while (node != null && node !== content) {
if (node is RecyclerView) return if (poster) node else null
poster = poster || ((node === focus || node.isFocusable || node.isClickable) && node.findViewById<View?>(R.id.imageView) != null)
node = node.parent as? View
}
return null
}
private fun inside(child: View, parent: View): Boolean {
var node: View? = child
while (node != null) { if (node === parent) return true; node = node.parent as? View }
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.lagradost.cloudstream3.ui

internal object RailNavigationPolicy {
fun isLeadingItem(position: Int, horizontal: Boolean, spanCount: Int): Boolean =
position >= 0 && if (horizontal) position == 0 else position % spanCount.coerceAtLeast(1) == 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ object SettingsGeneralScreen : SearchableSettings {
}.sortedBy { fromTagToLanguageName(it) ?: it }

return persistentListOf(
Preference.PreferenceGroup(
title = stringResource(R.string.navigation_options_title),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = settings.general.restoreNavigation,
title = stringResource(R.string.restore_navigation_title),
subtitle = stringResource(R.string.restore_navigation_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = settings.general.autoHideNavigationRail,
title = stringResource(R.string.auto_hide_rail_title),
subtitle = stringResource(R.string.auto_hide_rail_summary),
),
),
),
Preference.PreferenceGroup(title = stringResource(R.string.extension_language), preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = settings.general.locale,
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/values-es/navigation_options.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<resources>
<string name="navigation_options_title">Navegación</string>
<string name="auto_hide_rail_title">Ocultar automáticamente el menú lateral en TV</string>
<string name="auto_hide_rail_summary">Se oculta al recorrer carátulas. Vuelve desde el primer elemento o pulsa Opciones para mostrarlo.</string>
<string name="restore_navigation_title">Restaurar la pantalla anterior</string>
<string name="restore_navigation_summary">Conserva la pantalla anterior al abrir la app y cambiar de sección. Desactivado: abre Inicio desde el lanzador y la raíz de cada sección.</string>
</resources>
7 changes: 7 additions & 0 deletions app/src/main/res/values/navigation_options.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<resources>
<string name="navigation_options_title">Navigation</string>
<string name="auto_hide_rail_title">Automatically hide the TV sidebar</string>
<string name="auto_hide_rail_summary">Hide while browsing posters. Return from the first item or press Menu to show it.</string>
<string name="restore_navigation_title">Restore previous screen</string>
<string name="restore_navigation_summary">Keep the previous screen when opening the app and switching sections. When off, launcher entry opens Home and sections open their root screen.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/xml/settings_general.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/navigation_options_title">
<SwitchPreferenceCompat android:key="restore_navigation" android:title="@string/restore_navigation_title" android:summary="@string/restore_navigation_summary" android:defaultValue="false" />
<SwitchPreferenceCompat android:key="auto_hide_navigation_rail" android:title="@string/auto_hide_rail_title" android:summary="@string/auto_hide_rail_summary" android:defaultValue="true" />
</PreferenceCategory>
<Preference
android:icon="@drawable/ic_baseline_language_24"
android:key="@string/locale_key"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.lagradost.cloudstream3.ui

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class RailNavigationPolicyTest {
@Test fun horizontalRowsOnlyRevealAtTheFirstItem() {
assertTrue(RailNavigationPolicy.isLeadingItem(0, true, 1))
assertFalse(RailNavigationPolicy.isLeadingItem(1, true, 1))
assertFalse(RailNavigationPolicy.isLeadingItem(6, true, 1))
}
@Test fun gridsRevealAtTheLeadingColumn() {
assertTrue(RailNavigationPolicy.isLeadingItem(8, false, 4))
assertFalse(RailNavigationPolicy.isLeadingItem(9, false, 4))
assertFalse(RailNavigationPolicy.isLeadingItem(-1, false, 4))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ class PlayerPreferences(preferences: PreferenceStore) {
}

class GeneralPreferences(preferences: PreferenceStore) {
val restoreNavigation = preferences.getBoolean("restore_navigation", false)
val autoHideNavigationRail = preferences.getBoolean("auto_hide_navigation_rail", true)
val locale = preferences.getString("app_locale", "")
val bananas = preferences.getInt("benene_count", 0)
val parallelDownloads = preferences.getInt("download_parallel_key", 3)
Expand Down
Loading