diff --git a/app/build.gradle b/app/build.gradle index e8648fbc..b7226a49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,7 +36,7 @@ android { targetSdkVersion 30 versionCode 50 - versionName "3.1.2" + versionName "3.1.3" resValue "string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}" diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 97611dbf..6ef034ae 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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() diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index c831884f..df585cda 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -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 LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) { liveData.observe(this) { it?.let { t -> action(t) } } } -fun LifecycleOwner.observeDirectly(liveData: LiveData, action: (t: T) -> Unit) { - liveData.observe(this) { it?.let { t -> action(t) } } - val currentValue = liveData.value - if (currentValue != null) - action(currentValue) -} - inline fun some(value: T?): Some { return if (value == null) { Some.None @@ -64,7 +58,7 @@ sealed class Some { object None : Some() override fun toString(): String { - return when(this) { + return when (this) { is None -> "None" is Success -> "Some(${value.toString()})" } @@ -125,6 +119,22 @@ fun safeFail(throwable: Throwable): Resource { 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 safeApiCall( apiCall: suspend () -> T, ): Resource { diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 9948bd59..d66a6d56 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -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 + } } /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index baf1b363..8d969b0a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -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 = _availableBytes val downloadBytes: LiveData = _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(it) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt index 3cde4ba6..04a20e73 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt @@ -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( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index 15dd2fc2..7319c5b2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -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>() val resumeWatching: LiveData> = _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?) = viewModelScope.launch { + fun loadStoredData(preferredWatchStatus: EnumSet?) = 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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 64906c3a..d0500e83 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 3f37fa15..0ed26b71 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -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>() val currentSubs = mutableSetOf() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 785db28d..8045c0fb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 6327ffe0..498eb109 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -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 -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index 779c62d0..b629b73f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -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) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt index 3eab1657..b5f82ae8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt @@ -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> = _pluginStats //TODO CACHE GET REQUESTS - fun loadStats() = viewModelScope.launch { + fun loadStats() = viewModelScope.launchSafe { val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index 778f61e0..db406e36 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -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() } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index dec9e7de..146f0f6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -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()) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index 7102d52a..39cc9fbe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -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)) + } } /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt index c2d15614..43718e0e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -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.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.ioSafe(work: suspend (CoroutineScope.(T) -> Unit)): Job { val value = this - return CoroutineScope(Dispatchers.IO).launch { + return CoroutineScope(Dispatchers.IO).launchSafe { + work(value) + } + } + + suspend fun V.ioWorkSafe(work: suspend (CoroutineScope.(V) -> T)): T? { + val value = this + return withContext(Dispatchers.IO) { try { work(value) } catch (e: Exception) { logError(e) + null } } } diff --git a/app/src/main/res/layout/repository_item.xml b/app/src/main/res/layout/repository_item.xml index a66a70f9..26976b9b 100644 --- a/app/src/main/res/layout/repository_item.xml +++ b/app/src/main/res/layout/repository_item.xml @@ -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"> @@ -78,6 +80,7 @@ tools:visibility="visible" /> + tools:visibility="visible" + android:contentDescription="@string/title_settings" /> + android:focusable="true" + android:contentDescription="@string/download" /> \ No newline at end of file