3
3
Fork 1
mirror of https://github.com/recloudstream/cloudstream.git synced 2024-08-15 01:53:11 +00:00

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
targetSdk = 33
versionCode = 60
versionName = "4.1.9"
versionCode = 62
versionName = "4.2.0"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
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_tv,
R.id.navigation_player,
R.id.navigation_quick_search,
).contains(destination.id)
binding?.navHostFragment?.apply {
@ -1101,15 +1102,19 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
updateTv()
// 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 lastAppAutoBackup: String = getKey("VERSION_NAME") ?: ""
if (appVer != lastAppAutoBackup) {
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

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> {
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
// Otherwise you might be able to go to the next item without being at the info button
homePreviewInfoBtt.setOnFocusChangeListener { _, hasFocus ->

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall
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.ExtractorUri
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class PlayerGeneratorViewModel : ViewModel() {
companion object {
@ -38,6 +40,11 @@ class PlayerGeneratorViewModel : ViewModel() {
private val _currentSubtitleYear = MutableLiveData<Int?>(null)
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?) {
_currentSubtitleYear.postValue(year)
}
@ -72,18 +79,32 @@ class PlayerGeneratorViewModel : ViewModel() {
}
fun preLoadNextLinks() {
val id = getId()
// Do not preload if already loading
if (id == currentLoadingEpisodeId) return
Log.i(TAG, "preLoadNextLinks")
currentJob?.cancel()
currentJob = viewModelScope.launchSafe {
if (generator?.hasCache == true && generator?.hasNext() == true) {
safeApiCall {
generator?.generateLinks(
type = LoadType.InApp,
clearCache = false,
callback = {},
subtitleCallback = {},
offset = 1
)
currentLoadingEpisodeId = id
currentJob = viewModelScope.launch {
try {
if (generator?.hasCache == true && generator?.hasNext() == true) {
safeApiCall {
generator?.generateLinks(
type = LoadType.InApp,
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
_loadingLinks.postValue(Resource.Loading())
val loadingState = safeApiCall {
generator?.generateLinks(type = type,clearCache = clearCache, callback = {
generator?.generateLinks(type = type, clearCache = clearCache, callback = {
currentLinks.add(it)
// Clone to prevent ConcurrentModificationException
normalSafeApiCall {
// Extra normalSafeApiCall since .toSet() iterates.
_currentLinks.postValue(currentLinks.toSet())
}
}, subtitleCallback = {
}, subtitleCallback = {
currentSubs.add(it)
normalSafeApiCall {
_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.SearchHelper
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.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -269,6 +270,10 @@ class QuickSearchFragment : Fragment() {
activity?.popCurrentPage()
}
if (isTrueTvSettings()) {
binding?.quickSearch?.requestFocus()
}
arguments?.getString(AUTOSEARCH_KEY)?.let {
binding?.quickSearch?.setQuery(it, true)
arguments?.remove(AUTOSEARCH_KEY)

View file

@ -3,6 +3,8 @@ package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
@ -12,7 +14,10 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.CastItemBinding
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(
var isInverted: Boolean,
val actor: ActorData,
@ -22,7 +27,8 @@ class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerV
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
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(
val binding: CastItemBinding,
private val focusCallback : (View?) -> Unit = {}
private val focusCallback: (View?) -> Unit = {}
) :
RecyclerView.ViewHolder(binding.root) {
@ -78,8 +84,18 @@ class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerV
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 ->
if(hasFocus) {
if (hasFocus) {
focusCallback(v)
}
}

View file

@ -114,10 +114,20 @@ class ResultFragmentTv : Fragment() {
}
}
private fun hasNoFocus(): Boolean {
val focus = activity?.currentFocus
if (focus == null || !focus.isVisible) return true
return focus == binding?.resultRoot
// private fun hasNoFocus(): Boolean {
// val focus = activity?.currentFocus
// if (focus == null || !focus.isVisible) return true
// 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?) {
@ -413,7 +423,13 @@ class ResultFragmentTv : Fragment() {
setHorizontal()
}
resultCastItems.adapter = ActorAdaptor {
val aboveCast = listOf(
binding?.resultEpisodesShow,
binding?.resultBookmarkButton,
).firstOrNull {
it?.isVisible == true
}
resultCastItems.adapter = ActorAdaptor(aboveCast?.id) {
toggleEpisodes(false)
}
}
@ -454,9 +470,7 @@ class ResultFragmentTv : Fragment() {
resultPlaySeries.isVisible = false
resultResumeSeries.isVisible = true
if (hasNoFocus()) {
resultResumeSeries.requestFocus()
}
focusPlayButton()
resultResumeSeries.text =
if (resume.isMovie) context?.getString(R.string.play_movie_button) else context?.getNameFull(
@ -539,9 +553,7 @@ class ResultFragmentTv : Fragment() {
)
return@setOnLongClickListener true
}
if (hasNoFocus()) {
resultPlayMovie.requestFocus()
}
focusPlayButton()
}
}
}
@ -636,6 +648,9 @@ class ResultFragmentTv : Fragment() {
.show()
}
}
// Used to request focus the first time the episodes are loaded.
var hasLoadedEpisodesOnce = false
observeNullable(viewModel.episodes) { episodes ->
binding?.apply {
resultEpisodes.isVisible = episodes is Resource.Success
@ -663,6 +678,10 @@ class ResultFragmentTv : Fragment() {
)
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 {
versionRegex.find(it.name)?.groupValues?.get(2)
}).toList().lastOrNull()*/
val found =
val foundList =
response.filter { rel ->
!rel.prerelease
}.sortedWith(compareBy { release ->
release.assets.filter { it.content_type == "application/vnd.android.package-archive" }
.getOrNull(0)?.name?.let { it1 ->
release.assets.firstOrNull { it.content_type == "application/vnd.android.package-archive" }?.name?.let { it1 ->
versionRegex.find(
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 currentVersion = packageName?.let {
packageManager.getPackageInfo(

View file

@ -114,6 +114,22 @@
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
android:id="@+id/home_switch_account"

View file

@ -45,9 +45,24 @@
android:background="@drawable/player_button_tv_attr_no_bg"
android:gravity="center_vertical"
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" />
<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
android:id="@+id/home_preview_switch_account"
android:layout_width="wrap_content"
@ -55,7 +70,8 @@
android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg"
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:padding="10dp"

View file

@ -113,7 +113,25 @@
android:background="@drawable/player_button_tv_attr_no_bg"
android:gravity="center_vertical"
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
android:id="@+id/home_switch_account"
@ -122,7 +140,9 @@
android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg"
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:src="@drawable/ic_outline_account_circle_24"

View file

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