forked from recloudstream/cloudstream
		
	pre 2.6.8, fixed many bugs and crashes
This commit is contained in:
		
							parent
							
								
									4bc86374d7
								
							
						
					
					
						commit
						dcb97a1f63
					
				
					 19 changed files with 464 additions and 134 deletions
				
			
		|  | @ -35,8 +35,8 @@ android { | ||||||
|         minSdkVersion 21 |         minSdkVersion 21 | ||||||
|         targetSdkVersion 30 |         targetSdkVersion 30 | ||||||
| 
 | 
 | ||||||
|         versionCode 41 |         versionCode 42 | ||||||
|         versionName "2.5.8" |         versionName "2.6.9" | ||||||
| 
 | 
 | ||||||
|         resValue "string", "app_version", |         resValue "string", "app_version", | ||||||
|                 "${defaultConfig.versionName}${versionNameSuffix ?: ""}" |                 "${defaultConfig.versionName}${versionNameSuffix ?: ""}" | ||||||
|  | @ -88,9 +88,9 @@ dependencies { | ||||||
| 
 | 
 | ||||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" |     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" | ||||||
|     implementation 'androidx.core:core-ktx:1.7.0' |     implementation 'androidx.core:core-ktx:1.7.0' | ||||||
|     implementation 'androidx.appcompat:appcompat:1.4.0' |     implementation 'androidx.appcompat:appcompat:1.4.1' | ||||||
|     implementation 'com.google.android.material:material:1.4.0' |     implementation 'com.google.android.material:material:1.5.0' | ||||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.2' |     implementation 'androidx.constraintlayout:constraintlayout:2.1.3' | ||||||
|     implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0-rc01' |     implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0-rc01' | ||||||
|     implementation 'androidx.navigation:navigation-ui-ktx:2.4.0-rc01' |     implementation 'androidx.navigation:navigation-ui-ktx:2.4.0-rc01' | ||||||
|     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' |     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' | ||||||
|  | @ -103,7 +103,7 @@ dependencies { | ||||||
|     implementation 'org.jsoup:jsoup:1.13.1' |     implementation 'org.jsoup:jsoup:1.13.1' | ||||||
|     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3" |     implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3" | ||||||
| 
 | 
 | ||||||
|     implementation("com.google.android.material:material:1.4.0") |     implementation "com.google.android.material:material:1.5.0" | ||||||
| 
 | 
 | ||||||
|     implementation "androidx.preference:preference-ktx:1.1.1" |     implementation "androidx.preference:preference-ktx:1.1.1" | ||||||
| 
 | 
 | ||||||
|  | @ -147,7 +147,7 @@ dependencies { | ||||||
|     implementation "androidx.work:work-runtime-ktx:2.7.1" |     implementation "androidx.work:work-runtime-ktx:2.7.1" | ||||||
| 
 | 
 | ||||||
|     // Networking |     // Networking | ||||||
|     implementation "com.squareup.okhttp3:okhttp:4.9.1" |     implementation "com.squareup.okhttp3:okhttp:4.9.2" | ||||||
|     implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1" |     implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1" | ||||||
|     implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1' |     implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| package com.lagradost.cloudstream3 | package com.lagradost.cloudstream3 | ||||||
| 
 | 
 | ||||||
| import androidx.test.platform.app.InstrumentationRegistry |  | ||||||
| import androidx.test.ext.junit.runners.AndroidJUnit4 | import androidx.test.ext.junit.runners.AndroidJUnit4 | ||||||
| 
 | import com.lagradost.cloudstream3.mvvm.logError | ||||||
|  | import com.lagradost.cloudstream3.utils.Qualities | ||||||
|  | import com.lagradost.cloudstream3.utils.SubtitleHelper | ||||||
|  | import kotlinx.coroutines.runBlocking | ||||||
|  | import org.junit.Assert | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| import org.junit.runner.RunWith | import org.junit.runner.RunWith | ||||||
| 
 | 
 | ||||||
| import org.junit.Assert.* |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Instrumented test, which will execute on an Android device. |  * Instrumented test, which will execute on an Android device. | ||||||
|  * |  * | ||||||
|  | @ -15,10 +16,232 @@ import org.junit.Assert.* | ||||||
|  */ |  */ | ||||||
| @RunWith(AndroidJUnit4::class) | @RunWith(AndroidJUnit4::class) | ||||||
| class ExampleInstrumentedTest { | class ExampleInstrumentedTest { | ||||||
|  |     //@Test | ||||||
|  |     //fun useAppContext() { | ||||||
|  |     //    // Context of the app under test. | ||||||
|  |     //    val appContext = InstrumentationRegistry.getInstrumentation().targetContext | ||||||
|  |     //    assertEquals("com.lagradost.cloudstream3", appContext.packageName) | ||||||
|  |     //} | ||||||
|  | 
 | ||||||
|  |     private fun getAllProviders(): List<MainAPI> { | ||||||
|  |         val allApis = APIHolder.apis | ||||||
|  |         allApis.addAll(APIHolder.restrictedApis) | ||||||
|  |         return allApis //.filter { !it.usesWebView } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private suspend fun loadLinks(api: MainAPI, url: String?): Boolean { | ||||||
|  |         Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) | ||||||
|  |         if (url == null) return true | ||||||
|  |         var linksLoaded = 0 | ||||||
|  |         try { | ||||||
|  |             val success = api.loadLinks(url, false, {}) { link -> | ||||||
|  |                 Assert.assertTrue( | ||||||
|  |                     "Api ${api.name} returns link with invalid Quality", | ||||||
|  |                     Qualities.values().map { it.value }.contains(link.quality) | ||||||
|  |                 ) | ||||||
|  |                 Assert.assertTrue( | ||||||
|  |                     "Api ${api.name} returns link with invalid url ${link.url}", | ||||||
|  |                     link.url.length > 4 | ||||||
|  |                 ) | ||||||
|  |                 linksLoaded++ | ||||||
|  |             } | ||||||
|  |             if (success) { | ||||||
|  |                 return linksLoaded > 0 | ||||||
|  |             } | ||||||
|  |             Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success) | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             if (e.cause is NotImplementedError) { | ||||||
|  |                 Assert.fail("Provider has not implemented .loadLinks") | ||||||
|  |             } | ||||||
|  |             logError(e) | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private suspend fun testSingleProviderApi(api: MainAPI): Boolean { | ||||||
|  |         val searchQueries = listOf("over", "iron", "guy") | ||||||
|  |         var correctResponses = 0 | ||||||
|  |         var searchResult: List<SearchResponse>? = null | ||||||
|  |         for (query in searchQueries) { | ||||||
|  |             val response = try { | ||||||
|  |                 api.search(query) | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 if (e.cause is NotImplementedError) { | ||||||
|  |                     Assert.fail("Provider has not implemented .search") | ||||||
|  |                 } | ||||||
|  |                 logError(e) | ||||||
|  |                 null | ||||||
|  |             } | ||||||
|  |             if (!response.isNullOrEmpty()) { | ||||||
|  |                 correctResponses++ | ||||||
|  |                 if (searchResult == null) { | ||||||
|  |                     searchResult = response | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (correctResponses == 0 || searchResult == null) { | ||||||
|  |             System.err.println("Api ${api.name} did not return any valid search responses") | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             var validResults = false | ||||||
|  |             for (result in searchResult) { | ||||||
|  |                 Assert.assertEquals( | ||||||
|  |                     "Invalid apiName on response on ${api.name}", | ||||||
|  |                     result.apiName, | ||||||
|  |                     api.name | ||||||
|  |                 ) | ||||||
|  |                 val load = api.load(result.url) ?: continue | ||||||
|  |                 Assert.assertEquals( | ||||||
|  |                     "Invalid apiName on load on ${api.name}", | ||||||
|  |                     load.apiName, | ||||||
|  |                     result.apiName | ||||||
|  |                 ) | ||||||
|  |                 Assert.assertTrue( | ||||||
|  |                     "Api ${api.name} on load does not contain any of the supportedTypes", | ||||||
|  |                     api.supportedTypes.contains(load.type) | ||||||
|  |                 ) | ||||||
|  |                 when (load) { | ||||||
|  |                     is AnimeLoadResponse -> { | ||||||
|  |                         val gotNoEpisodes = | ||||||
|  |                             load.episodes.keys.isEmpty() || load.episodes.keys.any { load.episodes[it].isNullOrEmpty() } | ||||||
|  | 
 | ||||||
|  |                         if (gotNoEpisodes) { | ||||||
|  |                             println("Api ${api.name} got no episodes on ${load.url}") | ||||||
|  |                             continue | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         val url = (load.episodes[load.episodes.keys.first()])?.first()?.url | ||||||
|  |                         validResults = loadLinks(api, url) | ||||||
|  |                         if (!validResults) continue | ||||||
|  |                     } | ||||||
|  |                     is MovieLoadResponse -> { | ||||||
|  |                         val gotNoEpisodes = load.dataUrl.isBlank() | ||||||
|  |                         if (gotNoEpisodes) { | ||||||
|  |                             println("Api ${api.name} got no movie on ${load.url}") | ||||||
|  |                             continue | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         validResults = loadLinks(api, load.dataUrl) | ||||||
|  |                         if (!validResults) continue | ||||||
|  |                     } | ||||||
|  |                     is TvSeriesLoadResponse -> { | ||||||
|  |                         val gotNoEpisodes = load.episodes.isEmpty() | ||||||
|  |                         if (gotNoEpisodes) { | ||||||
|  |                             println("Api ${api.name} got no episodes on ${load.url}") | ||||||
|  |                             continue | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         validResults = loadLinks(api, load.episodes.first().data) | ||||||
|  |                         if (!validResults) continue | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 break | ||||||
|  |             } | ||||||
|  |             if(!validResults) { | ||||||
|  |                 System.err.println("Api ${api.name} did not load on any") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return validResults | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             if (e.cause is NotImplementedError) { | ||||||
|  |                 Assert.fail("Provider has not implemented .load") | ||||||
|  |             } | ||||||
|  |             logError(e) | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun useAppContext() { |     fun providersExist() { | ||||||
|         // Context of the app under test. |         Assert.assertTrue(getAllProviders().isNotEmpty()) | ||||||
|         val appContext = InstrumentationRegistry.getInstrumentation().targetContext |         println("Done providersExist") | ||||||
|         assertEquals("com.lagradost.cloudstream3", appContext.packageName) |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     fun providerCorrectData() { | ||||||
|  |         val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 } | ||||||
|  |         Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty()) | ||||||
|  |         for (api in getAllProviders()) { | ||||||
|  |             Assert.assertTrue("Api does not contain a mainUrl", api.mainUrl != "NONE") | ||||||
|  |             Assert.assertTrue("Api does not contain a name", api.name != "NONE") | ||||||
|  |             Assert.assertTrue( | ||||||
|  |                 "Api ${api.name} does not contain a valid language code", | ||||||
|  |                 isoNames.contains(api.lang) | ||||||
|  |             ) | ||||||
|  |             Assert.assertTrue( | ||||||
|  |                 "Api ${api.name} does not contain any supported types", | ||||||
|  |                 api.supportedTypes.isNotEmpty() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         println("Done providerCorrectData") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     fun providerCorrectHomepage() { | ||||||
|  |         runBlocking { | ||||||
|  |             getAllProviders().apmap { api -> | ||||||
|  |                 if (api.hasMainPage) { | ||||||
|  |                     try { | ||||||
|  |                         val homepage = api.getMainPage() | ||||||
|  |                         when { | ||||||
|  |                             homepage == null -> { | ||||||
|  |                                 System.err.println("Homepage provider ${api.name} did not correctly load homepage!") | ||||||
|  |                             } | ||||||
|  |                             homepage.items.isEmpty() -> { | ||||||
|  |                                 System.err.println("Homepage provider ${api.name} does not contain any items!") | ||||||
|  |                             } | ||||||
|  |                             homepage.items.any { it.list.isEmpty() } -> { | ||||||
|  |                                 System.err.println ("Homepage provider ${api.name} does not have any items on result!") | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } catch (e: Exception) { | ||||||
|  |                         if (e.cause is NotImplementedError) { | ||||||
|  |                             Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") | ||||||
|  |                         } | ||||||
|  |                         logError(e) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         println("Done providerCorrectHomepage") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | //    @Test | ||||||
|  | //    fun testSingleProvider() { | ||||||
|  | //        testSingleProviderApi(ThenosProvider()) | ||||||
|  | //    } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     fun providerCorrect() { | ||||||
|  |         runBlocking { | ||||||
|  |             val invalidProvider = ArrayList<Pair<MainAPI, Exception?>>() | ||||||
|  |             val providers = getAllProviders() | ||||||
|  |             providers.apmap { api -> | ||||||
|  |                 try { | ||||||
|  |                     println("Trying $api") | ||||||
|  |                     if (testSingleProviderApi(api)) { | ||||||
|  |                         println("Success $api") | ||||||
|  |                     } else { | ||||||
|  |                         System.err.println("Error $api") | ||||||
|  |                         invalidProvider.add(Pair(api, null)) | ||||||
|  |                     } | ||||||
|  |                 } catch (e: Exception) { | ||||||
|  |                     logError(e) | ||||||
|  |                     invalidProvider.add(Pair(api, e)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if(invalidProvider.isEmpty()) { | ||||||
|  |                 println("No Invalid providers! :D") | ||||||
|  |             } else { | ||||||
|  |                 println("Invalid providers are: ") | ||||||
|  |                 for (provider in invalidProvider) { | ||||||
|  |                     println("${provider.first}") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         println("Done providerCorrect") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -184,12 +184,12 @@ object APIHolder { | ||||||
|         return realSet |         return realSet | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun Context.filterProviderByPreferredMedia(): List<MainAPI> { |     fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired : Boolean = true): List<MainAPI> { | ||||||
|         val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) |         val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||||
|         val currentPrefMedia = |         val currentPrefMedia = | ||||||
|             settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) |             settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) | ||||||
|         val langs = this.getApiProviderLangSettings() |         val langs = this.getApiProviderLangSettings() | ||||||
|         val allApis = apis.filter { langs.contains(it.lang) }.filter { api -> api.hasMainPage } |         val allApis = apis.filter { langs.contains(it.lang) }.filter { api -> api.hasMainPage || !hasHomePageIsRequired} | ||||||
|         return if (currentPrefMedia < 1) { |         return if (currentPrefMedia < 1) { | ||||||
|             allApis |             allApis | ||||||
|         } else { |         } else { | ||||||
|  |  | ||||||
|  | @ -45,7 +45,7 @@ class VfFilmProvider : MainAPI() { | ||||||
|         subtitleCallback: (SubtitleFile) -> Unit, |         subtitleCallback: (SubtitleFile) -> Unit, | ||||||
|         callback: (ExtractorLink) -> Unit |         callback: (ExtractorLink) -> Unit | ||||||
|     ): Boolean { |     ): Boolean { | ||||||
|         if (data == "") return false |         if (data.length <= 4) return false | ||||||
|         callback.invoke( |         callback.invoke( | ||||||
|             ExtractorLink( |             ExtractorLink( | ||||||
|                 this.name, |                 this.name, | ||||||
|  | @ -100,7 +100,7 @@ class VfFilmProvider : MainAPI() { | ||||||
|                 number_player += 1 |                 number_player += 1 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (found == false) { |         if (!found) { | ||||||
|             number_player = 0 |             number_player = 0 | ||||||
|         } |         } | ||||||
|         val i = number_player.toString() |         val i = number_player.toString() | ||||||
|  | @ -108,7 +108,6 @@ class VfFilmProvider : MainAPI() { | ||||||
| 
 | 
 | ||||||
|         val data = getDirect("$mainUrl/?trembed=$i&trid=$trid&trtype=1") |         val data = getDirect("$mainUrl/?trembed=$i&trid=$trid&trtype=1") | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         return MovieLoadResponse( |         return MovieLoadResponse( | ||||||
|             title, |             title, | ||||||
|             url, |             url, | ||||||
|  |  | ||||||
|  | @ -61,13 +61,13 @@ class FrenchStreamProvider : MainAPI() { | ||||||
|         val listEpisode = soup.selectFirst("div.elink") |         val listEpisode = soup.selectFirst("div.elink") | ||||||
| 
 | 
 | ||||||
|         if (isMovie) { |         if (isMovie) { | ||||||
|             val trailer = soup.selectFirst("div.fleft > span > a").attr("href").toString() |             val trailer = soup.selectFirst("div.fleft > span > a")?.attr("href") | ||||||
|             val date = soup.select("ul.flist-col > li")[2].text().toIntOrNull() |             val date = soup.select("ul.flist-col > li")?.getOrNull(2)?.text()?.toIntOrNull() | ||||||
|             val ratingAverage = soup.select("div.fr-count > div").text().toIntOrNull() |             val ratingAverage = soup.select("div.fr-count > div")?.text()?.toIntOrNull() | ||||||
|             val tags = soup.select("ul.flist-col > li")[1] |             val tags = soup.select("ul.flist-col > li")?.getOrNull(1) | ||||||
|             val tagsList = tags.select("a") |             val tagsList = tags?.select("a") | ||||||
|                 .map {   // all the tags like action, thriller ...; unused variable |                 ?.mapNotNull {   // all the tags like action, thriller ...; unused variable | ||||||
|                     it.text() |                     it?.text() | ||||||
|                 } |                 } | ||||||
|             return MovieLoadResponse( |             return MovieLoadResponse( | ||||||
|                 title, |                 title, | ||||||
|  | @ -185,10 +185,10 @@ class FrenchStreamProvider : MainAPI() { | ||||||
|                 val serversvo =  // Original version servers |                 val serversvo =  // Original version servers | ||||||
|                     soup.select("div#episode$translated > div.selink > ul.btnss $div> li") |                     soup.select("div#episode$translated > div.selink > ul.btnss $div> li") | ||||||
|                         .mapNotNull { li -> |                         .mapNotNull { li -> | ||||||
|                             val serverurl = fixUrl(li.selectFirst("a").attr("href")) |                             val serverUrl = fixUrlNull(li.selectFirst("a")?.attr("href")) | ||||||
|                             if (serverurl != "") { |                             if (!serverUrl.isNullOrEmpty()) { | ||||||
|                                 if (li.text().replace(" ", "").replace(" ", "") != "") { |                                 if (li.text().replace(" ", "").replace(" ", "") != "") { | ||||||
|                                     Pair(li.text().replace(" ", ""), fixUrl(serverurl)) |                                     Pair(li.text().replace(" ", ""), fixUrl(serverUrl)) | ||||||
|                                 } else { |                                 } else { | ||||||
|                                     null |                                     null | ||||||
|                                 } |                                 } | ||||||
|  | @ -198,22 +198,22 @@ class FrenchStreamProvider : MainAPI() { | ||||||
|                         } |                         } | ||||||
|                 serversvf + serversvo |                 serversvf + serversvo | ||||||
|             } else {  // it's a movie |             } else {  // it's a movie | ||||||
|                 val soup = app.get(fixUrl(data)).document |  | ||||||
|                 val movieServers = |                 val movieServers = | ||||||
|                     soup.select("nav#primary_nav_wrap > ul > li > ul > li > a").mapNotNull { a -> |                     app.get(fixUrl(data)).document.select("nav#primary_nav_wrap > ul > li > ul > li > a") | ||||||
|                         val serverurl = fixUrl(a.attr("href")) |                         .mapNotNull { a -> | ||||||
|                         val parent = a.parents()[2] |                             val serverurl = fixUrlNull(a.attr("href")) ?: return@mapNotNull null | ||||||
|                         val element = parent.selectFirst("a").text().plus(" ") |                             val parent = a.parents()[2] | ||||||
|                         if (a.text().replace(" ", "").trim() != "") { |                             val element = parent.selectFirst("a").text().plus(" ") | ||||||
|                             Pair(element.plus(a.text()), fixUrl(serverurl)) |                             if (a.text().replace(" ", "").trim() != "") { | ||||||
|                         } else { |                                 Pair(element.plus(a.text()), fixUrl(serverurl)) | ||||||
|                             null |                             } else { | ||||||
|  |                                 null | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |  | ||||||
|                 movieServers |                 movieServers | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         servers.forEach { |         servers.apmap { | ||||||
|             for (extractor in extractorApis) { |             for (extractor in extractorApis) { | ||||||
|                 if (it.first.contains(extractor.name, ignoreCase = true)) { |                 if (it.first.contains(extractor.name, ignoreCase = true)) { | ||||||
| //                    val name = it.first | //                    val name = it.first | ||||||
|  |  | ||||||
|  | @ -438,7 +438,7 @@ class HomeFragment : Fragment() { | ||||||
|                     val d = data.value |                     val d = data.value | ||||||
| 
 | 
 | ||||||
|                     currentHomePage = d |                     currentHomePage = d | ||||||
|                     (home_master_recycler?.adapter as ParentItemAdapter?)?.items = |                     (home_master_recycler?.adapter as ParentItemAdapter?)?.updateList( | ||||||
|                         d?.items?.mapNotNull { |                         d?.items?.mapNotNull { | ||||||
|                             try { |                             try { | ||||||
|                                 HomePageList(it.name, it.list.filterSearchResponse()) |                                 HomePageList(it.name, it.list.filterSearchResponse()) | ||||||
|  | @ -446,9 +446,7 @@ class HomeFragment : Fragment() { | ||||||
|                                 logError(e) |                                 logError(e) | ||||||
|                                 null |                                 null | ||||||
|                             } |                             } | ||||||
|                         } ?: listOf() |                         } ?: listOf()) | ||||||
| 
 |  | ||||||
|                     home_master_recycler?.adapter?.notifyDataSetChanged() |  | ||||||
| 
 | 
 | ||||||
|                     home_loading?.isVisible = false |                     home_loading?.isVisible = false | ||||||
|                     home_loading_error?.isVisible = false |                     home_loading_error?.isVisible = false | ||||||
|  | @ -470,9 +468,13 @@ class HomeFragment : Fragment() { | ||||||
|                                 api.name |                                 api.name | ||||||
|                             ) |                             ) | ||||||
|                         }) { |                         }) { | ||||||
|                             val i = Intent(Intent.ACTION_VIEW) |                             try { | ||||||
|                             i.data = Uri.parse(validAPIs[itemId].mainUrl) |                                 val i = Intent(Intent.ACTION_VIEW) | ||||||
|                             startActivity(i) |                                 i.data = Uri.parse(validAPIs[itemId].mainUrl) | ||||||
|  |                                 startActivity(i) | ||||||
|  |                             } catch (e: Exception) { | ||||||
|  |                                 logError(e) | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|  | @ -489,9 +491,8 @@ class HomeFragment : Fragment() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = |         val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = | ||||||
|             ParentItemAdapter(listOf(), { callback -> |             ParentItemAdapter(mutableListOf(), { callback -> | ||||||
|                 homeHandleSearch(callback) |                 homeHandleSearch(callback) | ||||||
|             }, { item -> |             }, { item -> | ||||||
|                 activity?.loadHomepageList(item) |                 activity?.loadHomepageList(item) | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import android.widget.FrameLayout | import android.widget.FrameLayout | ||||||
| import android.widget.TextView | import android.widget.TextView | ||||||
|  | import androidx.recyclerview.widget.DiffUtil | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
| import com.lagradost.cloudstream3.HomePageList | import com.lagradost.cloudstream3.HomePageList | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
|  | @ -12,14 +13,16 @@ import com.lagradost.cloudstream3.ui.search.SearchClickCallback | ||||||
| import kotlinx.android.synthetic.main.homepage_parent.view.* | import kotlinx.android.synthetic.main.homepage_parent.view.* | ||||||
| 
 | 
 | ||||||
| class ParentItemAdapter( | class ParentItemAdapter( | ||||||
|     var items: List<HomePageList>, |     private var items: MutableList<HomePageList>, | ||||||
|     private val clickCallback: (SearchClickCallback) -> Unit, |     private val clickCallback: (SearchClickCallback) -> Unit, | ||||||
|     private val moreInfoClickCallback: (HomePageList) -> Unit, |     private val moreInfoClickCallback: (HomePageList) -> Unit, | ||||||
| ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | ||||||
|     override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder { |     override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder { | ||||||
|         val layout = R.layout.homepage_parent |         val layout = R.layout.homepage_parent | ||||||
|         return ParentViewHolder( |         return ParentViewHolder( | ||||||
|             LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback, moreInfoClickCallback |             LayoutInflater.from(parent.context).inflate(layout, parent, false), | ||||||
|  |             clickCallback, | ||||||
|  |             moreInfoClickCallback | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -35,6 +38,20 @@ class ParentItemAdapter( | ||||||
|         return items.size |         return items.size | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override fun getItemId(position: Int): Long { | ||||||
|  |         return items[position].name.hashCode().toLong() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun updateList(newList: List<HomePageList>) { | ||||||
|  |         val diffResult = DiffUtil.calculateDiff( | ||||||
|  |             SearchDiffCallback(this.items, newList)) | ||||||
|  | 
 | ||||||
|  |         items.clear() | ||||||
|  |         items.addAll(newList) | ||||||
|  | 
 | ||||||
|  |         diffResult.dispatchUpdatesTo(this) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     class ParentViewHolder |     class ParentViewHolder | ||||||
|     constructor( |     constructor( | ||||||
|         itemView: View, |         itemView: View, | ||||||
|  | @ -61,3 +78,16 @@ class ParentItemAdapter( | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | class SearchDiffCallback(private val oldList: List<HomePageList>, private val newList: List<HomePageList>) : | ||||||
|  |     DiffUtil.Callback() { | ||||||
|  |     override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = | ||||||
|  |         oldList[oldItemPosition].name == newList[newItemPosition].name | ||||||
|  | 
 | ||||||
|  |     override fun getOldListSize() = oldList.size | ||||||
|  | 
 | ||||||
|  |     override fun getNewListSize() = newList.size | ||||||
|  | 
 | ||||||
|  |     override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = | ||||||
|  |         oldList[oldItemPosition] == newList[newItemPosition] | ||||||
|  | } | ||||||
|  | @ -197,6 +197,7 @@ abstract class AbstractPlayerFragment( | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun playerError(exception: Exception) { |     private fun playerError(exception: Exception) { | ||||||
|  |         val ctx = context ?: return | ||||||
|         when (exception) { |         when (exception) { | ||||||
|             is PlaybackException -> { |             is PlaybackException -> { | ||||||
|                 val msg = exception.message ?: "" |                 val msg = exception.message ?: "" | ||||||
|  | @ -205,7 +206,7 @@ abstract class AbstractPlayerFragment( | ||||||
|                     PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, PlaybackException.ERROR_CODE_IO_NO_PERMISSION, PlaybackException.ERROR_CODE_IO_UNSPECIFIED -> { |                     PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, PlaybackException.ERROR_CODE_IO_NO_PERMISSION, PlaybackException.ERROR_CODE_IO_UNSPECIFIED -> { | ||||||
|                         showToast( |                         showToast( | ||||||
|                             activity, |                             activity, | ||||||
|                             "${getString(R.string.source_error)}\n$errorName ($code)\n$msg", |                             "${ctx.getString(R.string.source_error)}\n$errorName ($code)\n$msg", | ||||||
|                             Toast.LENGTH_SHORT |                             Toast.LENGTH_SHORT | ||||||
|                         ) |                         ) | ||||||
|                         nextMirror() |                         nextMirror() | ||||||
|  | @ -213,7 +214,7 @@ abstract class AbstractPlayerFragment( | ||||||
|                     PlaybackException.ERROR_CODE_REMOTE_ERROR, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_TIMEOUT, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE -> { |                     PlaybackException.ERROR_CODE_REMOTE_ERROR, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_TIMEOUT, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE -> { | ||||||
|                         showToast( |                         showToast( | ||||||
|                             activity, |                             activity, | ||||||
|                             "${getString(R.string.remote_error)}\n$errorName ($code)\n$msg", |                             "${ctx.getString(R.string.remote_error)}\n$errorName ($code)\n$msg", | ||||||
|                             Toast.LENGTH_SHORT |                             Toast.LENGTH_SHORT | ||||||
|                         ) |                         ) | ||||||
|                         nextMirror() |                         nextMirror() | ||||||
|  | @ -221,7 +222,7 @@ abstract class AbstractPlayerFragment( | ||||||
|                     PlaybackException.ERROR_CODE_DECODING_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_INIT_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_OTHER, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED -> { |                     PlaybackException.ERROR_CODE_DECODING_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_INIT_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_OTHER, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED -> { | ||||||
|                         showToast( |                         showToast( | ||||||
|                             activity, |                             activity, | ||||||
|                             "${getString(R.string.render_error)}\n$errorName ($code)\n$msg", |                             "${ctx.getString(R.string.render_error)}\n$errorName ($code)\n$msg", | ||||||
|                             Toast.LENGTH_SHORT |                             Toast.LENGTH_SHORT | ||||||
|                         ) |                         ) | ||||||
|                         nextMirror() |                         nextMirror() | ||||||
|  | @ -229,7 +230,7 @@ abstract class AbstractPlayerFragment( | ||||||
|                     else -> { |                     else -> { | ||||||
|                         showToast( |                         showToast( | ||||||
|                             activity, |                             activity, | ||||||
|                             "${getString(R.string.unexpected_error)}\n$errorName ($code)\n$msg", |                             "${ctx.getString(R.string.unexpected_error)}\n$errorName ($code)\n$msg", | ||||||
|                             Toast.LENGTH_SHORT |                             Toast.LENGTH_SHORT | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -42,8 +42,8 @@ class DownloadFileGenerator( | ||||||
|         return episodes[currentIndex].id |         return episodes[currentIndex].id | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun getCurrent(): Any { |     override fun getCurrent(offset: Int): Any? { | ||||||
|         return episodes[currentIndex] |         return episodes.getOrNull(currentIndex + offset) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun generateLinks( |     override suspend fun generateLinks( | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import android.view.KeyEvent | ||||||
| import androidx.appcompat.app.AppCompatActivity | import androidx.appcompat.app.AppCompatActivity | ||||||
| import com.lagradost.cloudstream3.CommonActivity | import com.lagradost.cloudstream3.CommonActivity | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
|  | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils | import com.lagradost.cloudstream3.utils.AppUtils | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.getUri | import com.lagradost.cloudstream3.utils.AppUtils.getUri | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorUri | import com.lagradost.cloudstream3.utils.ExtractorUri | ||||||
|  | @ -72,8 +73,12 @@ class DownloadedPlayerActivity : AppCompatActivity() { | ||||||
|             "NULL" |             "NULL" | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         val realUri = AppUtils.getVideoContentUri(this, realPath) |         val tryUri = try { | ||||||
|         val tryUri = realUri ?: uri |             AppUtils.getVideoContentUri(this, realPath) ?: uri | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             logError(e) | ||||||
|  |             uri | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         setContentView(R.layout.empty_layout) |         setContentView(R.layout.empty_layout) | ||||||
|         Log.i(DTAG, "navigating") |         Log.i(DTAG, "navigating") | ||||||
|  |  | ||||||
|  | @ -352,7 +352,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { | ||||||
|         } |         } | ||||||
|         activity?.hideSystemUI() |         activity?.hideSystemUI() | ||||||
|         animateLayoutChanges() |         animateLayoutChanges() | ||||||
|         player_pause_play.requestFocus() |         player_pause_play?.requestFocus() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun toggleLock() { |     private fun toggleLock() { | ||||||
|  |  | ||||||
|  | @ -71,6 +71,15 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|         return setSubtitles(null) |         return setSubtitles(null) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private fun getPos(): Long { | ||||||
|  |         val durPos = DataStoreHelper.getViewPos(viewModel.getId()) ?: return 0L | ||||||
|  |         if (durPos.duration == 0L) return 0L | ||||||
|  |         if (durPos.position * 100L / durPos.duration > 95L) { | ||||||
|  |             return 0L | ||||||
|  |         } | ||||||
|  |         return durPos.position | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private fun loadLink(link: Pair<ExtractorLink?, ExtractorUri?>?, sameEpisode: Boolean) { |     private fun loadLink(link: Pair<ExtractorLink?, ExtractorUri?>?, sameEpisode: Boolean) { | ||||||
|         if (link == null) return |         if (link == null) return | ||||||
| 
 | 
 | ||||||
|  | @ -93,8 +102,7 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|                 url, |                 url, | ||||||
|                 uri, |                 uri, | ||||||
|                 startPosition = if (sameEpisode) null else { |                 startPosition = if (sameEpisode) null else { | ||||||
|                     if (isNextEpisode) 0L else (DataStoreHelper.getViewPos(viewModel.getId())?.position |                     if (isNextEpisode) 0L else getPos() | ||||||
|                         ?: 0L) |  | ||||||
|                 }, |                 }, | ||||||
|                 currentSubs, |                 currentSubs, | ||||||
|             ) |             ) | ||||||
|  | @ -272,11 +280,16 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|                         init = init || if (subtitleIndex <= 0) { |                         init = init || if (subtitleIndex <= 0) { | ||||||
|                             noSubtitles() |                             noSubtitles() | ||||||
|                         } else { |                         } else { | ||||||
|                             setSubtitles(currentSubtitles[subtitleIndex - 1]) |                             currentSubtitles.getOrNull(subtitleIndex - 1)?.let { | ||||||
|  |                                 setSubtitles(it) | ||||||
|  |                                 true | ||||||
|  |                             } ?: false | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     if (init) { |                     if (init) { | ||||||
|                         loadLink(sortedUrls[sourceIndex], true) |                         sortedUrls.getOrNull(sourceIndex)?.let { | ||||||
|  |                             loadLink(it, true) | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                     sourceDialog.dismissSafe(activity) |                     sourceDialog.dismissSafe(activity) | ||||||
|                 } |                 } | ||||||
|  | @ -304,11 +317,13 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
| 
 | 
 | ||||||
|     override fun nextEpisode() { |     override fun nextEpisode() { | ||||||
|         isNextEpisode = true |         isNextEpisode = true | ||||||
|  |         player.release() | ||||||
|         viewModel.loadLinksNext() |         viewModel.loadLinksNext() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun prevEpisode() { |     override fun prevEpisode() { | ||||||
|         isNextEpisode = true |         isNextEpisode = true | ||||||
|  |         player.release() | ||||||
|         viewModel.loadLinksPrev() |         viewModel.loadLinksPrev() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -336,6 +351,7 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|     override fun playerPositionChanged(posDur: Pair<Long, Long>) { |     override fun playerPositionChanged(posDur: Pair<Long, Long>) { | ||||||
|         val (position, duration) = posDur |         val (position, duration) = posDur | ||||||
|         viewModel.getId()?.let { |         viewModel.getId()?.let { | ||||||
|  |             println("SET VIEW ID: $it ($position/$duration)") | ||||||
|             DataStoreHelper.setViewPos(it, position, duration) |             DataStoreHelper.setViewPos(it, position, duration) | ||||||
|         } |         } | ||||||
|         val percentage = position * 100L / duration |         val percentage = position * 100L / duration | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ interface IGenerator { | ||||||
|     fun goto(index: Int) |     fun goto(index: Int) | ||||||
| 
 | 
 | ||||||
|     fun getCurrentId(): Int?   // this is used to save data or read data about this id |     fun getCurrentId(): Int?   // this is used to save data or read data about this id | ||||||
|     fun getCurrent(): Any?     // this is used to get metadata about the current playing, can return null |     fun getCurrent(offset : Int = 0): Any?     // this is used to get metadata about the current playing, can return null | ||||||
| 
 | 
 | ||||||
|     /* not safe, must use try catch */ |     /* not safe, must use try catch */ | ||||||
|     suspend fun generateLinks( |     suspend fun generateLinks( | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.ui.player | package com.lagradost.cloudstream3.ui.player | ||||||
| 
 | 
 | ||||||
|  | import android.util.Log | ||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||||
| import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||||
|  | @ -13,6 +14,10 @@ import kotlinx.coroutines.Job | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| 
 | 
 | ||||||
| class PlayerGeneratorViewModel : ViewModel() { | class PlayerGeneratorViewModel : ViewModel() { | ||||||
|  |     companion object { | ||||||
|  |         val TAG = "PlayViewGen" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private var generator: IGenerator? = null |     private var generator: IGenerator? = null | ||||||
| 
 | 
 | ||||||
|     private val _currentLinks = MutableLiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>>(setOf()) |     private val _currentLinks = MutableLiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>>(setOf()) | ||||||
|  | @ -34,6 +39,7 @@ class PlayerGeneratorViewModel : ViewModel() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun loadLinksPrev() { |     fun loadLinksPrev() { | ||||||
|  |         Log.i(TAG, "loadLinksPrev") | ||||||
|         if (generator?.hasPrev() == true) { |         if (generator?.hasPrev() == true) { | ||||||
|             generator?.prev() |             generator?.prev() | ||||||
|             loadLinks() |             loadLinks() | ||||||
|  | @ -41,6 +47,7 @@ class PlayerGeneratorViewModel : ViewModel() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun loadLinksNext() { |     fun loadLinksNext() { | ||||||
|  |         Log.i(TAG, "loadLinksNext") | ||||||
|         if (generator?.hasNext() == true) { |         if (generator?.hasNext() == true) { | ||||||
|             generator?.next() |             generator?.next() | ||||||
|             loadLinks() |             loadLinks() | ||||||
|  | @ -52,11 +59,18 @@ class PlayerGeneratorViewModel : ViewModel() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun preLoadNextLinks() { |     fun preLoadNextLinks() { | ||||||
|  |         Log.i(TAG, "preLoadNextLinks") | ||||||
|         currentJob?.cancel() |         currentJob?.cancel() | ||||||
|         currentJob = viewModelScope.launch { |         currentJob = viewModelScope.launch { | ||||||
|             if (generator?.hasCache == true && generator?.hasNext() == true) { |             if (generator?.hasCache == true && generator?.hasNext() == true) { | ||||||
|                 safeApiCall { |                 safeApiCall { | ||||||
|                     generator?.generateLinks(clearCache = false, isCasting = false, {}, {}, offset = 1) |                     generator?.generateLinks( | ||||||
|  |                         clearCache = false, | ||||||
|  |                         isCasting = false, | ||||||
|  |                         {}, | ||||||
|  |                         {}, | ||||||
|  |                         offset = 1 | ||||||
|  |                     ) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -69,10 +83,7 @@ class PlayerGeneratorViewModel : ViewModel() { | ||||||
|     fun getNextMeta(): Any? { |     fun getNextMeta(): Any? { | ||||||
|         return normalSafeApiCall { |         return normalSafeApiCall { | ||||||
|             if (generator?.hasNext() == false) return@normalSafeApiCall null |             if (generator?.hasNext() == false) return@normalSafeApiCall null | ||||||
|             generator?.next() |             generator?.getCurrent(offset = 1) | ||||||
|             val next = generator?.getCurrent() |  | ||||||
|             generator?.prev() |  | ||||||
|             next |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -91,6 +102,7 @@ class PlayerGeneratorViewModel : ViewModel() { | ||||||
|     private var currentJob: Job? = null |     private var currentJob: Job? = null | ||||||
| 
 | 
 | ||||||
|     fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) { |     fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) { | ||||||
|  |         Log.i(TAG, "loadLinks") | ||||||
|         currentJob?.cancel() |         currentJob?.cancel() | ||||||
|         currentJob = viewModelScope.launch { |         currentJob = viewModelScope.launch { | ||||||
|             val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>() |             val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>() | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.ui.player | package com.lagradost.cloudstream3.ui.player | ||||||
| 
 | 
 | ||||||
|  | import android.util.Log | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull | import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull | ||||||
| import com.lagradost.cloudstream3.ui.APIRepository | import com.lagradost.cloudstream3.ui.APIRepository | ||||||
| import com.lagradost.cloudstream3.ui.result.ResultEpisode | import com.lagradost.cloudstream3.ui.result.ResultEpisode | ||||||
|  | @ -8,7 +9,14 @@ import com.lagradost.cloudstream3.utils.ExtractorUri | ||||||
| import kotlin.math.max | import kotlin.math.max | ||||||
| import kotlin.math.min | import kotlin.math.min | ||||||
| 
 | 
 | ||||||
| class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var currentIndex: Int = 0) : IGenerator { | class RepoLinkGenerator( | ||||||
|  |     private val episodes: List<ResultEpisode>, | ||||||
|  |     private var currentIndex: Int = 0 | ||||||
|  | ) : IGenerator { | ||||||
|  |     companion object { | ||||||
|  |         val TAG = "RepoLink" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     override val hasCache = true |     override val hasCache = true | ||||||
| 
 | 
 | ||||||
|     override fun hasNext(): Boolean { |     override fun hasNext(): Boolean { | ||||||
|  | @ -20,16 +28,19 @@ class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var c | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun next() { |     override fun next() { | ||||||
|  |         Log.i(TAG, "next") | ||||||
|         if (hasNext()) |         if (hasNext()) | ||||||
|             currentIndex++ |             currentIndex++ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun prev() { |     override fun prev() { | ||||||
|  |         Log.i(TAG, "prev") | ||||||
|         if (hasPrev()) |         if (hasPrev()) | ||||||
|             currentIndex-- |             currentIndex-- | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun goto(index: Int) { |     override fun goto(index: Int) { | ||||||
|  |         Log.i(TAG, "goto $index") | ||||||
|         // clamps value |         // clamps value | ||||||
|         currentIndex = min(episodes.size - 1, max(0, index)) |         currentIndex = min(episodes.size - 1, max(0, index)) | ||||||
|     } |     } | ||||||
|  | @ -38,8 +49,8 @@ class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var c | ||||||
|         return episodes[currentIndex].id |         return episodes[currentIndex].id | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun getCurrent(): Any { |     override fun getCurrent(offset: Int): Any? { | ||||||
|         return episodes[currentIndex] |         return episodes.getOrNull(currentIndex + offset) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // this is a simple array that is used to instantly load links if they are already loaded |     // this is a simple array that is used to instantly load links if they are already loaded | ||||||
|  | @ -51,10 +62,10 @@ class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var c | ||||||
|         isCasting: Boolean, |         isCasting: Boolean, | ||||||
|         callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, |         callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, | ||||||
|         subtitleCallback: (SubtitleData) -> Unit, |         subtitleCallback: (SubtitleData) -> Unit, | ||||||
|         offset : Int, |         offset: Int, | ||||||
|     ): Boolean { |     ): Boolean { | ||||||
|         val index = currentIndex |         val index = currentIndex | ||||||
|         val current = episodes[index + offset] |         val current = episodes.getOrNull(index + offset) ?: return false | ||||||
| 
 | 
 | ||||||
|         val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet() |         val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet() | ||||||
|         val currentSubsCache = if (clearCache) mutableSetOf() else subsCache[index].toMutableSet() |         val currentSubsCache = if (clearCache) mutableSetOf() else subsCache[index].toMutableSet() | ||||||
|  | @ -76,7 +87,7 @@ class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var c | ||||||
| 
 | 
 | ||||||
|         // this stops all execution if links are cached |         // this stops all execution if links are cached | ||||||
|         // no extra get requests |         // no extra get requests | ||||||
|         if(currentLinkCache.size > 0) { |         if (currentLinkCache.size > 0) { | ||||||
|             return true |             return true | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -86,13 +97,13 @@ class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var c | ||||||
|             isCasting, |             isCasting, | ||||||
|             { file -> |             { file -> | ||||||
|                 val correctFile = PlayerSubtitleHelper.getSubtitleData(file) |                 val correctFile = PlayerSubtitleHelper.getSubtitleData(file) | ||||||
|                 if(!currentSubsUrls.contains(correctFile.url)) { |                 if (!currentSubsUrls.contains(correctFile.url)) { | ||||||
|                     currentSubsUrls.add(correctFile.url) |                     currentSubsUrls.add(correctFile.url) | ||||||
| 
 | 
 | ||||||
|                     // this part makes sure that all names are unique for UX |                     // this part makes sure that all names are unique for UX | ||||||
|                     var name = correctFile.name |                     var name = correctFile.name | ||||||
|                     var count = 0 |                     var count = 0 | ||||||
|                     while(currentSubsNames.contains(name)) { |                     while (currentSubsNames.contains(name)) { | ||||||
|                         count++ |                         count++ | ||||||
|                         name = "${correctFile.name} $count" |                         name = "${correctFile.name} $count" | ||||||
|                     } |                     } | ||||||
|  | @ -108,7 +119,7 @@ class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var c | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             { link -> |             { link -> | ||||||
|                 if(!currentLinks.contains(link.url)) { |                 if (!currentLinks.contains(link.url)) { | ||||||
|                     if (!currentLinkCache.contains(link)) { |                     if (!currentLinkCache.contains(link)) { | ||||||
|                         currentLinks.add(link.url) |                         currentLinks.add(link.url) | ||||||
|                         callback(Pair(link, null)) |                         callback(Pair(link, null)) | ||||||
|  |  | ||||||
|  | @ -38,7 +38,11 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() { | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         fun pushSync(activity: Activity?, autoSearch: String? = null, callback: (SearchClickCallback) -> Unit) { |         fun pushSync( | ||||||
|  |             activity: Activity?, | ||||||
|  |             autoSearch: String? = null, | ||||||
|  |             callback: (SearchClickCallback) -> Unit | ||||||
|  |         ) { | ||||||
|             clickCallback = callback |             clickCallback = callback | ||||||
|             activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply { |             activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply { | ||||||
|                 putBoolean("mainapi", false) |                 putBoolean("mainapi", false) | ||||||
|  | @ -82,14 +86,13 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() { | ||||||
|                 // https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist |                 // https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist | ||||||
|                 listLock.lock() |                 listLock.lock() | ||||||
|                 (quick_search_master_recycler?.adapter as ParentItemAdapter?)?.apply { |                 (quick_search_master_recycler?.adapter as ParentItemAdapter?)?.apply { | ||||||
|                     items = list.map { ongoing -> |                     updateList(list.map { ongoing -> | ||||||
|                         val ongoingList = HomePageList( |                         val ongoingList = HomePageList( | ||||||
|                             ongoing.apiName, |                             ongoing.apiName, | ||||||
|                             if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() |                             if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() | ||||||
|                         ) |                         ) | ||||||
|                         ongoingList |                         ongoingList | ||||||
|                     } |                     }) | ||||||
|                     notifyDataSetChanged() |  | ||||||
|                 } |                 } | ||||||
|             } catch (e: Exception) { |             } catch (e: Exception) { | ||||||
|                 logError(e) |                 logError(e) | ||||||
|  | @ -98,31 +101,38 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = ParentItemAdapter(listOf(), { callback -> |         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = | ||||||
|             when (callback.action) { |             ParentItemAdapter(mutableListOf(), { callback -> | ||||||
|                 SEARCH_ACTION_LOAD -> { |                 when (callback.action) { | ||||||
|                     if (isMainApis) { |                     SEARCH_ACTION_LOAD -> { | ||||||
|                         activity?.popCurrentPage() |                         if (isMainApis) { | ||||||
|  |                             activity?.popCurrentPage() | ||||||
| 
 | 
 | ||||||
|                         SearchHelper.handleSearchClickCallback(activity, callback) |                             SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|                     } else { |                         } else { | ||||||
|                         clickCallback?.invoke(callback) |                             clickCallback?.invoke(callback) | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|  |                     else -> SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|                 } |                 } | ||||||
|                 else -> SearchHelper.handleSearchClickCallback(activity, callback) |             }, { item -> | ||||||
|             } |                 activity?.loadHomepageList(item) | ||||||
|         }, { item -> |             }) | ||||||
|             activity?.loadHomepageList(item) |  | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|         val searchExitIcon = quick_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn) |         val searchExitIcon = | ||||||
|         val searchMagIcon = quick_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon) |             quick_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn) | ||||||
|  |         val searchMagIcon = | ||||||
|  |             quick_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon) | ||||||
| 
 | 
 | ||||||
|         searchMagIcon.scaleX = 0.65f |         searchMagIcon.scaleX = 0.65f | ||||||
|         searchMagIcon.scaleY = 0.65f |         searchMagIcon.scaleY = 0.65f | ||||||
|         quick_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |         quick_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||||
|             override fun onQueryTextSubmit(query: String): Boolean { |             override fun onQueryTextSubmit(query: String): Boolean { | ||||||
|                 searchViewModel.searchAndCancel(query = query, isMainApis = isMainApis, ignoreSettings = true) |                 searchViewModel.searchAndCancel( | ||||||
|  |                     query = query, | ||||||
|  |                     isMainApis = isMainApis, | ||||||
|  |                     ignoreSettings = true | ||||||
|  |                 ) | ||||||
|                 quick_search?.let { |                 quick_search?.let { | ||||||
|                     UIHelper.hideKeyboard(it) |                     UIHelper.hideKeyboard(it) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1132,7 +1132,7 @@ class ResultFragment : Fragment() { | ||||||
|                             try { |                             try { | ||||||
|                                 startActivity(i) |                                 startActivity(i) | ||||||
|                             } catch (e: Exception) { |                             } catch (e: Exception) { | ||||||
|                                 e.printStackTrace() |                                 logError(e) | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|  | @ -1394,7 +1394,7 @@ class ResultFragment : Fragment() { | ||||||
|                     try { |                     try { | ||||||
|                         startActivity(i) |                         startActivity(i) | ||||||
|                     } catch (e: Exception) { |                     } catch (e: Exception) { | ||||||
|                         e.printStackTrace() |                         logError(e) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -145,7 +145,7 @@ class SearchFragment : Fragment() { | ||||||
| 
 | 
 | ||||||
|         search_filter.setOnClickListener { searchView -> |         search_filter.setOnClickListener { searchView -> | ||||||
|             searchView?.context?.let { ctx -> |             searchView?.context?.let { ctx -> | ||||||
|                 val validAPIs = ctx.filterProviderByPreferredMedia() |                 val validAPIs = ctx.filterProviderByPreferredMedia(hasHomePageIsRequired = false) | ||||||
|                 var currentValidApis = listOf<MainAPI>() |                 var currentValidApis = listOf<MainAPI>() | ||||||
|                 val currentSelectedApis = if (selectedApis.isEmpty()) validAPIs.map { it.name } |                 val currentSelectedApis = if (selectedApis.isEmpty()) validAPIs.map { it.name } | ||||||
|                     .toMutableSet() else selectedApis |                     .toMutableSet() else selectedApis | ||||||
|  | @ -213,7 +213,7 @@ class SearchFragment : Fragment() { | ||||||
|                     fun updateList() { |                     fun updateList() { | ||||||
|                         arrayAdapter.clear() |                         arrayAdapter.clear() | ||||||
|                         currentValidApis = validAPIs.filter { api -> |                         currentValidApis = validAPIs.filter { api -> | ||||||
|                             api.hasMainPage && api.supportedTypes.any { |                             api.supportedTypes.any { | ||||||
|                                 selectedSearchTypes.contains(it) |                                 selectedSearchTypes.contains(it) | ||||||
|                             } |                             } | ||||||
|                         }.sortedBy { it.name } |                         }.sortedBy { it.name } | ||||||
|  | @ -224,7 +224,7 @@ class SearchFragment : Fragment() { | ||||||
|                             listView?.setItemChecked(index, currentSelectedApis.contains(api)) |                             listView?.setItemChecked(index, currentSelectedApis.contains(api)) | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         arrayAdapter.notifyDataSetChanged() |                         //arrayAdapter.notifyDataSetChanged() | ||||||
|                         arrayAdapter.addAll(names) |                         arrayAdapter.addAll(names) | ||||||
|                         arrayAdapter.notifyDataSetChanged() |                         arrayAdapter.notifyDataSetChanged() | ||||||
|                     } |                     } | ||||||
|  | @ -373,14 +373,16 @@ class SearchFragment : Fragment() { | ||||||
|                 // https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist |                 // https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist | ||||||
|                 listLock.lock() |                 listLock.lock() | ||||||
|                 (search_master_recycler?.adapter as ParentItemAdapter?)?.apply { |                 (search_master_recycler?.adapter as ParentItemAdapter?)?.apply { | ||||||
|                     items = list.map { ongoing -> |                     val newItems = list.map { ongoing -> | ||||||
|                         val ongoingList = HomePageList( |                         val ongoingList = HomePageList( | ||||||
|                             ongoing.apiName, |                             ongoing.apiName, | ||||||
|                             if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() |                             if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() | ||||||
|                         ) |                         ) | ||||||
|                         ongoingList |                         ongoingList | ||||||
|                     } |                     } | ||||||
|                     notifyDataSetChanged() |                     updateList(newItems) | ||||||
|  | 
 | ||||||
|  |                     //notifyDataSetChanged() | ||||||
|                 } |                 } | ||||||
|             } catch (e: Exception) { |             } catch (e: Exception) { | ||||||
|                 logError(e) |                 logError(e) | ||||||
|  | @ -399,7 +401,7 @@ class SearchFragment : Fragment() { | ||||||
|         //main_search.onActionViewExpanded()*/ |         //main_search.onActionViewExpanded()*/ | ||||||
| 
 | 
 | ||||||
|         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = |         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = | ||||||
|             ParentItemAdapter(listOf(), { callback -> |             ParentItemAdapter(mutableListOf(), { callback -> | ||||||
|                 SearchHelper.handleSearchClickCallback(activity, callback) |                 SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|             }, { item -> |             }, { item -> | ||||||
|                 activity?.loadHomepageList(item) |                 activity?.loadHomepageList(item) | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package com.lagradost.cloudstream3 | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
| import com.lagradost.cloudstream3.utils.SubtitleHelper | import com.lagradost.cloudstream3.utils.SubtitleHelper | ||||||
|  | import kotlinx.coroutines.runBlocking | ||||||
| import org.junit.Assert | import org.junit.Assert | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +24,10 @@ class ProviderTests { | ||||||
|                     "Api ${api.name} returns link with invalid Quality", |                     "Api ${api.name} returns link with invalid Quality", | ||||||
|                     Qualities.values().map { it.value }.contains(link.quality) |                     Qualities.values().map { it.value }.contains(link.quality) | ||||||
|                 ) |                 ) | ||||||
|                 Assert.assertTrue("Api ${api.name} returns link with invalid url", link.url.length > 4) |                 Assert.assertTrue( | ||||||
|  |                     "Api ${api.name} returns link with invalid url", | ||||||
|  |                     link.url.length > 4 | ||||||
|  |                 ) | ||||||
|                 linksLoaded++ |                 linksLoaded++ | ||||||
|             } |             } | ||||||
|             if (success) { |             if (success) { | ||||||
|  | @ -69,9 +73,17 @@ class ProviderTests { | ||||||
|         try { |         try { | ||||||
|             var validResults = false |             var validResults = false | ||||||
|             for (result in searchResult) { |             for (result in searchResult) { | ||||||
|                 Assert.assertEquals("Invalid apiName on response on ${api.name}", result.apiName, api.name) |                 Assert.assertEquals( | ||||||
|  |                     "Invalid apiName on response on ${api.name}", | ||||||
|  |                     result.apiName, | ||||||
|  |                     api.name | ||||||
|  |                 ) | ||||||
|                 val load = api.load(result.url) ?: continue |                 val load = api.load(result.url) ?: continue | ||||||
|                 Assert.assertEquals("Invalid apiName on load on ${api.name}", load.apiName, result.apiName) |                 Assert.assertEquals( | ||||||
|  |                     "Invalid apiName on load on ${api.name}", | ||||||
|  |                     load.apiName, | ||||||
|  |                     result.apiName | ||||||
|  |                 ) | ||||||
|                 Assert.assertTrue( |                 Assert.assertTrue( | ||||||
|                     "Api ${api.name} on load does not contain any of the supportedTypes", |                     "Api ${api.name} on load does not contain any of the supportedTypes", | ||||||
|                     api.supportedTypes.contains(load.type) |                     api.supportedTypes.contains(load.type) | ||||||
|  | @ -137,33 +149,41 @@ class ProviderTests { | ||||||
|         for (api in getAllProviders()) { |         for (api in getAllProviders()) { | ||||||
|             Assert.assertTrue("Api does not contain a mainUrl", api.mainUrl != "NONE") |             Assert.assertTrue("Api does not contain a mainUrl", api.mainUrl != "NONE") | ||||||
|             Assert.assertTrue("Api does not contain a name", api.name != "NONE") |             Assert.assertTrue("Api does not contain a name", api.name != "NONE") | ||||||
|             Assert.assertTrue("Api ${api.name} does not contain a valid language code", isoNames.contains(api.lang)) |             Assert.assertTrue( | ||||||
|             Assert.assertTrue("Api ${api.name} does not contain any supported types", api.supportedTypes.isNotEmpty()) |                 "Api ${api.name} does not contain a valid language code", | ||||||
|  |                 isoNames.contains(api.lang) | ||||||
|  |             ) | ||||||
|  |             Assert.assertTrue( | ||||||
|  |                 "Api ${api.name} does not contain any supported types", | ||||||
|  |                 api.supportedTypes.isNotEmpty() | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     fun providerCorrectHomepage() { |     fun providerCorrectHomepage() { | ||||||
|         getAllProviders().apmap { api -> |         runBlocking { | ||||||
|             if (api.hasMainPage) { |             getAllProviders().apmap { api -> | ||||||
|                 try { |                 if (api.hasMainPage) { | ||||||
|                     val homepage = api.getMainPage() |                     try { | ||||||
|                     when { |                         val homepage = api.getMainPage() | ||||||
|                         homepage == null -> { |                         when { | ||||||
|                             Assert.fail("Homepage provider ${api.name} did not correctly load homepage!") |                             homepage == null -> { | ||||||
|  |                                 Assert.fail("Homepage provider ${api.name} did not correctly load homepage!") | ||||||
|  |                             } | ||||||
|  |                             homepage.items.isEmpty() -> { | ||||||
|  |                                 Assert.fail("Homepage provider ${api.name} does not contain any items!") | ||||||
|  |                             } | ||||||
|  |                             homepage.items.any { it.list.isEmpty() } -> { | ||||||
|  |                                 Assert.fail("Homepage provider ${api.name} does not have any items on result!") | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                         homepage.items.isEmpty() -> { |                     } catch (e: Exception) { | ||||||
|                             Assert.fail("Homepage provider ${api.name} does not contain any items!") |                         if (e.cause is NotImplementedError) { | ||||||
|                         } |                             Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") | ||||||
|                         homepage.items.any { it.list.isEmpty() } -> { |  | ||||||
|                             Assert.fail("Homepage provider ${api.name} does not have any items on result!") |  | ||||||
|                         } |                         } | ||||||
|  |                         logError(e) | ||||||
|                     } |                     } | ||||||
|                 } catch (e: Exception) { |  | ||||||
|                     if (e.cause is NotImplementedError) { |  | ||||||
|                         Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") |  | ||||||
|                     } |  | ||||||
|                     logError(e) |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -176,7 +196,7 @@ class ProviderTests { | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     suspend fun providerCorrect() { |     suspend fun providerCorrect() { | ||||||
|         val invalidProvider = ArrayList<Pair<MainAPI,Exception?>>() |         val invalidProvider = ArrayList<Pair<MainAPI, Exception?>>() | ||||||
|         val providers = getAllProviders() |         val providers = getAllProviders() | ||||||
|         providers.apmap { api -> |         providers.apmap { api -> | ||||||
|             try { |             try { | ||||||
|  | @ -189,7 +209,7 @@ class ProviderTests { | ||||||
|                 } |                 } | ||||||
|             } catch (e: Exception) { |             } catch (e: Exception) { | ||||||
|                 logError(e) |                 logError(e) | ||||||
|                 invalidProvider.add(Pair(api,e)) |                 invalidProvider.add(Pair(api, e)) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue