fixed crashes

This commit is contained in:
reduplicated 2022-08-18 02:54:05 +02:00
parent e9e4298a7a
commit 0fcb7b8db5
18 changed files with 141 additions and 106 deletions

View file

@ -36,7 +36,7 @@ android {
targetSdkVersion 30 targetSdkVersion 30
versionCode 50 versionCode 50
versionName "3.1.2" versionName "3.1.3"
resValue "string", "app_version", resValue "string", "app_version",
"${defaultConfig.versionName}${versionNameSuffix ?: ""}" "${defaultConfig.versionName}${versionNameSuffix ?: ""}"

View file

@ -71,10 +71,6 @@ object APIHolder {
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap() apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
} }
fun getApiFromName(apiName: String?): MainAPI {
return getApiFromNameNull(apiName) ?: apis[defProvider]
}
fun getApiFromNameNull(apiName: String?): MainAPI? { fun getApiFromNameNull(apiName: String?): MainAPI? {
if (apiName == null) return null if (apiName == null) return null
initMap() initMap()

View file

@ -6,11 +6,12 @@ import androidx.lifecycle.LiveData
import com.bumptech.glide.load.HttpException import com.bumptech.glide.load.HttpException
import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.ErrorLoadingException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.withContext
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.net.ssl.SSLHandshakeException import javax.net.ssl.SSLHandshakeException
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!" const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!"
@ -44,13 +45,6 @@ fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this) { it?.let { t -> action(t) } } liveData.observe(this) { it?.let { t -> action(t) } }
} }
fun <T> LifecycleOwner.observeDirectly(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this) { it?.let { t -> action(t) } }
val currentValue = liveData.value
if (currentValue != null)
action(currentValue)
}
inline fun <reified T : Any> some(value: T?): Some<T> { inline fun <reified T : Any> some(value: T?): Some<T> {
return if (value == null) { return if (value == null) {
Some.None Some.None
@ -64,7 +58,7 @@ sealed class Some<out T> {
object None : Some<Nothing>() object None : Some<Nothing>()
override fun toString(): String { override fun toString(): String {
return when(this) { return when (this) {
is None -> "None" is None -> "None"
is Success -> "Some(${value.toString()})" is Success -> "Some(${value.toString()})"
} }
@ -125,6 +119,22 @@ fun <T> safeFail(throwable: Throwable): Resource<T> {
return Resource.Failure(false, null, null, stackTraceMsg) return Resource.Failure(false, null, null, stackTraceMsg)
} }
fun CoroutineScope.launchSafe(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val obj: suspend CoroutineScope.() -> Unit = {
try {
block()
} catch (e: Exception) {
logError(e)
}
}
return this.launch(context, start, obj)
}
suspend fun <T> safeApiCall( suspend fun <T> safeApiCall(
apiCall: suspend () -> T, apiCall: suspend () -> T,
): Resource<T> { ): Resource<T> {

View file

@ -21,6 +21,7 @@ import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.APIHolder.removePluginMapping import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.extractorApis
@ -335,7 +336,7 @@ object PluginManager {
} }
// remove all registered apis // remove all registered apis
APIHolder.apis.filter { it -> it.sourcePlugin == plugin.__filename }.forEach { APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
removePluginMapping(it) removePluginMapping(it)
} }
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename } APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
@ -363,16 +364,21 @@ object PluginManager {
internalName: String, internalName: String,
repositoryUrl: String repositoryUrl: String
): Boolean { ): Boolean {
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique try {
val fileName = getPluginSanitizedFileName(internalName) val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") val fileName = getPluginSanitizedFileName(internalName)
// The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName")
val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName) // The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names
return loadPlugin( val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
activity, return loadPlugin(
file ?: return false, activity,
PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET) file ?: return false,
) PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET)
)
} catch (e : Exception) {
logError(e)
return false
}
} }
/** /**

View file

@ -8,6 +8,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
@ -38,7 +39,7 @@ class DownloadViewModel : ViewModel() {
val availableBytes: LiveData<Long> = _availableBytes val availableBytes: LiveData<Long> = _availableBytes
val downloadBytes: LiveData<Long> = _downloadBytes val downloadBytes: LiveData<Long> = _downloadBytes
fun updateList(context: Context) = viewModelScope.launch { fun updateList(context: Context) = viewModelScope.launchSafe {
val children = withContext(Dispatchers.IO) { val children = withContext(Dispatchers.IO) {
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE) val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) } headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }

View file

@ -229,7 +229,7 @@ class EasyDownloadButton : IDisposable {
downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent += it } downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent += it }
downloadView.setOnClickListener { downloadView.setOnClickListener {
if (currentBytes <= 0) { if (currentBytes <= 0 || totalBytes <= 0) {
_clickCallback?.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data)) _clickCallback?.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data))
} else { } else {
val list = arrayListOf( val list = arrayListOf(

View file

@ -15,10 +15,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.mvvm.debugWarning
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
@ -63,7 +60,7 @@ class HomeViewModel : ViewModel() {
private val _resumeWatching = MutableLiveData<List<SearchResponse>>() private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
fun loadResumeWatching() = viewModelScope.launch { fun loadResumeWatching() = viewModelScope.launchSafe {
val resumeWatching = withContext(Dispatchers.IO) { val resumeWatching = withContext(Dispatchers.IO) {
getAllResumeStateIds()?.mapNotNull { id -> getAllResumeStateIds()?.mapNotNull { id ->
getLastWatched(id) getLastWatched(id)
@ -99,12 +96,12 @@ class HomeViewModel : ViewModel() {
} }
} }
fun loadStoredData(preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launch { fun loadStoredData(preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launchSafe {
val watchStatusIds = withContext(Dispatchers.IO) { val watchStatusIds = withContext(Dispatchers.IO) {
getAllWatchStateIds()?.map { id -> getAllWatchStateIds()?.map { id ->
Pair(id, getResultWatchState(id)) Pair(id, getResultWatchState(id))
} }
}?.distinctBy { it.first } ?: return@launch }?.distinctBy { it.first } ?: return@launchSafe
val length = WatchType.values().size val length = WatchType.values().size
val currentWatchTypes = EnumSet.noneOf(WatchType::class.java) val currentWatchTypes = EnumSet.noneOf(WatchType::class.java)
@ -120,7 +117,7 @@ class HomeViewModel : ViewModel() {
if (currentWatchTypes.size <= 0) { if (currentWatchTypes.size <= 0) {
_bookmarks.postValue(Pair(false, ArrayList())) _bookmarks.postValue(Pair(false, ArrayList()))
return@launch return@launchSafe
} }
val watchPrefNotNull = preferredWatchStatus ?: EnumSet.of(currentWatchTypes.first()) val watchPrefNotNull = preferredWatchStatus ?: EnumSet.of(currentWatchTypes.first())
@ -204,11 +201,11 @@ class HomeViewModel : ViewModel() {
} }
// this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive: // this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive:
fun expand(name: String) = viewModelScope.launch { fun expand(name: String) = viewModelScope.launchSafe {
expandAndReturn(name) expandAndReturn(name)
} }
private fun load(api: MainAPI?) = viewModelScope.launch { private fun load(api: MainAPI?) = viewModelScope.launchSafe {
repo = if (api != null) { repo = if (api != null) {
APIRepository(api) APIRepository(api)
} else { } else {
@ -267,7 +264,7 @@ class HomeViewModel : ViewModel() {
} }
} }
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch { fun loadAndCancel(preferredApiName: String?) = viewModelScope.launchSafe {
val api = getApiFromNameNull(preferredApiName) val api = getApiFromNameNull(preferredApiName)
if (preferredApiName == noneApi.name){ if (preferredApiName == noneApi.name){
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)

View file

@ -734,6 +734,8 @@ class GeneratorPlayer : FullScreenPlayer() {
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
val (position, duration) = posDur val (position, duration) = posDur
if(duration == 0L) return // idk how you achieved this, but div by zero crash
viewModel.getId()?.let { viewModel.getId()?.let {
DataStoreHelper.setViewPos(it, position, duration) DataStoreHelper.setViewPos(it, position, duration)
} }

View file

@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel 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.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -61,7 +62,7 @@ class PlayerGeneratorViewModel : ViewModel() {
fun preLoadNextLinks() { fun preLoadNextLinks() {
Log.i(TAG, "preLoadNextLinks") Log.i(TAG, "preLoadNextLinks")
currentJob?.cancel() currentJob?.cancel()
currentJob = viewModelScope.launch { currentJob = viewModelScope.launchSafe {
if (generator?.hasCache == true && generator?.hasNext() == true) { if (generator?.hasCache == true && generator?.hasNext() == true) {
safeApiCall { safeApiCall {
generator?.generateLinks( generator?.generateLinks(
@ -116,7 +117,7 @@ class PlayerGeneratorViewModel : ViewModel() {
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) { fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) {
Log.i(TAG, "loadLinks") Log.i(TAG, "loadLinks")
currentJob?.cancel() currentJob?.cancel()
currentJob = viewModelScope.launch { currentJob = viewModelScope.launchSafe {
val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>() val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>()
val currentSubs = mutableSetOf<SubtitleData>() val currentSubs = mutableSetOf<SubtitleData>()

View file

@ -11,7 +11,9 @@ import android.text.Editable
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 android.widget.* import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -19,14 +21,10 @@ import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.discord.panels.OverlappingPanelsLayout import com.discord.panels.OverlappingPanelsLayout
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
@ -43,10 +41,9 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
@ -95,13 +92,8 @@ import kotlinx.android.synthetic.main.fragment_result.result_title
import kotlinx.android.synthetic.main.fragment_result.result_vpn import kotlinx.android.synthetic.main.fragment_result.result_vpn
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.* import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.result_sync.* import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import android.widget.EditText
import android.widget.AbsListView
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_RESUME_LATEST = 1
@ -346,7 +338,7 @@ open class ResultFragment : ResultTrailerPlayer() {
main { main {
val file = val file =
ioWork { ioWorkSafe {
context?.let { context?.let {
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings( VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
it, it,
@ -360,11 +352,11 @@ open class ResultFragment : ResultTrailerPlayer() {
downloadButton?.setUpMoreButton( downloadButton?.setUpMoreButton(
file?.fileLength, file?.fileLength,
file?.totalBytes, file?.totalBytes,
result_movie_progress_downloaded, result_movie_progress_downloaded ?: return@main,
result_movie_download_icon, result_movie_download_icon ?: return@main,
result_movie_download_text, result_movie_download_text ?: return@main,
result_movie_download_text_precentage, result_movie_download_text_precentage ?: return@main,
result_download_movie, result_download_movie ?: return@main,
true, true,
VideoDownloadHelper.DownloadEpisodeCached( VideoDownloadHelper.DownloadEpisodeCached(
ep.name, ep.name,

View file

@ -41,6 +41,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.CastHelper.startCast import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
@ -831,7 +832,7 @@ class ResultViewModel2 : ViewModel() {
text, text,
options options
) { value -> ) { value ->
viewModelScope.launch { viewModelScope.launchSafe {
_selectPopup.postValue(Some.None) _selectPopup.postValue(Some.None)
callback.invoke(value) callback.invoke(value)
} }
@ -850,7 +851,7 @@ class ResultViewModel2 : ViewModel() {
text, text,
options, options,
) { value -> ) { value ->
viewModelScope.launch { viewModelScope.launchSafe {
_selectPopup.value = Some.None _selectPopup.value = Some.None
callback.invoke(value) callback.invoke(value)
} }
@ -858,7 +859,7 @@ class ResultViewModel2 : ViewModel() {
) )
} }
fun loadLinks( private fun loadLinks(
result: ResultEpisode, result: ResultEpisode,
isVisible: Boolean, isVisible: Boolean,
isCasting: Boolean, isCasting: Boolean,
@ -910,7 +911,7 @@ class ResultViewModel2 : ViewModel() {
} }
} }
suspend fun CoroutineScope.loadLinks( private suspend fun CoroutineScope.loadLinks(
result: ResultEpisode, result: ResultEpisode,
isVisible: Boolean, isVisible: Boolean,
isCasting: Boolean, isCasting: Boolean,
@ -1006,7 +1007,7 @@ class ResultViewModel2 : ViewModel() {
} }
} }
fun handleAction(activity: Activity?, click: EpisodeClickEvent) = viewModelScope.launch { fun handleAction(activity: Activity?, click: EpisodeClickEvent) = viewModelScope.launchSafe {
handleEpisodeClickEvent(activity, click) handleEpisodeClickEvent(activity, click)
} }
@ -1314,7 +1315,7 @@ class ResultViewModel2 : ViewModel() {
return return
} }
Log.i(TAG, "setMeta") Log.i(TAG, "setMeta")
viewModelScope.launch { viewModelScope.launchSafe {
currentMeta = meta currentMeta = meta
currentSync = syncs currentSync = syncs
val (value, updateEpisodes) = ioWork { val (value, updateEpisodes) = ioWork {
@ -1325,9 +1326,9 @@ class ResultViewModel2 : ViewModel() {
} }
postSuccessful( postSuccessful(
value ?: return@launch, value ?: return@launchSafe,
currentRepo ?: return@launch, currentRepo ?: return@launchSafe,
updateEpisodes ?: return@launch, updateEpisodes ?: return@launchSafe,
false false
) )
} }
@ -1336,13 +1337,8 @@ class ResultViewModel2 : ViewModel() {
private suspend fun updateFillers(name: String) { private suspend fun updateFillers(name: String) {
fillers = fillers =
ioWork { ioWorkSafe {
try { FillerEpisodeCheck.getFillerEpisodes(name)
FillerEpisodeCheck.getFillerEpisodes(name)
} catch (e: Exception) {
logError(e)
null
}
} ?: emptyMap() } ?: emptyMap()
} }
@ -1799,8 +1795,8 @@ class ResultViewModel2 : ViewModel() {
fun hasLoaded() = currentResponse != null fun hasLoaded() = currentResponse != null
private fun handleAutoStart(activity: Activity?, autostart: AutoResume?) = private fun handleAutoStart(activity: Activity?, autostart: AutoResume?) =
viewModelScope.launch { viewModelScope.launchSafe {
if (autostart == null || activity == null) return@launch if (autostart == null || activity == null) return@launchSafe
when (autostart.startAction) { when (autostart.startAction) {
START_ACTION_RESUME_LATEST -> { START_ACTION_RESUME_LATEST -> {
@ -1823,7 +1819,7 @@ class ResultViewModel2 : ViewModel() {
currentEpisodes[currentIndex]?.firstOrNull { it.episode == ep && it.season == autostart.episode } currentEpisodes[currentIndex]?.firstOrNull { it.episode == ep && it.season == autostart.episode }
?: all.firstOrNull { it.episode == ep && it.season == autostart.episode } ?: all.firstOrNull { it.episode == ep && it.season == autostart.episode }
} }
?: return@launch ?: return@launchSafe
handleAction( handleAction(
activity, activity,
EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, episode) EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, episode)
@ -1840,7 +1836,7 @@ class ResultViewModel2 : ViewModel() {
dubStatus: DubStatus, dubStatus: DubStatus,
autostart: AutoResume?, autostart: AutoResume?,
) = ) =
viewModelScope.launch { viewModelScope.launchSafe {
_page.postValue(Resource.Loading(url)) _page.postValue(Resource.Loading(url))
_episodes.postValue(ResourceSome.Loading()) _episodes.postValue(ResourceSome.Loading())
@ -1858,12 +1854,12 @@ class ResultViewModel2 : ViewModel() {
"This provider does not exist" "This provider does not exist"
) )
) )
return@launch return@launchSafe
} }
// validate url // validate url
var validUrlResource = safeApiCall { val validUrlResource = safeApiCall {
SyncRedirector.redirect( SyncRedirector.redirect(
url, url,
api.mainUrl api.mainUrl
@ -1882,7 +1878,7 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(validUrlResource) _page.postValue(validUrlResource)
} }
return@launch return@launchSafe
} }
val validUrl = validUrlResource.value val validUrl = validUrlResource.value
val repo = APIRepository(api) val repo = APIRepository(api)
@ -1893,11 +1889,11 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(data) _page.postValue(data)
} }
is Resource.Success -> { is Resource.Success -> {
if (!isActive) return@launch if (!isActive) return@launchSafe
val loadResponse = ioWork { val loadResponse = ioWork {
applyMeta(data.value, currentMeta, currentSync).first applyMeta(data.value, currentMeta, currentSync).first
} }
if (!isActive) return@launch if (!isActive) return@launchSafe
val mainId = loadResponse.getId() val mainId = loadResponse.getId()
preferDubStatus = getDub(mainId) ?: preferDubStatus preferDubStatus = getDub(mainId) ?: preferDubStatus
@ -1924,7 +1920,7 @@ class ResultViewModel2 : ViewModel() {
updateFillers = showFillers, updateFillers = showFillers,
apiRepository = repo apiRepository = repo
) )
if (!isActive) return@launch if (!isActive) return@launchSafe
handleAutoStart(activity, autostart) handleAutoStart(activity, autostart)
} }
is Resource.Loading -> { is Resource.Loading -> {

View file

@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -76,11 +77,11 @@ class SearchViewModel : ViewModel() {
ignoreSettings: Boolean = false, ignoreSettings: Boolean = false,
isQuickSearch: Boolean = false, isQuickSearch: Boolean = false,
) = ) =
viewModelScope.launch { viewModelScope.launchSafe {
val currentIndex = currentSearchIndex val currentIndex = currentSearchIndex
if (query.length <= 1) { if (query.length <= 1) {
clearSearch() clearSearch()
return@launch return@launchSafe
} }
if (!isQuickSearch) { if (!isQuickSearch) {

View file

@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.mvvm.Some import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.debugAssert import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline
import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.RepositoryManager
@ -45,7 +46,7 @@ class ExtensionsViewModel : ViewModel() {
val pluginStats: LiveData<Some<PluginStats>> = _pluginStats val pluginStats: LiveData<Some<PluginStats>> = _pluginStats
//TODO CACHE GET REQUESTS //TODO CACHE GET REQUESTS
fun loadStats() = viewModelScope.launch { fun loadStats() = viewModelScope.launchSafe {
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY) val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES ?: emptyArray()) + PREBUILT_REPOSITORIES

View file

@ -4,15 +4,17 @@ import android.util.Log
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.core.view.isGone
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
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.GlideApp import com.lagradost.cloudstream3.utils.GlideApp
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.repository_item.view.* import kotlinx.android.synthetic.main.repository_item.view.*
@ -93,6 +95,15 @@ class PluginAdapter(
iconClickCallback.invoke(data.plugin) iconClickCallback.invoke(data.plugin)
} }
//if (itemView.context?.isTrueTvSettings() == false) {
// val siteUrl = metadata.repositoryUrl
// if (siteUrl != null && siteUrl.isNotBlank() && siteUrl != "NONE") {
// itemView.setOnClickListener {
// openBrowser(siteUrl)
// }
// }
//}
if (data.isDownloaded) { if (data.isDownloaded) {
val plugin = PluginManager.urlPlugins[metadata.url] val plugin = PluginManager.urlPlugins[metadata.url]
if (plugin?.openSettings != null) { if (plugin?.openSettings != null) {
@ -117,7 +128,12 @@ class PluginAdapter(
itemView.action_settings?.isVisible = false itemView.action_settings?.isVisible = false
} }
if (itemView.entry_icon?.setImage(metadata.iconUrl, null) != true) { if (itemView.entry_icon?.setImage(
metadata.iconUrl?.replace("&sz=24", "&sz=128"), // lazy fix for better resolution
null,
errorImageDrawable = R.drawable.ic_baseline_extension_24
) != true
) {
itemView.entry_icon?.setImageResource(R.drawable.ic_baseline_extension_24) itemView.entry_icon?.setImageResource(R.drawable.ic_baseline_extension_24)
} }
@ -132,7 +148,8 @@ class PluginAdapter(
} }
itemView.main_text?.text = metadata.name itemView.main_text?.text = metadata.name
itemView.sub_text?.text = metadata.description itemView.sub_text?.isGone = metadata.description.isNullOrBlank()
itemView.sub_text?.text = metadata.description.html()
} }
} }
} }

View file

@ -10,6 +10,7 @@ import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.plugins.PluginData import com.lagradost.cloudstream3.plugins.PluginData
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.RepositoryManager
@ -199,7 +200,7 @@ class PluginsViewModel : ViewModel() {
_filteredPlugins.postValue(false to plugins.filterTvTypes().sortByQuery(currentQuery)) _filteredPlugins.postValue(false to plugins.filterTvTypes().sortByQuery(currentQuery))
} }
fun updatePluginList(repositoryUrl: String) = viewModelScope.launch { fun updatePluginList(repositoryUrl: String) = viewModelScope.launchSafe {
Log.i(TAG, "updatePluginList = $repositoryUrl") Log.i(TAG, "updatePluginList = $repositoryUrl")
updatePluginListPrivate(repositoryUrl) updatePluginListPrivate(repositoryUrl)
} }
@ -212,7 +213,7 @@ class PluginsViewModel : ViewModel() {
/** /**
* Update the list but only with the local data. Used for file management. * Update the list but only with the local data. Used for file management.
* */ * */
fun updatePluginListLocal() = viewModelScope.launch { fun updatePluginListLocal() = viewModelScope.launchSafe {
Log.i(TAG, "updatePluginList = local") Log.i(TAG, "updatePluginList = local")
val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal()) val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal())

View file

@ -266,11 +266,11 @@ object AppUtils {
private fun openWebView(fragment: Fragment?, url: String) { private fun openWebView(fragment: Fragment?, url: String) {
if (fragment?.context?.hasWebView() == true) if (fragment?.context?.hasWebView() == true)
normalSafeApiCall { normalSafeApiCall {
fragment fragment
.findNavController() .findNavController()
.navigate(R.id.navigation_webview, WebviewFragment.newInstance(url)) .navigate(R.id.navigation_webview, WebviewFragment.newInstance(url))
} }
} }
/** /**

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
@ -10,7 +11,7 @@ import kotlinx.coroutines.*
object Coroutines { object Coroutines {
fun <T> T.main(work: suspend ((T) -> Unit)): Job { fun <T> T.main(work: suspend ((T) -> Unit)): Job {
val value = this val value = this
return CoroutineScope(Dispatchers.Main).launch { return CoroutineScope(Dispatchers.Main).launchSafe {
work(value) work(value)
} }
} }
@ -18,11 +19,19 @@ object Coroutines {
fun <T> T.ioSafe(work: suspend (CoroutineScope.(T) -> Unit)): Job { fun <T> T.ioSafe(work: suspend (CoroutineScope.(T) -> Unit)): Job {
val value = this val value = this
return CoroutineScope(Dispatchers.IO).launch { return CoroutineScope(Dispatchers.IO).launchSafe {
work(value)
}
}
suspend fun <T, V> V.ioWorkSafe(work: suspend (CoroutineScope.(V) -> T)): T? {
val value = this
return withContext(Dispatchers.IO) {
try { try {
work(value) work(value)
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
null
} }
} }
} }

View file

@ -7,7 +7,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:nextFocusRight="@id/action_button" android:nextFocusRight="@id/action_button"
android:background="@drawable/outline_drawable" android:background="?attr/selectableItemBackground"
android:padding="20dp"> android:padding="20dp">
<ImageView <ImageView
@ -16,16 +16,18 @@
android:layout_height="24dp" android:layout_height="24dp"
android:layout_gravity="start|center_vertical" android:layout_gravity="start|center_vertical"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:scaleType="centerInside" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_github_logo" /> app:srcCompat="@drawable/ic_github_logo" />
<LinearLayout <LinearLayout
android:layout_gravity="center_vertical"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:layout_gravity="center_vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal">
@ -78,6 +80,7 @@
tools:visibility="visible" /> tools:visibility="visible" />
<ImageView <ImageView
android:background="?attr/selectableItemBackgroundBorderless"
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -85,11 +88,12 @@
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:visibility="gone" android:visibility="gone"
app:srcCompat="@drawable/ic_baseline_tune_24" app:srcCompat="@drawable/ic_baseline_tune_24"
tools:visibility="visible" /> tools:visibility="visible"
android:contentDescription="@string/title_settings" />
<ImageView <ImageView
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="10dp" android:padding="10dp"
android:background="@drawable/outline_drawable"
android:nextFocusLeft="@id/repository_item_root" android:nextFocusLeft="@id/repository_item_root"
android:clickable="true" android:clickable="true"
android:id="@+id/action_button" android:id="@+id/action_button"
@ -98,6 +102,7 @@
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
tools:src="@drawable/ic_baseline_add_24" tools:src="@drawable/ic_baseline_add_24"
android:focusable="true" /> android:focusable="true"
android:contentDescription="@string/download" />
</LinearLayout> </LinearLayout>