mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	+ Added Confirm exit on Android TV
+ Added support for Play Next on Android TV
This commit is contained in:
		
							parent
							
								
									4f54bf3ae4
								
							
						
					
					
						commit
						95f4a15864
					
				
					 8 changed files with 182 additions and 59 deletions
				
			
		|  | @ -11,7 +11,7 @@ | |||
|     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <!-- Used for player vertical slide --> | ||||
|     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Used for app update --> | ||||
|     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Used for app notifications on Android 13+ --> | ||||
|     <!-- <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> not used atm, but code exist that requires it that are not run --> | ||||
|     <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- not used atm, but code exist that requires it that are not run --> | ||||
|     <!-- <permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!– Used for getting if vlc is installed –> --> | ||||
|     <!-- Fixes android tv fuckery --> | ||||
|     <uses-feature | ||||
|  | @ -122,6 +122,19 @@ | |||
|                 <data android:scheme="cloudstreamsearch" /> | ||||
|             </intent-filter> | ||||
| 
 | ||||
|             <!-- | ||||
|             Allow opening from continue watching with intents: cloudstreamsearch://1234 | ||||
|             Used on Android TV Watch Next | ||||
|              --> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
| 
 | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|                 <category android:name="android.intent.category.BROWSABLE" /> | ||||
| 
 | ||||
|                 <data android:scheme="cloudstreamcontinuewatching" /> | ||||
|             </intent-filter> | ||||
| 
 | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,13 +54,17 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2A | |||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers | ||||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString | ||||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo | ||||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching | ||||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch | ||||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths | ||||
| import com.lagradost.cloudstream3.ui.APIRepository | ||||
| import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO | ||||
| import com.lagradost.cloudstream3.ui.home.HomeViewModel | ||||
| import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST | ||||
| import com.lagradost.cloudstream3.ui.search.SearchFragment | ||||
| import com.lagradost.cloudstream3.ui.search.SearchResultBuilder | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsGeneral | ||||
| import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY | ||||
|  | @ -69,6 +73,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable | |||
| import com.lagradost.cloudstream3.utils.AppUtils.loadCache | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.loadRepository | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.loadResult | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult | ||||
| import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.DataStore.getKey | ||||
|  | @ -83,6 +88,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute | |||
| import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.requestRW | ||||
| import com.lagradost.cloudstream3.utils.USER_PROVIDER_API | ||||
| import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API | ||||
|  | @ -289,6 +295,19 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | |||
|                         nextSearchQuery = | ||||
|                             URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8") | ||||
|                         nav_view.selectedItemId = R.id.navigation_search | ||||
|                     } else if (safeURI(str)?.scheme == appStringResumeWatching) { | ||||
|                         val id = | ||||
|                             str.substringAfter("$appStringResumeWatching://").toIntOrNull() | ||||
|                                 ?: return false | ||||
|                         ioSafe { | ||||
|                             val resumeWatchingCard = | ||||
|                                 HomeViewModel.getResumeWatching()?.firstOrNull { it.id == id } | ||||
|                                     ?: return@ioSafe | ||||
|                             activity.loadSearchResult( | ||||
|                                 resumeWatchingCard, | ||||
|                                 START_ACTION_RESUME_LATEST | ||||
|                             ) | ||||
|                         } | ||||
|                     } else if (!isWebview) { | ||||
|                         if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) { | ||||
|                             this.navigate(R.id.navigation_downloads) | ||||
|  | @ -449,12 +468,33 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | |||
|         onUserLeaveHint(this) | ||||
|     } | ||||
| 
 | ||||
|     private fun showConfirmExitDialog() { | ||||
|         val builder: AlertDialog.Builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) | ||||
|         builder.setTitle(R.string.confirm_exit_dialog) | ||||
|         builder.apply { | ||||
|             setPositiveButton(R.string.yes) { _, _ -> super.onBackPressed() } | ||||
|             setNegativeButton(R.string.no) { _, _ -> } | ||||
|         } | ||||
|         builder.show() | ||||
|     } | ||||
| 
 | ||||
|     private fun backPressed() { | ||||
|         this.window?.navigationBarColor = | ||||
|             this.colorFromAttribute(R.attr.primaryGrayBackground) | ||||
|         this.updateLocale() | ||||
|         super.onBackPressed() | ||||
|         this.updateLocale() | ||||
| 
 | ||||
|         val navHostFragment = | ||||
|             supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment | ||||
|         val navController = navHostFragment?.navController | ||||
|         val isAtHome = | ||||
|             navController?.currentDestination?.matchDestination(R.id.navigation_home) == true | ||||
| 
 | ||||
|         if (isAtHome && isTrueTvSettings()) { | ||||
|             showConfirmExitDialog() | ||||
|         } else { | ||||
|             super.onBackPressed() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun onBackPressed() { | ||||
|  |  | |||
|  | @ -48,6 +48,9 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { | |||
|         // Instantly start the search given a query | ||||
|         const val appStringSearch = "cloudstreamsearch" | ||||
| 
 | ||||
|         // Instantly resume watching a show | ||||
|         const val appStringResumeWatching = "cloudstreamcontinuewatching" | ||||
| 
 | ||||
|         val unixTime: Long | ||||
|             get() = System.currentTimeMillis() / 1000L | ||||
|         val unixTimeMs: Long | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import android.content.DialogInterface | |||
| import android.content.Intent | ||||
| import android.content.res.Configuration | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
|  | @ -56,6 +57,7 @@ import com.lagradost.cloudstream3.ui.search.* | |||
| import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.loadResult | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult | ||||
|  | @ -552,7 +554,7 @@ class HomeFragment : Fragment() { | |||
| 
 | ||||
|         observe(homeViewModel.preview) { preview -> | ||||
|             // Always reset the padding, otherwise the will move lower and lower | ||||
|            // home_fix_padding?.setPadding(0, 0, 0, 0) | ||||
|             // home_fix_padding?.setPadding(0, 0, 0, 0) | ||||
|             home_fix_padding?.let { v -> | ||||
|                 val params = v.layoutParams | ||||
|                 params.height = 0 | ||||
|  | @ -596,7 +598,7 @@ class HomeFragment : Fragment() { | |||
|             val callback: OnPageChangeCallback = object : OnPageChangeCallback() { | ||||
|                 override fun onPageSelected(position: Int) { | ||||
| 
 | ||||
|                    // home_search?.isIconified = true | ||||
|                     // home_search?.isIconified = true | ||||
|                     //home_search?.isVisible = true | ||||
|                     //home_search?.clearFocus() | ||||
| 
 | ||||
|  | @ -927,11 +929,13 @@ class HomeFragment : Fragment() { | |||
|                 resumeWatching | ||||
|             ) | ||||
| 
 | ||||
|             //if (context?.isTvSettings() == true) { | ||||
|             //    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|             //        context?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult }) | ||||
|             //    } | ||||
|             //} | ||||
|             if (isTrueTvSettings()) { | ||||
|                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|                     ioSafe { | ||||
|                         activity?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult }) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             home_watch_child_more_info?.setOnClickListener { | ||||
|                 activity?.loadHomepageList( | ||||
|  |  | |||
|  | @ -36,6 +36,42 @@ import java.util.* | |||
| import kotlin.collections.set | ||||
| 
 | ||||
| class HomeViewModel : ViewModel() { | ||||
|     companion object { | ||||
|         suspend fun getResumeWatching(): List<DataStoreHelper.ResumeWatchingResult>? { | ||||
|             val resumeWatching = withContext(Dispatchers.IO) { | ||||
|                 getAllResumeStateIds()?.mapNotNull { id -> | ||||
|                     getLastWatched(id) | ||||
|                 }?.sortedBy { -it.updateTime } | ||||
|             } | ||||
|             val resumeWatchingResult = withContext(Dispatchers.IO) { | ||||
|                 resumeWatching?.mapNotNull { resume -> | ||||
| 
 | ||||
|                     val data = getKey<VideoDownloadHelper.DownloadHeaderCached>( | ||||
|                         DOWNLOAD_HEADER_CACHE, | ||||
|                         resume.parentId.toString() | ||||
|                     ) ?: return@mapNotNull null | ||||
| 
 | ||||
|                     val watchPos = getViewPos(resume.episodeId) | ||||
| 
 | ||||
|                     DataStoreHelper.ResumeWatchingResult( | ||||
|                         data.name, | ||||
|                         data.url, | ||||
|                         data.apiName, | ||||
|                         data.type, | ||||
|                         data.poster, | ||||
|                         watchPos, | ||||
|                         resume.episodeId, | ||||
|                         resume.parentId, | ||||
|                         resume.episode, | ||||
|                         resume.season, | ||||
|                         resume.isFromDownload | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|             return resumeWatchingResult | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private var repo: APIRepository? = null | ||||
| 
 | ||||
|     private val _apiName = MutableLiveData<String>() | ||||
|  | @ -66,36 +102,7 @@ class HomeViewModel : ViewModel() { | |||
|     val preview: LiveData<Resource<Pair<Boolean, List<LoadResponse>>>> = _preview | ||||
| 
 | ||||
|     fun loadResumeWatching() = viewModelScope.launchSafe { | ||||
|         val resumeWatching = withContext(Dispatchers.IO) { | ||||
|             getAllResumeStateIds()?.mapNotNull { id -> | ||||
|                 getLastWatched(id) | ||||
|             }?.sortedBy { -it.updateTime } | ||||
|         } | ||||
| 
 | ||||
|         // val resumeWatchingResult = ArrayList<DataStoreHelper.ResumeWatchingResult>() | ||||
| 
 | ||||
|         val resumeWatchingResult = withContext(Dispatchers.IO) { | ||||
|             resumeWatching?.map { resume -> | ||||
|                 val data = getKey<VideoDownloadHelper.DownloadHeaderCached>( | ||||
|                     DOWNLOAD_HEADER_CACHE, | ||||
|                     resume.parentId.toString() | ||||
|                 ) ?: return@map null | ||||
|                 val watchPos = getViewPos(resume.episodeId) | ||||
|                 DataStoreHelper.ResumeWatchingResult( | ||||
|                     data.name, | ||||
|                     data.url, | ||||
|                     data.apiName, | ||||
|                     data.type, | ||||
|                     data.poster, | ||||
|                     watchPos, | ||||
|                     resume.episodeId, | ||||
|                     resume.parentId, | ||||
|                     resume.episode, | ||||
|                     resume.season, | ||||
|                     resume.isFromDownload | ||||
|                 ) | ||||
|             }?.filterNotNull() | ||||
|         } | ||||
|         val resumeWatchingResult = getResumeWatching() | ||||
|         resumeWatchingResult?.let { | ||||
|             _resumeWatching.postValue(it) | ||||
|         } | ||||
|  |  | |||
|  | @ -3,9 +3,7 @@ package com.lagradost.cloudstream3.utils | |||
| import android.annotation.SuppressLint | ||||
| import android.app.Activity | ||||
| import android.app.Activity.RESULT_CANCELED | ||||
| import android.content.ContentValues | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.content.* | ||||
| import android.content.pm.PackageManager | ||||
| import android.database.Cursor | ||||
| import android.media.AudioAttributes | ||||
|  | @ -26,7 +24,6 @@ import androidx.activity.result.contract.ActivityResultContracts | |||
| import androidx.annotation.RequiresApi | ||||
| import androidx.annotation.WorkerThread | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.core.text.HtmlCompat | ||||
| import androidx.core.text.toSpanned | ||||
|  | @ -35,9 +32,7 @@ import androidx.fragment.app.FragmentActivity | |||
| import androidx.navigation.fragment.findNavController | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import androidx.tvprovider.media.tv.PreviewChannelHelper | ||||
| import androidx.tvprovider.media.tv.TvContractCompat | ||||
| import androidx.tvprovider.media.tv.WatchNextProgram | ||||
| import androidx.tvprovider.media.tv.* | ||||
| import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor | ||||
| import com.fasterxml.jackson.module.kotlin.readValue | ||||
| import com.google.android.gms.cast.framework.CastContext | ||||
|  | @ -51,6 +46,7 @@ import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEv | |||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | ||||
| import com.lagradost.cloudstream3.plugins.RepositoryManager | ||||
| import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching | ||||
| import com.lagradost.cloudstream3.ui.WebviewFragment | ||||
| import com.lagradost.cloudstream3.ui.result.ResultFragment | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings | ||||
|  | @ -58,9 +54,13 @@ import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Compan | |||
| import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.main | ||||
| import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds | ||||
| import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched | ||||
| import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir | ||||
| import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
| import okhttp3.Cache | ||||
| import java.io.* | ||||
| import java.net.URL | ||||
|  | @ -110,7 +110,8 @@ object AppUtils { | |||
|     @SuppressLint("RestrictedApi") | ||||
|     private fun buildWatchNextProgramUri( | ||||
|         context: Context, | ||||
|         card: DataStoreHelper.ResumeWatchingResult | ||||
|         card: DataStoreHelper.ResumeWatchingResult, | ||||
|         resumeWatching: VideoDownloadHelper.ResumeWatching? | ||||
|     ): WatchNextProgram { | ||||
|         val isSeries = card.type?.isMovieType() == false | ||||
|         val title = if (isSeries) { | ||||
|  | @ -129,15 +130,18 @@ object AppUtils { | |||
|             .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) | ||||
|             .setTitle(title) | ||||
|             .setPosterArtUri(Uri.parse(card.posterUrl)) | ||||
|             .setIntentUri(Uri.parse(card.url)) //TODO FIX intent | ||||
|             .setIntentUri(Uri.parse(card.id?.let { | ||||
|                 "$appStringResumeWatching://$it" | ||||
|             } ?: card.url)) | ||||
|             .setInternalProviderId(card.url) | ||||
|         //.setLastEngagementTimeUtcMillis(System.currentTimeMillis()) | ||||
|             .setLastEngagementTimeUtcMillis( | ||||
|                 resumeWatching?.updateTime ?: System.currentTimeMillis() | ||||
|             ) | ||||
| 
 | ||||
|         card.watchPos?.let { | ||||
|             builder.setDurationMillis(it.duration.toInt()) | ||||
|             builder.setLastPlaybackPositionMillis(it.position.toInt()) | ||||
|         } | ||||
|         // .setLastEngagementTimeUtcMillis() //TODO | ||||
| 
 | ||||
|         if (isSeries) | ||||
|             card.episode?.let { | ||||
|  | @ -147,6 +151,27 @@ object AppUtils { | |||
|         return builder.build() | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("RestrictedApi") | ||||
|     fun getAllWatchNextPrograms(context: Context): Set<Long> { | ||||
|         val COLUMN_WATCH_NEXT_ID_INDEX = 0 | ||||
|         val cursor = context.contentResolver.query( | ||||
|             TvContractCompat.WatchNextPrograms.CONTENT_URI, | ||||
|             WatchNextProgram.PROJECTION, | ||||
|             /* selection = */ null, | ||||
|             /* selectionArgs = */ null, | ||||
|             /* sortOrder = */ null | ||||
|         ) | ||||
|         val set = mutableSetOf<Long>() | ||||
|         cursor?.use { | ||||
|             if (it.moveToFirst()) { | ||||
|                 do { | ||||
|                     set.add(cursor.getLong(COLUMN_WATCH_NEXT_ID_INDEX)) | ||||
|                 } while (it.moveToNext()) | ||||
|             } | ||||
|         } | ||||
|         return set | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Find the Watch Next program for given id. | ||||
|      * Returns the first instance available. | ||||
|  | @ -164,7 +189,7 @@ object AppUtils { | |||
|             WatchNextProgram.PROJECTION, | ||||
|             /* selection = */ null, | ||||
|             /* selectionArgs = */ null, | ||||
|             /* sortOrder= */ null | ||||
|             /* sortOrder = */ null | ||||
|         ) | ||||
|         cursor?.use { | ||||
|             if (it.moveToFirst()) { | ||||
|  | @ -195,17 +220,32 @@ object AppUtils { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** Prevents losing data when removing and adding simultaneously */ | ||||
|     private val continueWatchingLock = Mutex() | ||||
| 
 | ||||
|     // https://github.com/googlearchive/leanback-homescreen-channels/blob/master/app/src/main/java/com/google/android/tvhomescreenchannels/SampleTvProvider.java | ||||
|     @SuppressLint("RestrictedApi") | ||||
|     @WorkerThread | ||||
|     fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) { | ||||
|     suspend fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) { | ||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return | ||||
|         val context = this | ||||
|         ioSafe { | ||||
|             data.forEach { episodeInfo -> | ||||
|         continueWatchingLock.withLock { | ||||
|             // A way to get all last watched timestamps | ||||
|             val timeStampHashMap = HashMap<Int, VideoDownloadHelper.ResumeWatching>() | ||||
|             getAllResumeStateIds()?.forEach { id -> | ||||
|                 val lastWatched = getLastWatched(id) ?: return@forEach | ||||
|                 timeStampHashMap[lastWatched.parentId] = lastWatched | ||||
|             } | ||||
| 
 | ||||
|             val currentProgramIds = data.mapNotNull { episodeInfo -> | ||||
|                 try { | ||||
|                     val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context) | ||||
|                     val nextProgram = buildWatchNextProgramUri(context, episodeInfo) | ||||
|                     val customId = "${episodeInfo.id}|${episodeInfo.apiName}|${episodeInfo.url}" | ||||
|                     val (program, id) = getWatchNextProgramByVideoId(customId, context) | ||||
|                     val nextProgram = buildWatchNextProgramUri( | ||||
|                         context, | ||||
|                         episodeInfo, | ||||
|                         timeStampHashMap[episodeInfo.id] | ||||
|                     ) | ||||
| 
 | ||||
|                     // If the program is already in the Watch Next row, update it | ||||
|                     if (program != null && id != null) { | ||||
|  | @ -213,13 +253,25 @@ object AppUtils { | |||
|                             nextProgram, | ||||
|                             id, | ||||
|                         ) | ||||
|                         id | ||||
|                     } else { | ||||
|                         PreviewChannelHelper(context) | ||||
|                             .publishWatchNextProgram(nextProgram) | ||||
|                     } | ||||
|                 } catch (e: Exception) { | ||||
|                     logError(e) | ||||
|                     null | ||||
|                 } | ||||
|             }.toSet() | ||||
| 
 | ||||
|             val allOldPrograms = getAllWatchNextPrograms(context) - currentProgramIds | ||||
| 
 | ||||
|             // Ensures synced watch next progress by deleting all old programs. | ||||
|             allOldPrograms.forEach { | ||||
|                 context.contentResolver.delete( | ||||
|                     TvContractCompat.buildWatchNextProgramUri(it), | ||||
|                     null, null | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -267,7 +319,7 @@ object AppUtils { | |||
|     fun Activity.downloadAllPluginsDialog(repositoryUrl: String, repositoryName: String) { | ||||
|         runOnUiThread { | ||||
|             val context = this | ||||
|             val builder: AlertDialog.Builder = AlertDialog.Builder(this) | ||||
|             val builder: AlertDialog.Builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) | ||||
|             builder.setTitle( | ||||
|                 repositoryName | ||||
|             ) | ||||
|  | @ -279,7 +331,7 @@ object AppUtils { | |||
|                     downloadAll(context, repositoryUrl, null) | ||||
|                 } | ||||
| 
 | ||||
|                 setNegativeButton(R.string.cancel) { _, _ -> } | ||||
|                 setNegativeButton(R.string.no) { _, _ -> } | ||||
|             } | ||||
|             builder.show() | ||||
|         } | ||||
|  |  | |||
|  | @ -655,4 +655,8 @@ | |||
|     <string name="history">History</string> | ||||
|     <string name="enable_skip_op_from_database_des">Show skip popups for opening/ending</string> | ||||
|     <string name="clipboard_too_large">Too much text. Unable to save to clipboard.</string> | ||||
|     <string name="action_mark_as_watched">Mark as watched</string> | ||||
|     <string name="confirm_exit_dialog">Are you sure you want to exit?</string> | ||||
|     <string name="yes">Yes</string> | ||||
|     <string name="no">No</string> | ||||
| </resources> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue