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
versionCode 50
versionName "3.1.2"
versionName "3.1.3"
resValue "string", "app_version",
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"

View file

@ -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()

View file

@ -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> {

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.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,16 +364,21 @@ object PluginManager {
internalName: String,
repositoryUrl: String
): Boolean {
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
val fileName = getPluginSanitizedFileName(internalName)
Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName")
// The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names
val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
return loadPlugin(
activity,
file ?: return false,
PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET)
)
try {
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
val fileName = getPluginSanitizedFileName(internalName)
Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName")
// The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names
val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
return loadPlugin(
activity,
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.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) }

View file

@ -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(

View file

@ -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)

View file

@ -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)
}

View file

@ -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>()

View file

@ -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,

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.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 {
FillerEpisodeCheck.getFillerEpisodes(name)
} catch (e: Exception) {
logError(e)
null
}
ioWorkSafe {
FillerEpisodeCheck.getFillerEpisodes(name)
} ?: 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 -> {

View file

@ -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) {

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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())

View file

@ -266,11 +266,11 @@ object AppUtils {
private fun openWebView(fragment: Fragment?, url: String) {
if (fragment?.context?.hasWebView() == true)
normalSafeApiCall {
fragment
.findNavController()
.navigate(R.id.navigation_webview, WebviewFragment.newInstance(url))
}
normalSafeApiCall {
fragment
.findNavController()
.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.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
}
}
}

View file

@ -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>