mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	fixed crashes
This commit is contained in:
		
							parent
							
								
									e9e4298a7a
								
							
						
					
					
						commit
						0fcb7b8db5
					
				
					 18 changed files with 141 additions and 106 deletions
				
			
		|  | @ -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,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 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -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 { | ||||
|                     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 -> { | ||||
|  |  | |||
|  | @ -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()) | ||||
|  |  | |||
|  | @ -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)) | ||||
|             } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue