Merge branch 'recloudstream:master' into master

This commit is contained in:
CranberrySoup 2023-09-22 11:14:51 +00:00 committed by GitHub
commit 05dd490ab7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 188 additions and 45 deletions

View file

@ -58,8 +58,8 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 33 targetSdk = 33
versionCode = 60 versionCode = 62
versionName = "4.1.9" versionName = "4.2.0"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "") resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")

View file

@ -497,6 +497,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
R.id.navigation_results_phone, R.id.navigation_results_phone,
R.id.navigation_results_tv, R.id.navigation_results_tv,
R.id.navigation_player, R.id.navigation_player,
R.id.navigation_quick_search,
).contains(destination.id) ).contains(destination.id)
binding?.navHostFragment?.apply { binding?.navHostFragment?.apply {
@ -1101,15 +1102,19 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
updateTv() updateTv()
// backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting? // backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting?
try { normalSafeApiCall {
val appVer = BuildConfig.VERSION_NAME val appVer = BuildConfig.VERSION_NAME
val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: "" val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: ""
if (appVer != lastAppAutoBackup) { if (appVer != lastAppAutoBackup) {
setKey("VERSION_NAME", BuildConfig.VERSION_NAME) setKey("VERSION_NAME", BuildConfig.VERSION_NAME)
backup() normalSafeApiCall {
backup()
}
normalSafeApiCall {
// Recompile oat on new version
PluginManager.deleteAllOatFiles(this)
}
} }
} catch (t: Throwable) {
logError(t)
} }
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH // just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH

View file

@ -137,6 +137,20 @@ object PluginManager {
} }
} }
/**
* Deletes all generated oat files which will force Android to recompile the dex extensions.
* This might fix unrecoverable SIGSEGV exceptions when old oat files are loaded in a new app update.
*/
fun deleteAllOatFiles(context: Context) {
File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}").listFiles()?.forEach { repo ->
repo.listFiles { file -> file.name == "oat" && file.isDirectory }?.forEach { file ->
val success = file.deleteRecursively()
Log.i(TAG, "Deleted oat directory: ${file.absolutePath} Success=$success")
}
}
}
fun getPluginsOnline(): Array<PluginData> { fun getPluginsOnline(): Array<PluginData> {
return getKey(PLUGINS_KEY) ?: emptyArray() return getKey(PLUGINS_KEY) ?: emptyArray()
} }

View file

@ -461,6 +461,11 @@ class HomeParentItemAdapterPreview(
} }
} }
homePreviewSearchButton.setOnClickListener { _ ->
// Open blank screen.
viewModel.queryTextSubmit("")
}
// This makes the hidden next buttons only available when on the info button // This makes the hidden next buttons only available when on the info button
// Otherwise you might be able to go to the next item without being at the info button // Otherwise you might be able to go to the next item without being at the info button
homePreviewInfoBtt.setOnFocusChangeListener { _, hasFocus -> homePreviewInfoBtt.setOnFocusChangeListener { _, hasFocus ->

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultEpisode
@ -15,6 +16,7 @@ import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.ExtractorUri
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class PlayerGeneratorViewModel : ViewModel() { class PlayerGeneratorViewModel : ViewModel() {
companion object { companion object {
@ -38,6 +40,11 @@ class PlayerGeneratorViewModel : ViewModel() {
private val _currentSubtitleYear = MutableLiveData<Int?>(null) private val _currentSubtitleYear = MutableLiveData<Int?>(null)
val currentSubtitleYear: LiveData<Int?> = _currentSubtitleYear val currentSubtitleYear: LiveData<Int?> = _currentSubtitleYear
/**
* Save the Episode ID to prevent starting multiple link loading Jobs when preloading links.
*/
private var currentLoadingEpisodeId: Int? = null
fun setSubtitleYear(year: Int?) { fun setSubtitleYear(year: Int?) {
_currentSubtitleYear.postValue(year) _currentSubtitleYear.postValue(year)
} }
@ -72,18 +79,32 @@ class PlayerGeneratorViewModel : ViewModel() {
} }
fun preLoadNextLinks() { fun preLoadNextLinks() {
val id = getId()
// Do not preload if already loading
if (id == currentLoadingEpisodeId) return
Log.i(TAG, "preLoadNextLinks") Log.i(TAG, "preLoadNextLinks")
currentJob?.cancel() currentJob?.cancel()
currentJob = viewModelScope.launchSafe { currentLoadingEpisodeId = id
if (generator?.hasCache == true && generator?.hasNext() == true) {
safeApiCall { currentJob = viewModelScope.launch {
generator?.generateLinks( try {
type = LoadType.InApp, if (generator?.hasCache == true && generator?.hasNext() == true) {
clearCache = false, safeApiCall {
callback = {}, generator?.generateLinks(
subtitleCallback = {}, type = LoadType.InApp,
offset = 1 clearCache = false,
) callback = {},
subtitleCallback = {},
offset = 1
)
}
}
} catch (t: Throwable) {
logError(t)
} finally {
if (currentLoadingEpisodeId == id) {
currentLoadingEpisodeId = null
} }
} }
} }
@ -162,14 +183,14 @@ class PlayerGeneratorViewModel : ViewModel() {
// load more data // load more data
_loadingLinks.postValue(Resource.Loading()) _loadingLinks.postValue(Resource.Loading())
val loadingState = safeApiCall { val loadingState = safeApiCall {
generator?.generateLinks(type = type,clearCache = clearCache, callback = { generator?.generateLinks(type = type, clearCache = clearCache, callback = {
currentLinks.add(it) currentLinks.add(it)
// Clone to prevent ConcurrentModificationException // Clone to prevent ConcurrentModificationException
normalSafeApiCall { normalSafeApiCall {
// Extra normalSafeApiCall since .toSet() iterates. // Extra normalSafeApiCall since .toSet() iterates.
_currentLinks.postValue(currentLinks.toSet()) _currentLinks.postValue(currentLinks.toSet())
} }
}, subtitleCallback = { }, subtitleCallback = {
currentSubs.add(it) currentSubs.add(it)
normalSafeApiCall { normalSafeApiCall {
_currentSubs.postValue(currentSubs.toSet()) _currentSubs.postValue(currentSubs.toSet())

View file

@ -33,6 +33,7 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.search.SearchViewModel import com.lagradost.cloudstream3.ui.search.SearchViewModel
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.ownShow import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -269,6 +270,10 @@ class QuickSearchFragment : Fragment() {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
if (isTrueTvSettings()) {
binding?.quickSearch?.requestFocus()
}
arguments?.getString(AUTOSEARCH_KEY)?.let { arguments?.getString(AUTOSEARCH_KEY)?.let {
binding?.quickSearch?.setQuery(it, true) binding?.quickSearch?.setQuery(it, true)
arguments?.remove(AUTOSEARCH_KEY) arguments?.remove(AUTOSEARCH_KEY)

View file

@ -3,6 +3,8 @@ package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -12,7 +14,10 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.CastItemBinding import com.lagradost.cloudstream3.databinding.CastItemBinding
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class ActorAdaptor(
private var nextFocusUpId: Int? = null,
private val focusCallback: (View?) -> Unit = {}
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
data class ActorMetaData( data class ActorMetaData(
var isInverted: Boolean, var isInverted: Boolean,
val actor: ActorData, val actor: ActorData,
@ -22,7 +27,8 @@ class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerV
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CardViewHolder( return CardViewHolder(
CastItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), focusCallback CastItemBinding.inflate(LayoutInflater.from(parent.context), parent, false),
focusCallback
) )
} }
@ -64,10 +70,10 @@ class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerV
} }
} }
private class CardViewHolder private inner class CardViewHolder
constructor( constructor(
val binding: CastItemBinding, val binding: CastItemBinding,
private val focusCallback : (View?) -> Unit = {} private val focusCallback: (View?) -> Unit = {}
) : ) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
@ -78,8 +84,18 @@ class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerV
Pair(actor.voiceActor?.image, actor.actor.image) Pair(actor.voiceActor?.image, actor.actor.image)
} }
// Fix tv focus escaping the recyclerview
if (position == 0) {
itemView.nextFocusLeftId = R.id.result_cast_items
} else if ((position - 1) == itemCount) {
itemView.nextFocusRightId = R.id.result_cast_items
}
nextFocusUpId?.let {
itemView.nextFocusUpId = it
}
itemView.setOnFocusChangeListener { v, hasFocus -> itemView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus) { if (hasFocus) {
focusCallback(v) focusCallback(v)
} }
} }

View file

@ -114,10 +114,20 @@ class ResultFragmentTv : Fragment() {
} }
} }
private fun hasNoFocus(): Boolean { // private fun hasNoFocus(): Boolean {
val focus = activity?.currentFocus // val focus = activity?.currentFocus
if (focus == null || !focus.isVisible) return true // if (focus == null || !focus.isVisible) return true
return focus == binding?.resultRoot // return focus == binding?.resultRoot
// }
/**
* Force focus any play button.
* Note that this will steal any focus if the episode loading is too slow (unlikely).
*/
private fun focusPlayButton() {
binding?.resultPlayMovie?.requestFocus()
binding?.resultPlaySeries?.requestFocus()
binding?.resultResumeSeries?.requestFocus()
} }
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) { private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
@ -413,7 +423,13 @@ class ResultFragmentTv : Fragment() {
setHorizontal() setHorizontal()
} }
resultCastItems.adapter = ActorAdaptor { val aboveCast = listOf(
binding?.resultEpisodesShow,
binding?.resultBookmarkButton,
).firstOrNull {
it?.isVisible == true
}
resultCastItems.adapter = ActorAdaptor(aboveCast?.id) {
toggleEpisodes(false) toggleEpisodes(false)
} }
} }
@ -454,9 +470,7 @@ class ResultFragmentTv : Fragment() {
resultPlaySeries.isVisible = false resultPlaySeries.isVisible = false
resultResumeSeries.isVisible = true resultResumeSeries.isVisible = true
if (hasNoFocus()) { focusPlayButton()
resultResumeSeries.requestFocus()
}
resultResumeSeries.text = resultResumeSeries.text =
if (resume.isMovie) context?.getString(R.string.play_movie_button) else context?.getNameFull( if (resume.isMovie) context?.getString(R.string.play_movie_button) else context?.getNameFull(
@ -539,9 +553,7 @@ class ResultFragmentTv : Fragment() {
) )
return@setOnLongClickListener true return@setOnLongClickListener true
} }
if (hasNoFocus()) { focusPlayButton()
resultPlayMovie.requestFocus()
}
} }
} }
} }
@ -636,6 +648,9 @@ class ResultFragmentTv : Fragment() {
.show() .show()
} }
} }
// Used to request focus the first time the episodes are loaded.
var hasLoadedEpisodesOnce = false
observeNullable(viewModel.episodes) { episodes -> observeNullable(viewModel.episodes) { episodes ->
binding?.apply { binding?.apply {
resultEpisodes.isVisible = episodes is Resource.Success resultEpisodes.isVisible = episodes is Resource.Success
@ -663,6 +678,10 @@ class ResultFragmentTv : Fragment() {
) )
return@setOnLongClickListener true return@setOnLongClickListener true
} }
if (!hasLoadedEpisodesOnce) {
hasLoadedEpisodesOnce = true
focusPlayButton()
}
} }
/* /*

View file

@ -205,6 +205,11 @@ class SettingsFragment : Fragment() {
} }
} }
} }
// Default focus on TV
if (isTrueTv) {
settingsGeneral.requestFocus()
}
} }
} }
} }

View file

@ -109,18 +109,19 @@ class InAppUpdater {
releases.sortedWith(compareBy { releases.sortedWith(compareBy {
versionRegex.find(it.name)?.groupValues?.get(2) versionRegex.find(it.name)?.groupValues?.get(2)
}).toList().lastOrNull()*/ }).toList().lastOrNull()*/
val found = val foundList =
response.filter { rel -> response.filter { rel ->
!rel.prerelease !rel.prerelease
}.sortedWith(compareBy { release -> }.sortedWith(compareBy { release ->
release.assets.filter { it.content_type == "application/vnd.android.package-archive" } release.assets.firstOrNull { it.content_type == "application/vnd.android.package-archive" }?.name?.let { it1 ->
.getOrNull(0)?.name?.let { it1 ->
versionRegex.find( versionRegex.find(
it1 it1
)?.groupValues?.get(2) )?.groupValues?.let {
it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt()
}
} }
}).toList().lastOrNull() }).toList()
val found = foundList.lastOrNull()
val foundAsset = found?.assets?.getOrNull(0) val foundAsset = found?.assets?.getOrNull(0)
val currentVersion = packageName?.let { val currentVersion = packageName?.let {
packageManager.getPackageInfo( packageManager.getPackageInfo(

View file

@ -114,6 +114,22 @@
android:nextFocusRight="@id/home_switch_account" android:nextFocusRight="@id/home_switch_account"
/> />
<ImageView
android:id="@+id/home_preview_search_button"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg"
android:contentDescription="@string/search"
android:nextFocusLeft="@id/home_change_api"
android:nextFocusRight="@id/home_preview_switch_account"
android:nextFocusDown="@id/home_preview_info_btt"
android:padding="10dp"
android:visibility="gone"
android:src="@drawable/search_icon"
android:tag="@string/tv_no_focus_tag"
app:tint="@color/player_on_button_tv_attr" />
<ImageView <ImageView
android:id="@+id/home_switch_account" android:id="@+id/home_switch_account"

View file

@ -45,9 +45,24 @@
android:background="@drawable/player_button_tv_attr_no_bg" android:background="@drawable/player_button_tv_attr_no_bg"
android:gravity="center_vertical" android:gravity="center_vertical"
android:nextFocusLeft="@id/nav_rail_view" android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/home_preview_switch_account" android:nextFocusRight="@id/home_preview_search_button"
android:nextFocusDown="@id/home_preview_play_btt" /> android:nextFocusDown="@id/home_preview_play_btt" />
<ImageView
android:id="@+id/home_preview_search_button"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg"
android:contentDescription="@string/search"
android:nextFocusLeft="@id/home_preview_change_api"
android:nextFocusRight="@id/home_preview_switch_account"
android:nextFocusDown="@id/home_preview_info_btt"
android:padding="10dp"
android:src="@drawable/search_icon"
android:tag="@string/tv_no_focus_tag"
app:tint="@color/player_on_button_tv_attr" />
<ImageView <ImageView
android:id="@+id/home_preview_switch_account" android:id="@+id/home_preview_switch_account"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -55,7 +70,8 @@
android:layout_gravity="end" android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg" android:background="@drawable/player_button_tv_attr_no_bg"
android:contentDescription="@string/account" android:contentDescription="@string/account"
android:nextFocusLeft="@id/home_preview_change_api" android:nextFocusLeft="@id/home_preview_search_button"
android:nextFocusRight="@id/home_preview_switch_account"
android:nextFocusDown="@id/home_preview_info_btt" android:nextFocusDown="@id/home_preview_info_btt"
android:padding="10dp" android:padding="10dp"

View file

@ -113,7 +113,25 @@
android:background="@drawable/player_button_tv_attr_no_bg" android:background="@drawable/player_button_tv_attr_no_bg"
android:gravity="center_vertical" android:gravity="center_vertical"
android:nextFocusLeft="@id/nav_rail_view" android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/home_switch_account" /> android:nextFocusRight="@id/home_preview_search_button"
android:nextFocusDown="@id/home_preview_play_btt" />
<ImageView
android:id="@+id/home_preview_search_button"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg"
android:clickable="true"
android:contentDescription="@string/search"
android:focusable="true"
android:nextFocusLeft="@id/home_change_api"
android:nextFocusRight="@id/home_switch_account"
android:nextFocusDown="@id/home_preview_info_btt"
android:padding="10dp"
android:src="@drawable/search_icon"
android:tag="@string/tv_no_focus_tag"
app:tint="@color/player_on_button_tv_attr" />
<ImageView <ImageView
android:id="@+id/home_switch_account" android:id="@+id/home_switch_account"
@ -122,7 +140,9 @@
android:layout_gravity="end" android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg" android:background="@drawable/player_button_tv_attr_no_bg"
android:contentDescription="@string/account" android:contentDescription="@string/account"
android:nextFocusLeft="@id/home_change_api" android:nextFocusLeft="@id/home_preview_search_button"
android:nextFocusRight="@id/home_switch_account"
android:nextFocusDown="@id/home_change_api"
android:padding="10dp" android:padding="10dp"
android:src="@drawable/ic_outline_account_circle_24" android:src="@drawable/ic_outline_account_circle_24"

View file

@ -99,7 +99,7 @@
android:id="@+id/search_autofit_results" android:id="@+id/search_autofit_results"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="@dimen/navbar_width"
android:background="?attr/primaryBlackBackground" android:background="?attr/primaryBlackBackground"
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"