diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6fd4ae21..5c864117 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,3 @@ -import com.android.build.gradle.api.BaseVariantOutput import org.jetbrains.dokka.gradle.DokkaTask import java.io.ByteArrayOutputStream import java.net.URL @@ -52,7 +51,7 @@ android { targetSdk = 33 versionCode = 59 - versionName = "4.1.1" + versionName = "4.1.2" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") @@ -108,14 +107,19 @@ android { versionCode = (System.currentTimeMillis() / 60000).toInt() } } + //toolchain { + // languageVersion.set(JavaLanguageVersion.of(17)) + // } + // jvmToolchain(17) + compileOptions { isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" freeCompilerArgs = listOf("-Xjvm-default=compatibility") } lint { @@ -131,22 +135,22 @@ repositories { dependencies { implementation("com.google.android.mediahome:video:1.0.0") - implementation("androidx.test.ext:junit-ktx:1.1.3") + implementation("androidx.test.ext:junit-ktx:1.1.5") testImplementation("org.json:json:20180813") - implementation("androidx.core:core-ktx:1.8.0") - implementation("androidx.appcompat:appcompat:1.4.2") // need target 32 for 1.5.0 + implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.appcompat:appcompat:1.6.1") // need target 32 for 1.5.0 // dont change this to 1.6.0 it looks ugly af implementation("com.google.android.material:material:1.5.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.navigation:navigation-fragment-ktx:2.5.1") - implementation("androidx.navigation:navigation-ui-ktx:2.5.1") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") + implementation("androidx.navigation:navigation-ui-ktx:2.6.0") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.3") - androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test:core") //implementation("io.karn:khttp-android:0.1.2") //okhttp instead @@ -165,7 +169,7 @@ dependencies { // implementation("androidx.leanback:leanback-paging:1.1.0-alpha09") - // Exoplayer + // Media 3 implementation("androidx.media3:media3-common:1.1.0") implementation("androidx.media3:media3-exoplayer:1.1.0") implementation("androidx.media3:media3-datasource-okhttp:1.1.0") @@ -174,10 +178,8 @@ dependencies { implementation("androidx.media3:media3-cast:1.1.0") implementation("androidx.media3:media3-exoplayer-hls:1.1.0") implementation("androidx.media3:media3-exoplayer-dash:1.1.0") - - - // Use the Jellyfin ffmpeg extension for easy ffmpeg audio decoding in exoplayer. Thank you Jellyfin <3 -// implementation("org.jellyfin.exoplayer:exoplayer-ffmpeg-extension:2.18.2+1") + // Custom ffmpeg extension for audio codecs + implementation("com.github.recloudstream:media-ffmpeg:1.1.0") //implementation("com.google.android.exoplayer:extension-leanback:2.14.0") @@ -203,8 +205,8 @@ dependencies { //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0") // Downloading - implementation("androidx.work:work-runtime:2.8.0") - implementation("androidx.work:work-runtime-ktx:2.8.0") + implementation("androidx.work:work-runtime:2.8.1") + implementation("androidx.work:work-runtime-ktx:2.8.1") // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 3cfde983..0bcd4152 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -19,8 +19,11 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat +import androidx.core.view.children import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession +import com.google.android.material.chip.ChipGroup +import com.google.android.material.navigationrail.NavigationRailView import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.mvvm.logError @@ -39,6 +42,13 @@ import org.schabi.newpipe.extractor.NewPipe import java.lang.ref.WeakReference import java.util.* +enum class FocusDirection { + Start, + End, + Up, + Down, +} + object CommonActivity { private var _activity: WeakReference? = null @@ -301,7 +311,8 @@ object CommonActivity { private fun localLook(from: View, id: Int): View? { if (id == NO_ID) return null var currentLook: View = from - while (true) { + // limit to 15 look depth + for (i in 0..15) { currentLook.findViewById(id)?.let { return it } currentLook = (currentLook.parent as? View) ?: break } @@ -317,17 +328,70 @@ object CommonActivity { currentLook = currentLook.parent as? View ?: break }*/ + /** skips the initial stage of searching for an id using the view, see getNextFocus for specification */ + fun continueGetNextFocus( + root: Any?, + view: View, + direction: FocusDirection, + nextId: Int, + depth: Int = 0 + ): View? { + if (nextId == NO_ID) return null + + // do an initial search for the view, in case the localLook is too deep we can use this as + // an early break and backup view + var next = + when (root) { + is Activity -> root.findViewById(nextId) + is View -> root.rootView.findViewById(nextId) + else -> null + } ?: return null + + next = localLook(view, nextId) ?: next + + // if cant focus but visible then break and let android decide + // the exception if is the view is a parent and has children that wants focus + val hasChildrenThatWantsFocus = (next as? ViewGroup)?.let { parent -> + parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.childCount > 0 + } ?: false + if (!next.isFocusable && next.isShown && !hasChildrenThatWantsFocus) return null + + // if not shown then continue because we will "skip" over views to get to a replacement + if (!next.isShown) { + // we don't want a while true loop, so we let android decide if we find a recursive view + if (next == view) return null + return getNextFocus(root, next, direction, depth + 1) + } + + (when (next) { + is ChipGroup -> { + next.children.firstOrNull { it.isFocusable && it.isShown } + } + + is NavigationRailView -> { + next.findViewById(next.selectedItemId) ?: next.findViewById(R.id.navigation_home) + } + + else -> null + })?.let { + return it + } + + // nothing wrong with the view found, return it + return next + } + /** recursively looks for a next focus up to a depth of 10, * this is used to override the normal shit focus system * because this application has a lot of invisible views that messes with some tv devices*/ - private fun getNextFocus( - act: Activity?, + fun getNextFocus( + root: Any?, view: View?, direction: FocusDirection, depth: Int = 0 ): View? { // if input is invalid let android decide + depth test to not crash if loop is found - if (view == null || depth >= 10 || act == null) { + if (view == null || depth >= 10 || root == null) { return null } @@ -359,38 +423,12 @@ object CommonActivity { // if not specified then use forward id nextId = view.nextFocusForwardId // if view is still not found to next focus then return and let android decide - if (nextId == NO_ID) return null + if (nextId == NO_ID) + return null } - - var next = act.findViewById(nextId) ?: return null - - next = localLook(view, nextId) ?: next - - var currentLook: View = view - while (currentLook.findViewById(nextId)?.also { next = it } == null) { - currentLook = (currentLook.parent as? View) ?: break - } - - // if cant focus but visible then break and let android decide - // the exception if is the view is a parent and has children that wants focus - val hasChildrenThatWantsFocus = (next as? ViewGroup)?.let { parent -> - parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.childCount > 0 - } ?: false - if (!next.isFocusable && next.isShown && !hasChildrenThatWantsFocus) return null - - // if not shown then continue because we will "skip" over views to get to a replacement - if (!next.isShown) return getNextFocus(act, next, direction, depth + 1) - - // nothing wrong with the view found, return it - return next + return continueGetNextFocus(root, view, direction, nextId, depth) } - private enum class FocusDirection { - Start, - End, - Up, - Down, - } fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?) { //println("Keycode: $keyCode") @@ -520,7 +558,7 @@ object CommonActivity { else -> null } - + // println("NEXT FOCUS : $nextView") if (nextView != null) { nextView.requestFocus() keyEventListener?.invoke(Pair(event, true)) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index b1f60ad7..d6e275ed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -27,6 +27,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.children import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.core.view.marginStart import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavController @@ -37,6 +38,8 @@ import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.RecyclerView import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper @@ -91,6 +94,7 @@ import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.ui.player.BasicLink import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.LinkGenerator +import com.lagradost.cloudstream3.ui.result.LinearListLayout import com.lagradost.cloudstream3.ui.result.ResultViewModel2 import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.setImage @@ -110,6 +114,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isLtr import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable +import com.lagradost.cloudstream3.utils.AppUtils.isRtl import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadResult @@ -146,6 +151,7 @@ import java.lang.ref.WeakReference import java.net.URI import java.net.URLDecoder import java.nio.charset.Charset +import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.reflect.KClass import kotlin.system.exitProcess @@ -848,6 +854,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { private var animator: ValueAnimator? = null + /** if this is enabled it will keep the focus unmoving + * during listview move */ + private const val NO_MOVE_LIST: Boolean = false + + /** If this is enabled then it will try to move the + * listview focus to the left instead of center */ + private const val LEFTMOST_MOVE_LIST: Boolean = true + + private val reflectedScroll by lazy { + try { + RecyclerView::class.java.declaredMethods.firstOrNull { + it.name == "scrollStep" + }?.also { it.isAccessible = true } + } catch (t : Throwable) { + null + } + } + @MainThread fun updateFocusView(newFocus: View?, same: Boolean = false) { val focusOutline = focusOutline.get() ?: return @@ -867,17 +891,67 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { if (newFocus != null) { lastFocus = WeakReference(newFocus) + val parent = newFocus.parent + var targetDx = 0 + if (parent is RecyclerView) { + val layoutManager = parent.layoutManager + if (layoutManager is LinearListLayout && layoutManager.orientation == LinearLayoutManager.HORIZONTAL) { + val dx = + LinearSnapHelper().calculateDistanceToFinalSnap(layoutManager, newFocus) + ?.get(0) + if (dx != null) { + val rdx = if (LEFTMOST_MOVE_LIST) { + // this makes the item the leftmost in ltr, instead of center + val diff = + ((layoutManager.width - layoutManager.paddingStart - newFocus.measuredWidth) / 2) - newFocus.marginStart + dx + if (parent.isRtl()) { + -diff + } else { + diff + } + } else { + if (dx > 0) dx else 0 + } + + if(!NO_MOVE_LIST) { + parent.smoothScrollBy(rdx, 0) + }else { + val smoothScroll = reflectedScroll + if(smoothScroll == null) { + parent.smoothScrollBy(rdx, 0) + } else { + try { + // this is very fucked but because it is a protected method to + // be able to compute the scroll I use reflection, scroll, then + // scroll back, then smooth scroll and set the no move + val out = IntArray(2) + smoothScroll.invoke(parent, rdx, 0, out) + val scrolledX = out[0] + if(abs(scrolledX) <= 0) { // newFocus.measuredWidth*2 + smoothScroll.invoke(parent, -rdx, 0, out) + parent.smoothScrollBy(scrolledX, 0) + if (NO_MOVE_LIST) targetDx = scrolledX + } + } catch (t : Throwable) { + parent.smoothScrollBy(rdx, 0) + } + } + } + } + } + } val out = IntArray(2) newFocus.getLocationInWindow(out) val (screenX, screenY) = out var (x, y) = screenX.toFloat() to screenY.toFloat() val (currentX, currentY) = focusOutline.translationX to focusOutline.translationY - // println(">><<< $x $y $currentX $currentY") + if (!newFocus.isLtr()) { x = x - focusOutline.rootView.width + newFocus.measuredWidth } + x -= targetDx // out of bounds = 0,0 if (screenX == 0 && screenY == 0) { @@ -1093,9 +1167,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } //Automatically download not existing plugins, using mode specified. - val auto_download_plugin = AutoDownloadMode.getEnum(settingsManager.getInt(getString(R.string.auto_download_plugins_key), 0)) ?: AutoDownloadMode.Disable + val auto_download_plugin = AutoDownloadMode.getEnum( + settingsManager.getInt( + getString(R.string.auto_download_plugins_key), + 0 + ) + ) ?: AutoDownloadMode.Disable if (auto_download_plugin != AutoDownloadMode.Disable) { - PluginManager.downloadNotExistingPluginsAndLoad(this@MainActivity, auto_download_plugin) + PluginManager.downloadNotExistingPluginsAndLoad( + this@MainActivity, + auto_download_plugin + ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 1d813ef1..f62482ed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -5,11 +5,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys @@ -110,7 +111,11 @@ class DownloadChildFragment : Fragment() { downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } binding?.downloadChildList?.adapter = adapter - binding?.downloadChildList?.layoutManager = GridLayoutManager(context, 1) + binding?.downloadChildList?.setLinearListLayout( + isHorizontal = false, + nextDown = FOCUS_SELF, + nextRight = FOCUS_SELF + )//layoutManager = GridLayoutManager(context, 1) updateList(folder) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index c8b381a6..27c2e1a3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -40,6 +40,8 @@ import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding import com.lagradost.cloudstream3.databinding.StreamInputBinding import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.ui.player.BasicLink +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import java.net.URI @@ -74,7 +76,7 @@ class DownloadFragment : Fragment() { super.onDestroyView() } - var binding : FragmentDownloadsBinding? = null + var binding: FragmentDownloadsBinding? = null override fun onCreateView( inflater: LayoutInflater, @@ -151,6 +153,7 @@ class DownloadFragment : Fragment() { ) } } + 1 -> { (activity as AppCompatActivity?)?.loadResult( click.data.url, @@ -187,7 +190,13 @@ class DownloadFragment : Fragment() { binding?.downloadList?.apply { this.adapter = adapter - layoutManager = GridLayoutManager(context, 1) + setLinearListLayout( + isHorizontal = false, + nextRight = FOCUS_SELF, + nextUp = FOCUS_SELF, + nextDown = FOCUS_SELF + ) + //layoutManager = GridLayoutManager(context, 1) } // Should be visible in emulator layout diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index 607cda01..f84966eb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -88,14 +88,14 @@ class HomeChildItemAdapter( private val nextFocusUp: Int? = null, private val nextFocusDown: Int? = null, private val isHorizontal: Boolean = false, - private val isRtl : Boolean + private val isRtl: Boolean ) : RecyclerView.ViewHolder(binding.root) { fun bind(card: SearchResponse, position: Int) { // TV focus fixing - val nextFocusBehavior = when (position) { + /*val nextFocusBehavior = when (position) { 0 -> true itemCount - 1 -> false else -> null @@ -113,7 +113,7 @@ class HomeChildItemAdapter( } else { itemView.nextFocusRightId = -1 itemView.nextFocusLeftId = -1 - } + }*/ when (binding) { @@ -171,7 +171,7 @@ class HomeChildItemAdapter( card, position, itemView, - nextFocusBehavior, + null, // nextFocusBehavior, nextFocusUp, nextFocusDown ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index a6e1b5e6..6f9a1654 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -310,6 +310,17 @@ class HomeFragment : Fragment() { selectedTypes: List, validTypes: List, callback: (List) -> Unit + ) { + bindChips(header, selectedTypes, validTypes, callback, null, null) + } + + fun bindChips( + header: TvtypesChipsBinding?, + selectedTypes: List, + validTypes: List, + callback: (List) -> Unit, + nextFocusDown: Int?, + nextFocusUp: Int? ) { if (header == null) return val pairList = getPairList(header) @@ -317,6 +328,17 @@ class HomeFragment : Fragment() { val isValid = validTypes.any { types.contains(it) } button?.isVisible = isValid button?.isChecked = isValid && selectedTypes.any { types.contains(it) } + button?.isFocusable = true + if (isTrueTvSettings()) { + button?.isFocusableInTouchMode = true + } + + if (nextFocusDown != null) + button?.nextFocusDownId = nextFocusDown + + if (nextFocusUp != null) + button?.nextFocusUpId = nextFocusUp + button?.setOnCheckedChangeListener { _, _ -> val list = ArrayList() for ((sbutton, vvalidTypes) in pairList) { @@ -462,7 +484,7 @@ class HomeFragment : Fragment() { private val apiChangeClickListener = View.OnClickListener { view -> view.context.selectHomepage(currentApiName) { api -> - homeViewModel.loadAndCancel(api, forceReload = true,fromUI = true) + homeViewModel.loadAndCancel(api, forceReload = true, fromUI = true) } /*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index f6c3fead..163a60a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.HomepageParentBinding +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse @@ -154,7 +155,7 @@ open class ParentItemAdapter( class ParentViewHolder constructor( val binding: HomepageParentBinding, - // val viewModel: HomeViewModel, + // val viewModel: HomeViewModel, private val clickCallback: (SearchClickCallback) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val expandCallback: ((String) -> Unit)? = null, @@ -162,7 +163,8 @@ open class ParentItemAdapter( RecyclerView.ViewHolder(binding.root) { val title: TextView = binding.homeChildMoreInfo private val recyclerView: RecyclerView = binding.homeChildRecyclerview - + private val startFocus = R.id.nav_rail_view + private val endFocus = FOCUS_SELF fun update(expand: HomeViewModel.ExpandableHomepageList) { val info = expand.list (recyclerView.adapter as? HomeChildItemAdapter?)?.apply { @@ -176,8 +178,13 @@ open class ParentItemAdapter( nextFocusDown = recyclerView.nextFocusDownId, ).apply { isHorizontal = info.isHorizontalImages + hasNext = expand.hasNext } - recyclerView.setLinearListLayout() + recyclerView.setLinearListLayout( + isHorizontal = true, + nextLeft = startFocus, + nextRight = endFocus, + ) } } @@ -192,7 +199,11 @@ open class ParentItemAdapter( isHorizontal = info.isHorizontalImages hasNext = expand.hasNext } - recyclerView.setLinearListLayout() + recyclerView.setLinearListLayout( + isHorizontal = true, + nextLeft = startFocus, + nextRight = endFocus, + ) title.text = info.name recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index eb7b6f74..1684dfe5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -14,6 +14,7 @@ import androidx.viewbinding.ViewBinding import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable +import com.google.android.material.chip.ChipGroup import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.activity @@ -29,6 +30,7 @@ import com.lagradost.cloudstream3.mvvm.debugException import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.ResultViewModel2 import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.setLinearListLayout @@ -416,6 +418,7 @@ class HomeParentItemAdapterPreview( isChecked = checked.contains(watch) } } + toggleListHolder?.isGone = visible.isEmpty() } } ?: debugException { "Expected findViewTreeLifecycleOwner" } } @@ -428,6 +431,8 @@ class HomeParentItemAdapterPreview( Pair(itemView.findViewById(R.id.home_plan_to_watch_btt), WatchType.PLANTOWATCH), ) + private val toggleListHolder : ChipGroup? = itemView.findViewById(R.id.home_type_holder) + init { previewViewpager.setPageTransformer(HomeScrollTransformer()) @@ -435,8 +440,8 @@ class HomeParentItemAdapterPreview( resumeRecyclerView.adapter = resumeAdapter bookmarkRecyclerView.adapter = bookmarkAdapter - resumeRecyclerView.setLinearListLayout() - bookmarkRecyclerView.setLinearListLayout() + resumeRecyclerView.setLinearListLayout(nextLeft = R.id.nav_rail_view, nextRight = FOCUS_SELF) + bookmarkRecyclerView.setLinearListLayout(nextLeft = R.id.nav_rail_view, nextRight = FOCUS_SELF) fixPaddingStatusbarMargin(topPadding) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 70e12a47..2067eb04 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -7,16 +7,14 @@ import android.os.Looper import android.util.Log import android.util.Rational import android.widget.FrameLayout -import androidx.media3.common.C -import androidx.preference.PreferenceManager import androidx.media3.common.C.* import androidx.media3.common.Format import androidx.media3.common.MediaItem -import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.MimeTypes import androidx.media3.common.PlaybackException import androidx.media3.common.Player import androidx.media3.common.TrackGroup +import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.Tracks import androidx.media3.common.VideoSize import androidx.media3.database.StandaloneDatabaseProvider @@ -30,6 +28,7 @@ import androidx.media3.datasource.cache.SimpleCache import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.DefaultRenderersFactory +import androidx.media3.exoplayer.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.SeekParameters import androidx.media3.exoplayer.source.ClippingMediaSource @@ -41,6 +40,7 @@ import androidx.media3.exoplayer.text.TextRenderer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.ui.SubtitleView +import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey @@ -49,8 +49,8 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle -import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData +import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorUri @@ -397,7 +397,7 @@ class CS3IPlayer : IPlayer { if (subtitle == null) { trackSelector.setParameters( trackSelector.buildUponParameters() - .setPreferredTextLanguage(null) + .setTrackTypeDisabled(TRACK_TYPE_TEXT, true) .clearOverridesOfType(TRACK_TYPE_TEXT) ) } else { @@ -415,6 +415,7 @@ class CS3IPlayer : IPlayer { .apply { val track = getTextTrack(subtitle.getId()) if (track != null) { + setTrackTypeDisabled(TRACK_TYPE_TEXT, false) setOverrideForType( TrackSelectionOverride( track.first, @@ -662,12 +663,7 @@ class CS3IPlayer : IPlayer { private fun getTrackSelector(context: Context, maxVideoHeight: Int?): TrackSelector { val trackSelector = DefaultTrackSelector(context) - trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(context) - // .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) - .setRendererDisabled(C.TRACK_TYPE_TEXT, true) - // Experimental, I think this causes issues with audio track init 5001 -// .setTunnelingEnabled(true) - .setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT) + trackSelector.parameters = trackSelector.buildUponParameters() // This will not force higher quality videos to fail // but will make the m3u8 pick the correct preferred .setMaxVideoSize(Int.MAX_VALUE, maxVideoHeight ?: Int.MAX_VALUE) @@ -701,9 +697,9 @@ class CS3IPlayer : IPlayer { ExoPlayer.Builder(context) .setRenderersFactory { eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput -> DefaultRenderersFactory(context).apply { -// setEnableDecoderFallback(true) + setEnableDecoderFallback(true) // Enable Ffmpeg extension -// setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON) + setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON) }.createRenderers( eventHandler, videoRendererEventListener, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt index 4bed0c9d..93857234 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt @@ -11,6 +11,7 @@ import android.util.Rational import androidx.annotation.RequiresApi import androidx.annotation.StringRes import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import kotlin.math.roundToInt class PlayerPipHelper { @@ -88,22 +89,25 @@ class PlayerPipHelper { val ratioAccuracy = 100000 // To convert the float to int // java.lang.IllegalArgumentException: setPictureInPictureParams: Aspect ratio is too extreme (must be between 0.418410 and 2.390000) - val fixedRational = aspectRatio?.toFloat()?.coerceIn(mixAspectRatio, maxAspectRatio)?.let { - Rational((it * ratioAccuracy).roundToInt(), ratioAccuracy) - } + val fixedRational = + aspectRatio?.toFloat()?.coerceIn(mixAspectRatio, maxAspectRatio)?.let { + Rational((it * ratioAccuracy).roundToInt(), ratioAccuracy) + } - activity.setPictureInPictureParams( - PictureInPictureParams.Builder() - .apply { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - setSeamlessResizeEnabled(true) - setAutoEnterEnabled(isPlaying) + normalSafeApiCall { + activity.setPictureInPictureParams( + PictureInPictureParams.Builder() + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + setSeamlessResizeEnabled(true) + setAutoEnterEnabled(isPlaying) + } } - } - .setAspectRatio(fixedRational) - .setActions(actions) - .build() - ) + .setAspectRatio(fixedRational) + .setActions(actions) + .build() + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt index 26cb7900..b4e3062b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt @@ -4,19 +4,45 @@ import android.content.Context import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.CommonActivity +import com.lagradost.cloudstream3.CommonActivity.activity +import com.lagradost.cloudstream3.FocusDirection import com.lagradost.cloudstream3.mvvm.logError -fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) { - if (this == null) return +const val FOCUS_SELF = View.NO_ID - 1 +const val FOCUS_INHERIT = FOCUS_SELF - 1 +fun RecyclerView?.setLinearListLayout( + isHorizontal: Boolean = true, + nextLeft: Int = FOCUS_INHERIT, + nextRight: Int = FOCUS_INHERIT, + nextUp: Int = FOCUS_INHERIT, + nextDown: Int = FOCUS_INHERIT +) { + if (this == null) return + val ctx = this.context ?: return this.layoutManager = - this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } } - // ?: this.layoutManager + LinearListLayout(ctx).apply { + if (isHorizontal) setHorizontal() else setVertical() + nextFocusLeft = + if (nextLeft == FOCUS_INHERIT) this@setLinearListLayout.nextFocusLeftId else nextLeft + nextFocusRight = + if (nextRight == FOCUS_INHERIT) this@setLinearListLayout.nextFocusRightId else nextRight + nextFocusUp = + if (nextUp == FOCUS_INHERIT) this@setLinearListLayout.nextFocusUpId else nextUp + nextFocusDown = + if (nextDown == FOCUS_INHERIT) this@setLinearListLayout.nextFocusDownId else nextDown + } } open class LinearListLayout(context: Context?) : LinearLayoutManager(context) { + var nextFocusLeft: Int = View.NO_ID + var nextFocusRight: Int = View.NO_ID + var nextFocusUp: Int = View.NO_ID + var nextFocusDown: Int = View.NO_ID + fun setHorizontal() { orientation = HORIZONTAL } @@ -56,8 +82,37 @@ open class LinearListLayout(context: Context?) : linearSmoothScroller.targetPosition = position startSmoothScroll(linearSmoothScroller) }*/ + + /** from the current focus go to a direction */ + private fun getNextDirection(focused: View?, direction: FocusDirection): View? { + val id = when (direction) { + FocusDirection.Start -> if (isLayoutRTL) nextFocusRight else nextFocusLeft + FocusDirection.End -> if (isLayoutRTL) nextFocusLeft else nextFocusRight + FocusDirection.Up -> nextFocusUp + FocusDirection.Down -> nextFocusDown + } + + return when (id) { + View.NO_ID -> null + FOCUS_SELF -> focused + else -> CommonActivity.continueGetNextFocus( + activity ?: focused, + focused ?: return null, + direction, + id + ) + } + } + override fun onInterceptFocusSearch(focused: View, direction: Int): View? { val dir = if (orientation == HORIZONTAL) { + if (direction == View.FOCUS_DOWN) getNextDirection(focused, FocusDirection.Down)?.let { newFocus -> + return newFocus + } + if (direction == View.FOCUS_UP) getNextDirection(focused, FocusDirection.Up)?.let { newFocus -> + return newFocus + } + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) { // This scrolls the recyclerview before doing focus search, which // allows the focus search to work better. @@ -69,34 +124,45 @@ open class LinearListLayout(context: Context?) : } var ret = if (direction == View.FOCUS_RIGHT) 1 else -1 // only flip on horizontal layout - if (this.isLayoutRTL) { + if (isLayoutRTL) { ret = -ret } ret } else { - if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null + if (direction == View.FOCUS_RIGHT) getNextDirection(focused, FocusDirection.End)?.let { newFocus -> + return newFocus + } + if (direction == View.FOCUS_LEFT) getNextDirection(focused, FocusDirection.Start)?.let { newFocus -> + return newFocus + } + + if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) { + (focused.parent as? RecyclerView)?.focusSearch(direction) + return null + } + + //if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null if (direction == View.FOCUS_DOWN) 1 else -1 } - return try { - getPosition(getCorrectParent(focused))?.let { position -> - val lookfor = dir + position - //clamp(dir + position, 0, recyclerView.adapter?.itemCount ?: return null) + try { + val position = getPosition(getCorrectParent(focused)) ?: return null + val lookFor = dir + position - // refocus on the same view if going out of bounds, note that we only do it - // for out of bounds one way as we may override the start where item == -1 - if (lookfor >= itemCount) { - return getViewFromPos(itemCount - 1) ?: focused - } - - getViewFromPos(lookfor) ?: run { - scrollToPosition(lookfor) + // if out of bounds then refocus as specified + return if (lookFor >= itemCount) { + getNextDirection(focused, if(orientation == HORIZONTAL) FocusDirection.End else FocusDirection.Down) + } else if (lookFor < 0) { + getNextDirection(focused, if(orientation == HORIZONTAL) FocusDirection.Start else FocusDirection.Up) + } else { + getViewFromPos(lookFor) ?: run { + scrollToPosition(lookFor) null } } } catch (e: Exception) { logError(e) - null + return null } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index e1514d63..3ddaee61 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -294,7 +294,7 @@ open class ResultFragmentPhone : FullScreenPlayer(), super.onStop() } - private fun updateUI(id : Int?) { + private fun updateUI(id: Int?) { syncModel.updateUserData() viewModel.reloadEpisodes() } @@ -338,7 +338,12 @@ open class ResultFragmentPhone : FullScreenPlayer(), ) } - resultCastItems.layoutManager = object : LinearListLayout(view.context) { + resultCastItems.setLinearListLayout( + isHorizontal = true, + nextLeft = FOCUS_SELF, + nextRight = FOCUS_SELF + ) + /*resultCastItems.layoutManager = object : LinearListLayout(view.context) { override fun onRequestChildFocus( parent: RecyclerView, state: RecyclerView.State, @@ -356,7 +361,7 @@ open class ResultFragmentPhone : FullScreenPlayer(), } }.apply { this.orientation = RecyclerView.HORIZONTAL - } + }*/ resultCastItems.adapter = ActorAdaptor() resultEpisodes.adapter = @@ -597,8 +602,14 @@ open class ResultFragmentPhone : FullScreenPlayer(), EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep) ) } + DOWNLOAD_ACTION_LONG_CLICK -> { - viewModel.handleAction(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, ep)) + viewModel.handleAction( + EpisodeClickEvent( + ACTION_DOWNLOAD_MIRROR, + ep + ) + ) } else -> DownloadButtonSetup.handleDownloadClick(click) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 698a0ab5..be3de52b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -307,7 +307,29 @@ class ResultFragmentTv : Fragment() { } } - resultEpisodes.setLinearListLayout(isHorizontal = false)/*.layoutManager = + resultEpisodes.setLinearListLayout( + isHorizontal = false, + nextUp = FOCUS_SELF, + nextDown = FOCUS_SELF, + nextRight = FOCUS_SELF, + ) + resultDubSelection.setLinearListLayout( + isHorizontal = false, + nextUp = FOCUS_SELF, + nextDown = FOCUS_SELF, + ) + resultRangeSelection.setLinearListLayout( + isHorizontal = false, + nextUp = FOCUS_SELF, + nextDown = FOCUS_SELF, + ) + resultSeasonSelection.setLinearListLayout( + isHorizontal = false, + nextUp = FOCUS_SELF, + nextDown = FOCUS_SELF, + ) + + /*.layoutManager = LinearListLayout(resultEpisodes.context, resultEpisodes.isRtl()).apply { setVertical() }*/ @@ -367,6 +389,11 @@ class ResultFragmentTv : Fragment() { ) resultCastItems.layoutManager = object : LinearListLayout(view.context) { + + override fun onInterceptFocusSearch(focused: View, direction: Int): View? { + return super.onInterceptFocusSearch(focused, direction) + } + override fun onRequestChildFocus( parent: RecyclerView, state: RecyclerView.State, @@ -383,8 +410,9 @@ class ResultFragmentTv : Fragment() { } } }.apply { - this.orientation = RecyclerView.HORIZONTAL + setHorizontal() } + resultCastItems.adapter = ActorAdaptor { toggleEpisodes(false) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 2f588c19..63213eb9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -45,6 +45,8 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips import com.lagradost.cloudstream3.ui.home.ParentItemAdapter +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.ownHide @@ -519,9 +521,12 @@ class SearchFragment : Fragment() { binding?.apply { searchHistoryRecycler.adapter = historyAdapter - searchHistoryRecycler.layoutManager = GridLayoutManager(context, 1) + searchHistoryRecycler.setLinearListLayout(isHorizontal = false, nextRight = FOCUS_SELF) + //searchHistoryRecycler.layoutManager = GridLayoutManager(context, 1) searchMasterRecycler.adapter = masterAdapter + //searchMasterRecycler.setLinearListLayout(isHorizontal = false, nextRight = FOCUS_SELF) + searchMasterRecycler.layoutManager = GridLayoutManager(context, 1) // Automatically search the specified query, this allows the app search to launch from intent diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 3c0b5b95..7b72fc3b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -23,6 +23,8 @@ import com.lagradost.cloudstream3.databinding.FragmentExtensionsBinding import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.plugins.RepositoryManager +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar @@ -82,6 +84,14 @@ class ExtensionsFragment : Fragment() { setUpToolbar(R.string.extensions) + binding?.repoRecyclerView?.setLinearListLayout( + isHorizontal = false, + nextUp = R.id.settings_toolbar, //FOCUS_SELF, // back has no id so we cant :pensive: + nextDown = R.id.plugin_storage_appbar, + nextRight = FOCUS_SELF, + nextLeft = R.id.nav_rail_view + ) + binding?.repoRecyclerView?.adapter = RepoAdapter(false, { findNavController().navigate( R.id.navigation_settings_extensions_to_navigation_settings_plugins, @@ -126,11 +136,11 @@ class ExtensionsFragment : Fragment() { (binding?.repoRecyclerView?.adapter as? RepoAdapter)?.updateList(it) } - binding?.repoRecyclerView?.apply { + /*binding?.repoRecyclerView?.apply { context?.let { ctx -> layoutManager = LinearRecycleViewLayoutManager(ctx, nextFocusUpId, nextFocusDownId) } - } + }*/ // list_repositories?.setOnClickListener { // // Open webview on tv if browser fails diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt index 00e1806d..d8047c11 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt @@ -62,100 +62,101 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen super.onViewCreated(view, savedInstanceState) val metadata = data.plugin.second binding?.apply { - if (!pluginIcon.setImage(//plugin_icon?.height ?: - metadata.iconUrl?.replace( - "%size%", - "$iconSize" - )?.replace( - "%exact_size%", - "$iconSizeExact" - ), - null, - errorImageDrawable = R.drawable.ic_baseline_extension_24 - ) - ) { - pluginIcon.setImageResource(R.drawable.ic_baseline_extension_24) - } - pluginName.text = metadata.name.removeSuffix("Provider") - pluginVersion.text = metadata.version.toString() - pluginDescription.text = metadata.description ?: getString(R.string.no_data) - pluginSize.text = - if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize( - context, - metadata.fileSize - ) - pluginAuthor.text = - if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString( - ", " - ) - pluginStatus.text = resources.getStringArray(R.array.extension_statuses)[metadata.status] - pluginTypes.text = - if (metadata.tvTypes.isNullOrEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString( - ", " - ) - pluginLang.text = if (metadata.language == null) - getString(R.string.no_data) - else - "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" - - githubBtn.setOnClickListener { - if (metadata.repositoryUrl != null) { - openBrowser(metadata.repositoryUrl) + if (!pluginIcon.setImage(//plugin_icon?.height ?: + metadata.iconUrl?.replace( + "%size%", + "$iconSize" + )?.replace( + "%exact_size%", + "$iconSizeExact" + ), + null, + errorImageDrawable = R.drawable.ic_baseline_extension_24 + ) + ) { + pluginIcon.setImageResource(R.drawable.ic_baseline_extension_24) } - } + pluginName.text = metadata.name.removeSuffix("Provider") + pluginVersion.text = metadata.version.toString() + pluginDescription.text = metadata.description ?: getString(R.string.no_data) + pluginSize.text = + if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize( + context, + metadata.fileSize + ) + pluginAuthor.text = + if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString( + ", " + ) + pluginStatus.text = + resources.getStringArray(R.array.extension_statuses)[metadata.status] + pluginTypes.text = + if (metadata.tvTypes.isNullOrEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString( + ", " + ) + pluginLang.text = if (metadata.language == null) + getString(R.string.no_data) + else + "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" - if (!metadata.canVote()) { - downvote.alpha = .6f - upvote.alpha = .6f - } + githubBtn.setOnClickListener { + if (metadata.repositoryUrl != null) { + openBrowser(metadata.repositoryUrl) + } + } - if (data.isDownloaded) { - // On local plugins page the filepath is provided instead of url. - val plugin = - PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] - if (plugin?.openSettings != null && context != null) { - actionSettings.isVisible = true - actionSettings.setOnClickListener { - try { - plugin.openSettings!!.invoke(requireContext()) - } catch (e: Throwable) { - Log.e( - "PluginAdapter", - "Failed to open ${metadata.name} settings: ${ - Log.getStackTraceString(e) - }" - ) + if (!metadata.canVote()) { + downvote.alpha = .6f + upvote.alpha = .6f + } + + if (data.isDownloaded) { + // On local plugins page the filepath is provided instead of url. + val plugin = + PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] + if (plugin?.openSettings != null && context != null) { + actionSettings.isVisible = true + actionSettings.setOnClickListener { + try { + plugin.openSettings!!.invoke(requireContext()) + } catch (e: Throwable) { + Log.e( + "PluginAdapter", + "Failed to open ${metadata.name} settings: ${ + Log.getStackTraceString(e) + }" + ) + } } + } else { + actionSettings.isVisible = false } } else { actionSettings.isVisible = false } - } else { - actionSettings.isVisible = false - } - upvote.setOnClickListener { + upvote.setOnClickListener { + ioSafe { + metadata.vote(VotingApi.VoteType.UPVOTE).main { + updateVoting(it) + } + } + } + downvote.setOnClickListener { + ioSafe { + metadata.vote(VotingApi.VoteType.DOWNVOTE).main { + updateVoting(it) + } + + } + } + ioSafe { - metadata.vote(VotingApi.VoteType.UPVOTE).main { + metadata.getVotes().main { updateVoting(it) } } } - downvote.setOnClickListener { - ioSafe { - metadata.vote(VotingApi.VoteType.DOWNVOTE).main { - updateVoting(it) - } - - } - } - - ioSafe { - metadata.getVotes().main { - updateVoting(it) - } - } - } } private fun updateVoting(value: Int) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index 1a6215db..172ea659 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -15,6 +15,8 @@ import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.appLanguages @@ -32,7 +34,7 @@ class PluginsFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - val localBinding = FragmentPluginsBinding.inflate(inflater,container,false) + val localBinding = FragmentPluginsBinding.inflate(inflater, container, false) binding = localBinding return localBinding.root//inflater.inflate(R.layout.fragment_plugins, container, false) } @@ -73,48 +75,51 @@ class PluginsFragment : Fragment() { setUpToolbar(name) binding?.settingsToolbar?.apply { - setOnMenuItemClickListener { menuItem -> - when (menuItem?.itemId) { - R.id.download_all -> { - PluginsViewModel.downloadAll(activity, url, pluginViewModel) - } - R.id.lang_filter -> { - val tempLangs = appLanguages.toMutableList() - val languageCodes = mutableListOf("none") + tempLangs.map { (_, _, iso) -> iso } - val languageNames = - mutableListOf(getString(R.string.no_data)) + tempLangs.map { (emoji, name, iso) -> - val flag = - emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" } - "$flag $name" - } - val selectedList = - pluginViewModel.languages.map { it -> languageCodes.indexOf(it) } - - activity?.showMultiDialog( - languageNames, - selectedList, - getString(R.string.provider_lang_settings), - {}) { newList -> - pluginViewModel.languages = newList.map { it -> languageCodes[it] } - pluginViewModel.updateFilteredPlugins() + setOnMenuItemClickListener { menuItem -> + when (menuItem?.itemId) { + R.id.download_all -> { + PluginsViewModel.downloadAll(activity, url, pluginViewModel) } + + R.id.lang_filter -> { + val tempLangs = appLanguages.toMutableList() + val languageCodes = + mutableListOf("none") + tempLangs.map { (_, _, iso) -> iso } + val languageNames = + mutableListOf(getString(R.string.no_data)) + tempLangs.map { (emoji, name, iso) -> + val flag = + emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" } + "$flag $name" + } + val selectedList = + pluginViewModel.languages.map { languageCodes.indexOf(it) } + + activity?.showMultiDialog( + languageNames, + selectedList, + getString(R.string.provider_lang_settings), + {}) { newList -> + pluginViewModel.languages = newList.map { languageCodes[it] } + pluginViewModel.updateFilteredPlugins() + } + } + + else -> {} } - else -> {} + return@setOnMenuItemClickListener true } - return@setOnMenuItemClickListener true - } - val searchView = - menu?.findItem(R.id.search_button)?.actionView as? SearchView + val searchView = + menu?.findItem(R.id.search_button)?.actionView as? SearchView - // Don't go back if active query - setNavigationOnClickListener { - if (searchView?.isIconified == false) { - searchView.isIconified = true - } else { - activity?.onBackPressed() + // Don't go back if active query + setNavigationOnClickListener { + if (searchView?.isIconified == false) { + searchView.isIconified = true + } else { + activity?.onBackPressed() + } } - } searchView?.setOnQueryTextFocusChangeListener { _, hasFocus -> if (!hasFocus) pluginViewModel.search(null) } @@ -137,7 +142,11 @@ class PluginsFragment : Fragment() { // Because onActionViewCollapsed doesn't wanna work we need this workaround :( - + binding?.pluginRecyclerView?.setLinearListLayout( + isHorizontal = false, + nextDown = FOCUS_SELF, + nextRight = FOCUS_SELF, + ) binding?.pluginRecyclerView?.adapter = PluginAdapter { @@ -167,11 +176,18 @@ class PluginsFragment : Fragment() { pluginViewModel.updatePluginList(context, url) binding?.tvtypesChipsScroll?.root?.isVisible = true - bindChips(binding?.tvtypesChipsScroll?.tvtypesChips, emptyList(), TvType.values().toList()) { list -> - pluginViewModel.tvTypes.clear() - pluginViewModel.tvTypes.addAll(list.map { it.name }) - pluginViewModel.updateFilteredPlugins() - } + bindChips( + binding?.tvtypesChipsScroll?.tvtypesChips, + emptyList(), + TvType.values().toList(), + callback = { list -> + pluginViewModel.tvTypes.clear() + pluginViewModel.tvTypes.addAll(list.map { it.name }) + pluginViewModel.updateFilteredPlugins() + }, + nextFocusDown = R.id.plugin_recycler_view, + nextFocusUp = null, + ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 137e1457..991651dc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WhoIsWatchingAdapter +import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.ui.result.setImage @@ -194,7 +195,13 @@ object DataStoreHelper { builder.setContentView(binding.root) val accountName = context.getString(R.string.account) - binding.profilesRecyclerview.setLinearListLayout(isHorizontal = true) + binding.profilesRecyclerview.setLinearListLayout( + isHorizontal = true, + nextUp = FOCUS_SELF, + nextDown = FOCUS_SELF, + nextLeft = FOCUS_SELF, + nextRight = FOCUS_SELF + ) binding.profilesRecyclerview.adapter = WhoIsWatchingAdapter( selectCallBack = { account -> setAccount(account) diff --git a/app/src/main/res/layout/fragment_child_downloads.xml b/app/src/main/res/layout/fragment_child_downloads.xml index a3cc8ce8..9afaea0b 100644 --- a/app/src/main/res/layout/fragment_child_downloads.xml +++ b/app/src/main/res/layout/fragment_child_downloads.xml @@ -1,39 +1,40 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/download_child_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryGrayBackground" + android:orientation="vertical" + tools:context=".ui.download.DownloadFragment"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent"> + android:id="@+id/download_child_toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/primaryGrayBackground" + android:paddingTop="@dimen/navbar_height" + app:layout_scrollFlags="scroll|enterAlways" + app:navigationIconTint="?attr/iconColor" + app:titleTextColor="?attr/textColor" + tools:title="Overlord" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground" + android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusUp="@id/download_child_toolbar" + android:padding="10dp" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:listitem="@layout/download_child_episode" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml index a550efa4..b3583539 100644 --- a/app/src/main/res/layout/fragment_extensions.xml +++ b/app/src/main/res/layout/fragment_extensions.xml @@ -64,6 +64,7 @@ android:focusable="true" android:foreground="@drawable/outline_drawable" android:nextFocusRight="@id/add_repo_button_imageview" + android:nextFocusUp="@id/repo_recycler_view" android:orientation="horizontal" android:padding="10dp" @@ -84,13 +85,13 @@ android:textColor="?attr/textColor" /> + android:elevation="0dp" + app:cardCornerRadius="@dimen/storage_radius" + app:cardElevation="0dp" + app:cardMaxElevation="0dp"> diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index d7fbb9e9..d2c20bc4 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -153,6 +153,7 @@ android:layout_marginStart="@dimen/navbar_width" android:backgroundTint="@color/semiWhite" android:minWidth="150dp" + android:nextFocusUp="@id/home_preview_play_btt" android:nextFocusLeft="@id/nav_rail_view" android:nextFocusDown="@id/home_watch_child_recyclerview" /> @@ -178,6 +179,8 @@ diff --git a/app/src/main/res/layout/fragment_plugins.xml b/app/src/main/res/layout/fragment_plugins.xml index c207b2c3..ee86f12b 100644 --- a/app/src/main/res/layout/fragment_plugins.xml +++ b/app/src/main/res/layout/fragment_plugins.xml @@ -25,18 +25,22 @@ app:titleTextColor="?attr/textColor" tools:title="Overlord" /> - + - diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 2fec04c6..1fde999c 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -248,6 +248,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit style="@style/ResultButtonTV" android:nextFocusRight="@id/result_description" + android:nextFocusUp="@id/result_play_movie" android:nextFocusDown="@id/result_play_series" android:text="@string/play_movie_button" android:visibility="visible" @@ -537,10 +538,10 @@ https://developer.android.com/design/ui/tv/samples/jet-fit + tools:visibility="gone"> --> + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="10dp"> - + @@ -116,7 +118,10 @@ android:background="?attr/primaryBlackBackground" android:descendantFocusability="afterDescendants" + android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusUp="@id/tvtypes_chips" + android:nextFocusDown="@id/search_clear_call_history" android:visibility="gone" tools:listitem="@layout/homepage_parent" /> @@ -134,20 +139,24 @@ android:background="?attr/primaryBlackBackground" android:descendantFocusability="afterDescendants" android:nextFocusLeft="@id/nav_rail_view" - android:visibility="visible" + android:nextFocusUp="@id/tvtypes_chips" + android:nextFocusDown="@id/search_clear_call_history" + android:paddingBottom="50dp" + android:visibility="visible" tools:listitem="@layout/search_history_item" /> + android:layout_height="50dp" + android:layout_gravity="bottom" + android:layout_margin="0dp" + android:nextFocusUp="@id/search_history_recycler" + android:padding="0dp" + android:text="@string/clear_history" + app:cornerRadius="0dp" + app:icon="@drawable/delete_all" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index 63c61393..4c4af404 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -11,11 +11,11 @@ tools:context=".ui.search.SearchFragment"> + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/navbar_width" + android:orientation="vertical" + android:paddingBottom="10dp"> - + @@ -118,38 +120,43 @@ android:background="?attr/primaryBlackBackground" android:descendantFocusability="afterDescendants" android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusUp="@id/tvtypes_chips" + android:nextFocusDown="@id/search_clear_call_history" android:visibility="gone" tools:listitem="@layout/homepage_parent" /> + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground"> + android:layout_height="50dp" + android:layout_gravity="bottom" + android:layout_margin="0dp" + android:layout_marginStart="@dimen/navbar_width" + android:nextFocusUp="@id/search_history_recycler" + android:padding="0dp" + android:text="@string/clear_history" + app:cornerRadius="0dp" + app:icon="@drawable/delete_all" /> \ No newline at end of file diff --git a/app/src/main/res/layout/standard_toolbar.xml b/app/src/main/res/layout/standard_toolbar.xml index a28bdc80..bd1f251d 100644 --- a/app/src/main/res/layout/standard_toolbar.xml +++ b/app/src/main/res/layout/standard_toolbar.xml @@ -1,19 +1,20 @@ - + + android:id="@+id/settings_toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/primaryGrayBackground" + android:descendantFocusability="afterDescendants" + android:paddingTop="@dimen/navbar_height" + app:layout_scrollFlags="scroll|enterAlways" + app:navigationIconTint="?attr/iconColor" + app:titleTextColor="?attr/textColor" + tools:title="Overlord" /> \ No newline at end of file diff --git a/app/src/main/res/layout/tvtypes_chips.xml b/app/src/main/res/layout/tvtypes_chips.xml index 6b13546b..ee792602 100644 --- a/app/src/main/res/layout/tvtypes_chips.xml +++ b/app/src/main/res/layout/tvtypes_chips.xml @@ -6,6 +6,7 @@ android:paddingStart="8dp" android:paddingEnd="8dp" android:id="@+id/home_select_group" + android:descendantFocusability="afterDescendants" app:singleSelection="false" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/layout/tvtypes_chips_scroll.xml b/app/src/main/res/layout/tvtypes_chips_scroll.xml index 66c7efda..8d006036 100644 --- a/app/src/main/res/layout/tvtypes_chips_scroll.xml +++ b/app/src/main/res/layout/tvtypes_chips_scroll.xml @@ -1,10 +1,13 @@ - + android:requiresFadingEdge="horizontal"> - + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 40d445ae..41ee5ed0 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -579,4 +579,6 @@ النوعيات خلفية الملف الشخصي تعذر إنشاء واجهة المستخدم بشكل صحيح ، وهذا خطأ كبير ويجب الإبلاغ عنه على الفور %s + حدد الوضع لتصفية تنزيل المكونات الإضافية + تعطيل diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index db9398ca..a2c3df3e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -571,4 +571,6 @@ \n \nPOZNÁMKA: Pokud je součet 10 nebo vyšší, přehrávač automaticky přeskočí načítání při načtení daného odkazu! Nepodařilo se správně vytvořit rozhraní. Toto je VÁŽNÁ CHYBA, kterou je potřeba ihned nahlásit %s + Zakázat + Výběr režimu pro filtrování stahování doplňků diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ac757b15..1ac81c20 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -547,4 +547,7 @@ Calidades Perfil del fondo La interfaz de usuario no se ha podido crear correctamente, se trata de un GRAN BUG y debe ser reportado inmediatamente %s + Selecciona el modo para filtrar la descarga de los plugins + Desactivar + @string/default_subtitles diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 8386188c..ad7916be 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -13,4 +13,158 @@ Póster do Episodio Regresar Cambiar provedor + Nova actualización atopada! +\n%s -> %s + Recheo + %d min + Configuración + Procurar… + Procurar en %s… + Sen datos + Máis opcións + Seguinte episodio + Xéneros + Mirando + En espera + Completado + Descartado + Planeando ver + Ningún + Remirando + Marcadores + Borrar + Seleccionar estado de visualización + Aplicar + Cancelar + Copiar + Cerrar + Limpar + Gardar + Velocidade do reproductor + Configurar Subtítulos + Cor de Fondo + Cor de Texto + Cor de Contorno + Cor de Ventá + Continuar Vendo + Nota: %.1f + CloudStream + Inicio + Descarga + Compartir + Abrir no Navegador + Navegador + Omitir carga + Cargando… + Reproducir o filme + Reproducir Trailer + Reproducir transmisión en vivo + Transmitir Torrent + Fontes + Tentar de novo a conexión… + Volver + Reproducir Episodio + Descargar + Descargado + Descargando + Descarga Pausada + Descarga comezada + Descarga Fallida + Descarga Cancelada + Descarga rematada + Actualización Comezada + Subtitulado + Borrar Arquivo + Reproducir Arquivo + Continuar Descarga + Pausar Descarga + Desactivar reporte automático de bugs + Máis información + Agochar + Reproducir + Tipo de Borde + Fonte + Non se deron bananas + Seleccionar idioma automáticamente + Descargar Idiomas + Idioma do Subtítulo + Manteña premido para restablecer os valores predeterminados + Importar fontes colocandoas en %s + Borrar + Máis Info + @string/home_play + Unha VPN pode ser necesaria para o correcto funcionamento deste provedor + Este proveedor é un torrent, recomendase o uso dunha VPN + Os metadatos non son proporcionados polo sitio, a carga do video fallará se non existe no sitio + Descripción + Trama non atopada + Descripción non atopada + Amosar Logcat 🐈 + Rexistro + Continúa a reprodución nun reproductor miniatura enriba doutras aplicacións + Reproducir con CloudStream + Procurar + Vista previa do fondo + Velocidade (%.2fx) + Subtítulos + Transmitir + Error Cargando Ligazón + Dobrado + Filtrar Marcadores + Almacenamento Interno + Tamaño de Fonte + Info + Elevación de Subtítulo + Procurar usando proveedores + %d Bananas dadas aos desenvolvedores + Procurar por tipos + Imaxe en Imaxe + Configuración de subtítulos do reprodutor + Subtítulos de Chromecast + Configuración de subtítulos de Chromecast + Modo Eigengravy + Engadir a opción de velocidade no reprodutor + Deslice para avanzar/retroceder + Deslice o dedo de lado a lado para controlar a posición nun video + Deslice para cambiar a configuración + Deslice cara arriba ou cara abaixo no lado esquerdo ou dereito para cambiar o brillo ou o volumen + Reproducir automáticamente episodio seguinte + Comezar o seguinte episodio cando o actual remate + Toca dúas veces para procurar + Tocar dúas veces para pausar + Tempo de rebobinado do reprodutor (segundos) + Toque dúas veces no lado dereito ou esquerdo para rebobinar cara adiante ou cara atrás + Toque dúas veces no medio para pausar + Usar brillo do sistema + Actualizar progreso do visto + Restaurar datos dende o respaldo + Crear respaldo de datos + Arquivo de respaldo cargado + Fallou a restauración dos datos dende o arquivo %s + Datos gardados + Faltan permisos de almacenamento. Por favor tenteo de novo. + Error respaldando de %s + Procurar + Biblioteca + Contas + Actualizacións e copias de seguridade + Info + Procura Avanzada + Da os resultados da procura separados por proveedor + Botón de cambio de tamaño do reprodutor + Eliminar bordes negros + Subtítulos + Sincronizar automáticamente o progreso do episodio actual + Use o brillo do sistema no reprodutor da aplicación en lugar dunha superposición oscura + Só envía datos se a aplicacción falla inesperadamente + Non enviar datos + Mostrar episodio de recheo para Anime + Mostrar Trailers + Mostrar pósters de Kitsu + Ocultar calidade de video nos resultados da procura + Actualicación automática de complementos + Descarga automática de complementos + Selecciona o modo para filtrar a descarga dos complementos + Instala automáticamente todos os complementos aínda non instalados dos repositorios engadidos. + Mostrar actualizacións da aplicación diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index f27b12fe..c413ba60 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -570,4 +570,6 @@ Kualitas Latar belakang profil UI tidak dapat dibuat dengan benar, ini adalah BUG UTAMA dan harus segera dilaporkan %s + Nonaktif + Pilih mode untuk memfilter unduhan plugin diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 5e415dc6..7131ee25 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -204,4 +204,11 @@ エピソード %d は にリリースされます 再視聴 ストリームトレント + 文字の色 + ファイルを削除 + 再生ファイル + 背景の色 + 窓の色 + エッジタイプ + ダウンロードを一時停止する diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 60b776b1..6db36065 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -550,4 +550,7 @@ Jakości Tło profilu Nie można było poprawnie utworzyć interfejsu użytkownika, jest to POWAŻNY BŁĄD i należy go natychmiast zgłosić %s + Wybierz tryb filtrowania pobieranych rozszerzeń + Wyłączać + @string/default_subtitles diff --git a/build.gradle.kts b/build.gradle.kts index 6e2a24f3..972a4caf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.3.1") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20") + classpath("com.android.tools.build:gradle:8.0.2") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20") classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0") // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index baa28c97..d4745142 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Apr 30 17:11:15 CEST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME