forked from recloudstream/cloudstream
fixed crashes
This commit is contained in:
parent
e9e4298a7a
commit
0fcb7b8db5
18 changed files with 141 additions and 106 deletions
|
@ -36,7 +36,7 @@ android {
|
|||
targetSdkVersion 30
|
||||
|
||||
versionCode 50
|
||||
versionName "3.1.2"
|
||||
versionName "3.1.3"
|
||||
|
||||
resValue "string", "app_version",
|
||||
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"
|
||||
|
|
|
@ -71,10 +71,6 @@ object APIHolder {
|
|||
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? {
|
||||
if (apiName == null) return null
|
||||
initMap()
|
||||
|
|
|
@ -6,11 +6,12 @@ import androidx.lifecycle.LiveData
|
|||
import com.bumptech.glide.load.HttpException
|
||||
import com.lagradost.cloudstream3.BuildConfig
|
||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import javax.net.ssl.SSLHandshakeException
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
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) } }
|
||||
}
|
||||
|
||||
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> {
|
||||
return if (value == null) {
|
||||
Some.None
|
||||
|
@ -64,7 +58,7 @@ sealed class Some<out T> {
|
|||
object None : Some<Nothing>()
|
||||
|
||||
override fun toString(): String {
|
||||
return when(this) {
|
||||
return when (this) {
|
||||
is None -> "None"
|
||||
is Success -> "Some(${value.toString()})"
|
||||
}
|
||||
|
@ -125,6 +119,22 @@ fun <T> safeFail(throwable: Throwable): Resource<T> {
|
|||
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(
|
||||
apiCall: suspend () -> T,
|
||||
): Resource<T> {
|
||||
|
|
|
@ -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.utils.VideoDownloadManager.sanitizeFilename
|
||||
import com.lagradost.cloudstream3.APIHolder.removePluginMapping
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.extractorApis
|
||||
|
@ -335,7 +336,7 @@ object PluginManager {
|
|||
}
|
||||
|
||||
// remove all registered apis
|
||||
APIHolder.apis.filter { it -> it.sourcePlugin == plugin.__filename }.forEach {
|
||||
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
|
||||
removePluginMapping(it)
|
||||
}
|
||||
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
|
||||
|
@ -363,6 +364,7 @@ object PluginManager {
|
|||
internalName: String,
|
||||
repositoryUrl: String
|
||||
): Boolean {
|
||||
try {
|
||||
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
|
||||
val fileName = getPluginSanitizedFileName(internalName)
|
||||
Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName")
|
||||
|
@ -373,6 +375,10 @@ object PluginManager {
|
|||
file ?: return false,
|
||||
PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET)
|
||||
)
|
||||
} catch (e : Exception) {
|
||||
logError(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.isMovieType
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||
|
@ -38,7 +39,7 @@ class DownloadViewModel : ViewModel() {
|
|||
val availableBytes: LiveData<Long> = _availableBytes
|
||||
val downloadBytes: LiveData<Long> = _downloadBytes
|
||||
|
||||
fun updateList(context: Context) = viewModelScope.launch {
|
||||
fun updateList(context: Context) = viewModelScope.launchSafe {
|
||||
val children = withContext(Dispatchers.IO) {
|
||||
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
||||
headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
|
||||
|
|
|
@ -229,7 +229,7 @@ class EasyDownloadButton : IDisposable {
|
|||
downloadStatusEventListener?.let { VideoDownloadManager.downloadStatusEvent += it }
|
||||
|
||||
downloadView.setOnClickListener {
|
||||
if (currentBytes <= 0) {
|
||||
if (currentBytes <= 0 || totalBytes <= 0) {
|
||||
_clickCallback?.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data))
|
||||
} else {
|
||||
val list = arrayListOf(
|
||||
|
|
|
@ -15,10 +15,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
|||
import com.lagradost.cloudstream3.HomePageList
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||
import com.lagradost.cloudstream3.mvvm.debugWarning
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||
|
@ -63,7 +60,7 @@ class HomeViewModel : ViewModel() {
|
|||
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
||||
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
||||
|
||||
fun loadResumeWatching() = viewModelScope.launch {
|
||||
fun loadResumeWatching() = viewModelScope.launchSafe {
|
||||
val resumeWatching = withContext(Dispatchers.IO) {
|
||||
getAllResumeStateIds()?.mapNotNull { 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) {
|
||||
getAllWatchStateIds()?.map { id ->
|
||||
Pair(id, getResultWatchState(id))
|
||||
}
|
||||
}?.distinctBy { it.first } ?: return@launch
|
||||
}?.distinctBy { it.first } ?: return@launchSafe
|
||||
|
||||
val length = WatchType.values().size
|
||||
val currentWatchTypes = EnumSet.noneOf(WatchType::class.java)
|
||||
|
@ -120,7 +117,7 @@ class HomeViewModel : ViewModel() {
|
|||
|
||||
if (currentWatchTypes.size <= 0) {
|
||||
_bookmarks.postValue(Pair(false, ArrayList()))
|
||||
return@launch
|
||||
return@launchSafe
|
||||
}
|
||||
|
||||
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:
|
||||
fun expand(name: String) = viewModelScope.launch {
|
||||
fun expand(name: String) = viewModelScope.launchSafe {
|
||||
expandAndReturn(name)
|
||||
}
|
||||
|
||||
private fun load(api: MainAPI?) = viewModelScope.launch {
|
||||
private fun load(api: MainAPI?) = viewModelScope.launchSafe {
|
||||
repo = if (api != null) {
|
||||
APIRepository(api)
|
||||
} else {
|
||||
|
@ -267,7 +264,7 @@ class HomeViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch {
|
||||
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launchSafe {
|
||||
val api = getApiFromNameNull(preferredApiName)
|
||||
if (preferredApiName == noneApi.name){
|
||||
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
|
||||
|
|
|
@ -734,6 +734,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
||||
|
||||
val (position, duration) = posDur
|
||||
if(duration == 0L) return // idk how you achieved this, but div by zero crash
|
||||
|
||||
viewModel.getId()?.let {
|
||||
DataStoreHelper.setViewPos(it, position, duration)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
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.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
@ -61,7 +62,7 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
fun preLoadNextLinks() {
|
||||
Log.i(TAG, "preLoadNextLinks")
|
||||
currentJob?.cancel()
|
||||
currentJob = viewModelScope.launch {
|
||||
currentJob = viewModelScope.launchSafe {
|
||||
if (generator?.hasCache == true && generator?.hasNext() == true) {
|
||||
safeApiCall {
|
||||
generator?.generateLinks(
|
||||
|
@ -116,7 +117,7 @@ class PlayerGeneratorViewModel : ViewModel() {
|
|||
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) {
|
||||
Log.i(TAG, "loadLinks")
|
||||
currentJob?.cancel()
|
||||
currentJob = viewModelScope.launch {
|
||||
currentJob = viewModelScope.launchSafe {
|
||||
val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>()
|
||||
val currentSubs = mutableSetOf<SubtitleData>()
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ import android.text.Editable
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
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.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -19,14 +21,10 @@ import androidx.core.widget.doOnTextChanged
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
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.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.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.R
|
||||
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.AppUtils.getNameFull
|
||||
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.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.DataStoreHelper.getViewPos
|
||||
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_swipe.*
|
||||
import kotlinx.android.synthetic.main.fragment_result_tv.*
|
||||
import kotlinx.android.synthetic.main.fragment_trailer.*
|
||||
import kotlinx.android.synthetic.main.result_sync.*
|
||||
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
|
||||
|
@ -346,7 +338,7 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
main {
|
||||
val file =
|
||||
ioWork {
|
||||
ioWorkSafe {
|
||||
context?.let {
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||
it,
|
||||
|
@ -360,11 +352,11 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
downloadButton?.setUpMoreButton(
|
||||
file?.fileLength,
|
||||
file?.totalBytes,
|
||||
result_movie_progress_downloaded,
|
||||
result_movie_download_icon,
|
||||
result_movie_download_text,
|
||||
result_movie_download_text_precentage,
|
||||
result_download_movie,
|
||||
result_movie_progress_downloaded ?: return@main,
|
||||
result_movie_download_icon ?: return@main,
|
||||
result_movie_download_text ?: return@main,
|
||||
result_movie_download_text_precentage ?: return@main,
|
||||
result_download_movie ?: return@main,
|
||||
true,
|
||||
VideoDownloadHelper.DownloadEpisodeCached(
|
||||
ep.name,
|
||||
|
|
|
@ -41,6 +41,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
|||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
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.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
|
||||
|
@ -831,7 +832,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
text,
|
||||
options
|
||||
) { value ->
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launchSafe {
|
||||
_selectPopup.postValue(Some.None)
|
||||
callback.invoke(value)
|
||||
}
|
||||
|
@ -850,7 +851,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
text,
|
||||
options,
|
||||
) { value ->
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launchSafe {
|
||||
_selectPopup.value = Some.None
|
||||
callback.invoke(value)
|
||||
}
|
||||
|
@ -858,7 +859,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
}
|
||||
|
||||
fun loadLinks(
|
||||
private fun loadLinks(
|
||||
result: ResultEpisode,
|
||||
isVisible: Boolean,
|
||||
isCasting: Boolean,
|
||||
|
@ -910,7 +911,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun CoroutineScope.loadLinks(
|
||||
private suspend fun CoroutineScope.loadLinks(
|
||||
result: ResultEpisode,
|
||||
isVisible: 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)
|
||||
}
|
||||
|
||||
|
@ -1314,7 +1315,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
return
|
||||
}
|
||||
Log.i(TAG, "setMeta")
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launchSafe {
|
||||
currentMeta = meta
|
||||
currentSync = syncs
|
||||
val (value, updateEpisodes) = ioWork {
|
||||
|
@ -1325,9 +1326,9 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
|
||||
postSuccessful(
|
||||
value ?: return@launch,
|
||||
currentRepo ?: return@launch,
|
||||
updateEpisodes ?: return@launch,
|
||||
value ?: return@launchSafe,
|
||||
currentRepo ?: return@launchSafe,
|
||||
updateEpisodes ?: return@launchSafe,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
@ -1336,13 +1337,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
|
||||
private suspend fun updateFillers(name: String) {
|
||||
fillers =
|
||||
ioWork {
|
||||
try {
|
||||
ioWorkSafe {
|
||||
FillerEpisodeCheck.getFillerEpisodes(name)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
null
|
||||
}
|
||||
} ?: emptyMap()
|
||||
}
|
||||
|
||||
|
@ -1799,8 +1795,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
fun hasLoaded() = currentResponse != null
|
||||
|
||||
private fun handleAutoStart(activity: Activity?, autostart: AutoResume?) =
|
||||
viewModelScope.launch {
|
||||
if (autostart == null || activity == null) return@launch
|
||||
viewModelScope.launchSafe {
|
||||
if (autostart == null || activity == null) return@launchSafe
|
||||
|
||||
when (autostart.startAction) {
|
||||
START_ACTION_RESUME_LATEST -> {
|
||||
|
@ -1823,7 +1819,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
currentEpisodes[currentIndex]?.firstOrNull { it.episode == ep && it.season == autostart.episode }
|
||||
?: all.firstOrNull { it.episode == ep && it.season == autostart.episode }
|
||||
}
|
||||
?: return@launch
|
||||
?: return@launchSafe
|
||||
handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, episode)
|
||||
|
@ -1840,7 +1836,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
dubStatus: DubStatus,
|
||||
autostart: AutoResume?,
|
||||
) =
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launchSafe {
|
||||
_page.postValue(Resource.Loading(url))
|
||||
_episodes.postValue(ResourceSome.Loading())
|
||||
|
||||
|
@ -1858,12 +1854,12 @@ class ResultViewModel2 : ViewModel() {
|
|||
"This provider does not exist"
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
return@launchSafe
|
||||
}
|
||||
|
||||
|
||||
// validate url
|
||||
var validUrlResource = safeApiCall {
|
||||
val validUrlResource = safeApiCall {
|
||||
SyncRedirector.redirect(
|
||||
url,
|
||||
api.mainUrl
|
||||
|
@ -1882,7 +1878,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
_page.postValue(validUrlResource)
|
||||
}
|
||||
|
||||
return@launch
|
||||
return@launchSafe
|
||||
}
|
||||
val validUrl = validUrlResource.value
|
||||
val repo = APIRepository(api)
|
||||
|
@ -1893,11 +1889,11 @@ class ResultViewModel2 : ViewModel() {
|
|||
_page.postValue(data)
|
||||
}
|
||||
is Resource.Success -> {
|
||||
if (!isActive) return@launch
|
||||
if (!isActive) return@launchSafe
|
||||
val loadResponse = ioWork {
|
||||
applyMeta(data.value, currentMeta, currentSync).first
|
||||
}
|
||||
if (!isActive) return@launch
|
||||
if (!isActive) return@launchSafe
|
||||
val mainId = loadResponse.getId()
|
||||
|
||||
preferDubStatus = getDub(mainId) ?: preferDubStatus
|
||||
|
@ -1924,7 +1920,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
updateFillers = showFillers,
|
||||
apiRepository = repo
|
||||
)
|
||||
if (!isActive) return@launch
|
||||
if (!isActive) return@launchSafe
|
||||
handleAutoStart(activity, autostart)
|
||||
}
|
||||
is Resource.Loading -> {
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
|||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -76,11 +77,11 @@ class SearchViewModel : ViewModel() {
|
|||
ignoreSettings: Boolean = false,
|
||||
isQuickSearch: Boolean = false,
|
||||
) =
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launchSafe {
|
||||
val currentIndex = currentSearchIndex
|
||||
if (query.length <= 1) {
|
||||
clearSearch()
|
||||
return@launch
|
||||
return@launchSafe
|
||||
}
|
||||
|
||||
if (!isQuickSearch) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
|
@ -45,7 +46,7 @@ class ExtensionsViewModel : ViewModel() {
|
|||
val pluginStats: LiveData<Some<PluginStats>> = _pluginStats
|
||||
|
||||
//TODO CACHE GET REQUESTS
|
||||
fun loadStats() = viewModelScope.launch {
|
||||
fun loadStats() = viewModelScope.launchSafe {
|
||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||
|
||||
|
|
|
@ -4,15 +4,17 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
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.UIHelper.setImage
|
||||
import kotlinx.android.synthetic.main.repository_item.view.*
|
||||
|
||||
|
||||
|
@ -93,6 +95,15 @@ class PluginAdapter(
|
|||
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) {
|
||||
val plugin = PluginManager.urlPlugins[metadata.url]
|
||||
if (plugin?.openSettings != null) {
|
||||
|
@ -117,7 +128,12 @@ class PluginAdapter(
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -132,7 +148,8 @@ class PluginAdapter(
|
|||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.plugins.PluginData
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
|
@ -199,7 +200,7 @@ class PluginsViewModel : ViewModel() {
|
|||
_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")
|
||||
updatePluginListPrivate(repositoryUrl)
|
||||
}
|
||||
|
@ -212,7 +213,7 @@ class PluginsViewModel : ViewModel() {
|
|||
/**
|
||||
* 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")
|
||||
|
||||
val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal())
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils
|
|||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -10,7 +11,7 @@ import kotlinx.coroutines.*
|
|||
object Coroutines {
|
||||
fun <T> T.main(work: suspend ((T) -> Unit)): Job {
|
||||
val value = this
|
||||
return CoroutineScope(Dispatchers.Main).launch {
|
||||
return CoroutineScope(Dispatchers.Main).launchSafe {
|
||||
work(value)
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +19,19 @@ object Coroutines {
|
|||
fun <T> T.ioSafe(work: suspend (CoroutineScope.(T) -> Unit)): Job {
|
||||
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 {
|
||||
work(value)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:nextFocusRight="@id/action_button"
|
||||
android:background="@drawable/outline_drawable"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="20dp">
|
||||
|
||||
<ImageView
|
||||
|
@ -16,16 +16,18 @@
|
|||
android:layout_height="24dp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:scaleType="centerInside"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_github_logo" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
@ -78,6 +80,7 @@
|
|||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:id="@+id/action_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -85,11 +88,12 @@
|
|||
android:layout_marginStart="10dp"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_baseline_tune_24"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="visible"
|
||||
android:contentDescription="@string/title_settings" />
|
||||
|
||||
<ImageView
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/outline_drawable"
|
||||
android:nextFocusLeft="@id/repository_item_root"
|
||||
android:clickable="true"
|
||||
android:id="@+id/action_button"
|
||||
|
@ -98,6 +102,7 @@
|
|||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_marginStart="10dp"
|
||||
tools:src="@drawable/ic_baseline_add_24"
|
||||
android:focusable="true" />
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/download" />
|
||||
|
||||
</LinearLayout>
|
Loading…
Reference in a new issue