forked from recloudstream/cloudstream
		
	chromecast subtitle support
This commit is contained in:
		
							parent
							
								
									9fc732c68c
								
							
						
					
					
						commit
						e5189a1c7e
					
				
					 11 changed files with 324 additions and 157 deletions
				
			
		|  | @ -151,7 +151,7 @@ class LookMovieProvider : MainAPI() { | ||||||
|     private fun addSubtitles(subs: List<LookMovieTokenSubtitle>?, subtitleCallback: (SubtitleFile) -> Unit) { |     private fun addSubtitles(subs: List<LookMovieTokenSubtitle>?, subtitleCallback: (SubtitleFile) -> Unit) { | ||||||
|         if (subs == null) return |         if (subs == null) return | ||||||
|         subs.forEach { |         subs.forEach { | ||||||
|             if (it.source != "opensubtitle") |             if (it.file.endsWith(".vtt")) | ||||||
|                 subtitleCallback.invoke(SubtitleFile(it.language, fixUrl(it.file))) |                 subtitleCallback.invoke(SubtitleFile(it.language, fixUrl(it.file))) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,25 +1,29 @@ | ||||||
| package com.lagradost.cloudstream3.ui | package com.lagradost.cloudstream3.ui | ||||||
| 
 | 
 | ||||||
|  | import android.graphics.Color | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
|  | import android.util.Log | ||||||
| import android.view.Menu | import android.view.Menu | ||||||
| import android.view.View.* | import android.view.View.* | ||||||
| import android.widget.AbsListView | import android.widget.* | ||||||
| import android.widget.ArrayAdapter | import androidx.appcompat.app.AlertDialog | ||||||
| import android.widget.ImageView | import androidx.core.graphics.toColorInt | ||||||
| import android.widget.ListView |  | ||||||
| import com.fasterxml.jackson.databind.DeserializationFeature | import com.fasterxml.jackson.databind.DeserializationFeature | ||||||
| import com.fasterxml.jackson.databind.json.JsonMapper | import com.fasterxml.jackson.databind.json.JsonMapper | ||||||
| import com.fasterxml.jackson.module.kotlin.KotlinModule | import com.fasterxml.jackson.module.kotlin.KotlinModule | ||||||
| import com.google.android.gms.cast.MediaQueueItem | import com.google.android.gms.cast.MediaQueueItem | ||||||
| import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF | import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF | ||||||
|  | import com.google.android.gms.cast.MediaTrack | ||||||
|  | import com.google.android.gms.cast.TextTrackStyle | ||||||
|  | import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_OUTLINE | ||||||
| import com.google.android.gms.cast.framework.CastButtonFactory | import com.google.android.gms.cast.framework.CastButtonFactory | ||||||
| import com.google.android.gms.cast.framework.CastSession | import com.google.android.gms.cast.framework.CastSession | ||||||
| import com.google.android.gms.cast.framework.media.RemoteMediaClient | import com.google.android.gms.cast.framework.media.RemoteMediaClient | ||||||
| import com.google.android.gms.cast.framework.media.uicontroller.UIController | import com.google.android.gms.cast.framework.media.uicontroller.UIController | ||||||
| import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity | import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity | ||||||
| import com.google.android.material.bottomsheet.BottomSheetDialog |  | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiFromName | import com.lagradost.cloudstream3.APIHolder.getApiFromName | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
|  | import com.lagradost.cloudstream3.SubtitleFile | ||||||
| import com.lagradost.cloudstream3.mvvm.Resource | import com.lagradost.cloudstream3.mvvm.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.safeApiCall | import com.lagradost.cloudstream3.mvvm.safeApiCall | ||||||
| import com.lagradost.cloudstream3.sortUrls | import com.lagradost.cloudstream3.sortUrls | ||||||
|  | @ -81,6 +85,7 @@ data class MetadataHolder( | ||||||
|     val currentEpisodeIndex: Int, |     val currentEpisodeIndex: Int, | ||||||
|     val episodes: List<ResultEpisode>, |     val episodes: List<ResultEpisode>, | ||||||
|     val currentLinks: List<ExtractorLink>, |     val currentLinks: List<ExtractorLink>, | ||||||
|  |     val currentSubtitles: List<SubtitleFile> | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() { | class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() { | ||||||
|  | @ -93,15 +98,59 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|             //  lateinit var dialog: AlertDialog |             //  lateinit var dialog: AlertDialog | ||||||
|             val holder = getCurrentMetaData() |             val holder = getCurrentMetaData() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|             if (holder != null) { |             if (holder != null) { | ||||||
|                 val items = holder.currentLinks |                 val items = holder.currentLinks | ||||||
|                 if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) { |                 if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) { | ||||||
|                     // val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom) |                     val subTracks = | ||||||
|                     /*val builder = BottomSheetDialog(view.context, R.style.AlertDialogCustom) |                         remoteMediaClient?.mediaInfo?.mediaTracks?.filter { it.type == MediaTrack.TYPE_TEXT } | ||||||
|                     builder.setTitle("Pick source")*/ |                             ?: ArrayList() | ||||||
|                     val bottomSheetDialog = BottomSheetDialog(view.context) | 
 | ||||||
|                     bottomSheetDialog.setContentView(R.layout.sort_bottom_sheet) |                     val bottomSheetDialogBuilder = AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack) | ||||||
|                     val res = bottomSheetDialog.findViewById<ListView>(R.id.sort_click)!! |                     bottomSheetDialogBuilder.setView(R.layout.sort_bottom_sheet) | ||||||
|  |                     val bottomSheetDialog = bottomSheetDialogBuilder.create() | ||||||
|  |                     bottomSheetDialog.show() | ||||||
|  |                     //  bottomSheetDialog.setContentView(R.layout.sort_bottom_sheet) | ||||||
|  |                     val providerList = bottomSheetDialog.findViewById<ListView>(R.id.sort_providers)!! | ||||||
|  |                     val subtitleList = bottomSheetDialog.findViewById<ListView>(R.id.sort_subtitles)!! | ||||||
|  |                     if (subTracks.isEmpty()) { | ||||||
|  |                         bottomSheetDialog.findViewById<LinearLayout>(R.id.sort_subtitles_holder)?.visibility = GONE | ||||||
|  |                     } else { | ||||||
|  |                         val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice) | ||||||
|  |                         arrayAdapter.add("No Subtitles") | ||||||
|  |                         arrayAdapter.addAll(subTracks.map { it.name }.filterNotNull()) | ||||||
|  | 
 | ||||||
|  |                         subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE | ||||||
|  |                         subtitleList.adapter = arrayAdapter | ||||||
|  | 
 | ||||||
|  |                         subtitleList.setOnItemClickListener { _, _, which, _ -> | ||||||
|  |                             if (which == 0) { | ||||||
|  |                                 remoteMediaClient.setActiveMediaTracks(longArrayOf()) // NO SUBS | ||||||
|  |                             } else { | ||||||
|  |                                 val font = TextTrackStyle() | ||||||
|  |                                 font.fontFamily = "Google Sans" //TODO FONT SETTINGS | ||||||
|  |                                 font.backgroundColor = 0x00FFFFFF // TRANSPARENT | ||||||
|  | 
 | ||||||
|  |                                 font.edgeColor = Color.BLACK | ||||||
|  |                                 font.edgeType = EDGE_TYPE_OUTLINE | ||||||
|  |                                 font.foregroundColor = Color.WHITE | ||||||
|  |                                 font.fontScale = 1.05f | ||||||
|  | 
 | ||||||
|  |                                 remoteMediaClient.setTextTrackStyle(font) | ||||||
|  | 
 | ||||||
|  |                                 remoteMediaClient.setActiveMediaTracks(longArrayOf(subTracks[which - 1].id)) | ||||||
|  |                                     .setResultCallback { | ||||||
|  |                                         if (!it.status.isSuccess) { | ||||||
|  |                                             Log.e( | ||||||
|  |                                                 "CHROMECAST", "Failed with status code:" + | ||||||
|  |                                                         it.status.statusCode + " > " + it.status.statusMessage | ||||||
|  |                                             ) | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                             } | ||||||
|  |                             bottomSheetDialog.dismiss() | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
| 
 | 
 | ||||||
|                     //https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation |                     //https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation | ||||||
|                     val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl |                     val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl | ||||||
|  | @ -113,12 +162,11 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|                     val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice) |                     val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice) | ||||||
|                     arrayAdapter.addAll(sortingMethods.toMutableList()) |                     arrayAdapter.addAll(sortingMethods.toMutableList()) | ||||||
| 
 | 
 | ||||||
|                     res.choiceMode = AbsListView.CHOICE_MODE_SINGLE |                     providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE | ||||||
|                     res.adapter = arrayAdapter |                     providerList.adapter = arrayAdapter | ||||||
|                     res.setItemChecked(sotringIndex, true) |                     providerList.setItemChecked(sotringIndex, true) | ||||||
| 
 | 
 | ||||||
| 
 |                     providerList.setOnItemClickListener { _, _, which, _ -> | ||||||
|                     res.setOnItemClickListener { _, _, which, _ -> |  | ||||||
|                         val epData = holder.episodes[holder.currentEpisodeIndex] |                         val epData = holder.episodes[holder.currentEpisodeIndex] | ||||||
| 
 | 
 | ||||||
|                         fun loadMirror(index: Int) { |                         fun loadMirror(index: Int) { | ||||||
|  | @ -128,7 +176,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|                                 epData, |                                 epData, | ||||||
|                                 holder, |                                 holder, | ||||||
|                                 index, |                                 index, | ||||||
|                                 remoteMediaClient?.mediaInfo?.customData |                                 remoteMediaClient?.mediaInfo?.customData, | ||||||
|  |                                 holder.currentSubtitles, | ||||||
|                             ) |                             ) | ||||||
| 
 | 
 | ||||||
|                             val startAt = remoteMediaClient?.approximateStreamPosition ?: 0 |                             val startAt = remoteMediaClient?.approximateStreamPosition ?: 0 | ||||||
|  | @ -168,10 +217,6 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
| 
 | 
 | ||||||
|                         bottomSheetDialog.dismiss() |                         bottomSheetDialog.dismiss() | ||||||
|                     } |                     } | ||||||
|                     bottomSheetDialog.show() |  | ||||||
|                     /* |  | ||||||
|                     dialog = builder.create() |  | ||||||
|                     dialog.show()*/ |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -208,20 +253,28 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|                         val index = meta.currentEpisodeIndex + 1 |                         val index = meta.currentEpisodeIndex + 1 | ||||||
|                         val epData = meta.episodes[index] |                         val epData = meta.episodes[index] | ||||||
|                         val links = ArrayList<ExtractorLink>() |                         val links = ArrayList<ExtractorLink>() | ||||||
|  |                         val subs = ArrayList<SubtitleFile>() | ||||||
| 
 | 
 | ||||||
|                         val res = safeApiCall { |                         val res = safeApiCall { | ||||||
|                             getApiFromName(meta.apiName).loadLinks(epData.data, true, { subtitleFile ->  }) { |                             getApiFromName(meta.apiName).loadLinks(epData.data, true, { subtitleFile -> | ||||||
|                                 for (i in links) { |                                 if (!subs.any { it.url == subtitleFile.url }) { | ||||||
|                                     if (i.url == it.url) return@loadLinks |                                     subs.add(subtitleFile) | ||||||
|  |                                 } | ||||||
|  |                             }) { link -> | ||||||
|  |                                 if (!links.any { it.url == link.url }) { | ||||||
|  |                                     links.add(link) | ||||||
|                                 } |                                 } | ||||||
|                                 links.add(it) |  | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         if (res is Resource.Success) { |                         if (res is Resource.Success) { | ||||||
|                             val sorted = sortUrls(links) |                             val sorted = sortUrls(links) | ||||||
|                             if (sorted.isNotEmpty()) { |                             if (sorted.isNotEmpty()) { | ||||||
|                                 val jsonCopy = meta.copy(currentLinks = sorted, currentEpisodeIndex = index) |                                 val jsonCopy = meta.copy( | ||||||
|  |                                     currentLinks = sorted, | ||||||
|  |                                     currentSubtitles = subs, | ||||||
|  |                                     currentEpisodeIndex = index | ||||||
|  |                                 ) | ||||||
| 
 | 
 | ||||||
|                                 val done = withContext(Dispatchers.IO) { |                                 val done = withContext(Dispatchers.IO) { | ||||||
|                                     JSONObject(mapper.writeValueAsString(jsonCopy)) |                                     JSONObject(mapper.writeValueAsString(jsonCopy)) | ||||||
|  | @ -231,7 +284,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|                                     epData, |                                     epData, | ||||||
|                                     jsonCopy, |                                     jsonCopy, | ||||||
|                                     0, |                                     0, | ||||||
|                                     done |                                     done, | ||||||
|  |                                     subs | ||||||
|                                 ) |                                 ) | ||||||
| 
 | 
 | ||||||
|                                 /*fun loadIndex(index: Int) { |                                 /*fun loadIndex(index: Int) { | ||||||
|  | @ -305,6 +359,8 @@ class ControllerActivity : ExpandedControllerActivity() { | ||||||
|         uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false)) |         uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false)) | ||||||
|         uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true)) |         uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true)) | ||||||
|         uiMediaController.bindViewToUIController(skipOpButton, SkipNextEpisodeController(skipOpButton)) |         uiMediaController.bindViewToUIController(skipOpButton, SkipNextEpisodeController(skipOpButton)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         /*      val progressBar: CastSeekBar? = findViewById(R.id.cast_seek_bar) |         /*      val progressBar: CastSeekBar? = findViewById(R.id.cast_seek_bar) | ||||||
| 
 | 
 | ||||||
|               progressBar?.backgroundTintList = (UIHelper.adjustAlpha(colorFromAttribute(R.attr.colorPrimary), 0.35f)) |               progressBar?.backgroundTintList = (UIHelper.adjustAlpha(colorFromAttribute(R.attr.colorPrimary), 0.35f)) | ||||||
|  |  | ||||||
|  | @ -356,8 +356,10 @@ class PlayerFragment : Fragment() { | ||||||
|                                 } |                                 } | ||||||
| 
 | 
 | ||||||
|                                 progressBarLeftHolder?.alpha = 1f |                                 progressBarLeftHolder?.alpha = 1f | ||||||
|                                 val vol = minOf(1f, |                                 val vol = minOf( | ||||||
|                                     cachedVolume - diffY.toFloat() * 0.5f) // 0.05f *if (diffY > 0) 1 else -1 |                                     1f, | ||||||
|  |                                     cachedVolume - diffY.toFloat() * 0.5f | ||||||
|  |                                 ) // 0.05f *if (diffY > 0) 1 else -1 | ||||||
|                                 cachedVolume = vol |                                 cachedVolume = vol | ||||||
|                                 //progressBarRight?.progress = ((1f - alpha) * 100).toInt() |                                 //progressBarRight?.progress = ((1f - alpha) * 100).toInt() | ||||||
| 
 | 
 | ||||||
|  | @ -405,8 +407,10 @@ class PlayerFragment : Fragment() { | ||||||
|                                 progressBarRight?.max = 100 * 100 |                                 progressBarRight?.max = 100 * 100 | ||||||
|                                 progressBarRight?.progress = (alpha * 100 * 100).toInt() |                                 progressBarRight?.progress = (alpha * 100 * 100).toInt() | ||||||
|                             } else { |                             } else { | ||||||
|                                 val alpha = minOf(0.95f, |                                 val alpha = minOf( | ||||||
|                                     brightness_overlay.alpha + diffY.toFloat() * 0.5f) // 0.05f *if (diffY > 0) 1 else -1 |                                     0.95f, | ||||||
|  |                                     brightness_overlay.alpha + diffY.toFloat() * 0.5f | ||||||
|  |                                 ) // 0.05f *if (diffY > 0) 1 else -1 | ||||||
|                                 brightness_overlay?.alpha = alpha |                                 brightness_overlay?.alpha = alpha | ||||||
| 
 | 
 | ||||||
|                                 progressBarRight?.max = 100 * 100 |                                 progressBarRight?.max = 100 * 100 | ||||||
|  | @ -653,6 +657,7 @@ class PlayerFragment : Fragment() { | ||||||
|     private var resizeMode = 0 |     private var resizeMode = 0 | ||||||
|     private var playbackSpeed = 0f |     private var playbackSpeed = 0f | ||||||
|     private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap() |     private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap() | ||||||
|  |     private var allEpisodesSubs: HashMap<Int, ArrayList<SubtitleFile>> = HashMap() | ||||||
|     private var episodes: List<ResultEpisode> = ArrayList() |     private var episodes: List<ResultEpisode> = ArrayList() | ||||||
|     var currentPoster: String? = null |     var currentPoster: String? = null | ||||||
|     var currentHeaderName: String? = null |     var currentHeaderName: String? = null | ||||||
|  | @ -788,8 +793,10 @@ class PlayerFragment : Fragment() { | ||||||
|                             epData.index, |                             epData.index, | ||||||
|                             episodes, |                             episodes, | ||||||
|                             links, |                             links, | ||||||
|  |                             getSubs() ?: ArrayList(), | ||||||
|                             index, |                             index, | ||||||
|                             exoPlayer.currentPosition) |                             exoPlayer.currentPosition | ||||||
|  |                         ) | ||||||
| 
 | 
 | ||||||
|                         /* |                         /* | ||||||
|                         val customData = |                         val customData = | ||||||
|  | @ -904,6 +911,10 @@ class PlayerFragment : Fragment() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         observeDirectly(viewModel.allEpisodesSubs) { _allEpisodesSubs -> | ||||||
|  |             allEpisodesSubs = _allEpisodesSubs | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         observeDirectly(viewModel.resultResponse) { data -> |         observeDirectly(viewModel.resultResponse) { data -> | ||||||
|             when (data) { |             when (data) { | ||||||
|                 is Resource.Success -> { |                 is Resource.Success -> { | ||||||
|  | @ -971,8 +982,10 @@ class PlayerFragment : Fragment() { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         overlay_loading_skip_button.setOnClickListener { |         overlay_loading_skip_button.setOnClickListener { | ||||||
|             setMirrorId(sortUrls(getUrls() ?: return@setOnClickListener).first() |             setMirrorId( | ||||||
|                 .getId()) // BECAUSE URLS CANT BE REORDERED |                 sortUrls(getUrls() ?: return@setOnClickListener).first() | ||||||
|  |                     .getId() | ||||||
|  |             ) // BECAUSE URLS CANT BE REORDERED | ||||||
|             if (!isCurrentlyPlaying) { |             if (!isCurrentlyPlaying) { | ||||||
|                 initPlayer(getCurrentUrl()) |                 initPlayer(getCurrentUrl()) | ||||||
|             } |             } | ||||||
|  | @ -1085,8 +1098,10 @@ class PlayerFragment : Fragment() { | ||||||
|                     builder.setOnDismissListener { |                     builder.setOnDismissListener { | ||||||
|                         activity?.hideSystemUI() |                         activity?.hideSystemUI() | ||||||
|                     } |                     } | ||||||
|                     builder.setSingleChoiceItems(sourcesText.toTypedArray(), |                     builder.setSingleChoiceItems( | ||||||
|                         sources.indexOf(getCurrentUrl())) { _, which -> |                         sourcesText.toTypedArray(), | ||||||
|  |                         sources.indexOf(getCurrentUrl()) | ||||||
|  |                     ) { _, which -> | ||||||
|                         //val speed = speedsText[which] |                         //val speed = speedsText[which] | ||||||
|                         //Toast.makeText(requireContext(), "$speed selected.", Toast.LENGTH_SHORT).show() |                         //Toast.makeText(requireContext(), "$speed selected.", Toast.LENGTH_SHORT).show() | ||||||
|                         playbackPosition = if (this::exoPlayer.isInitialized) exoPlayer.currentPosition else 0 |                         playbackPosition = if (this::exoPlayer.isInitialized) exoPlayer.currentPosition else 0 | ||||||
|  | @ -1156,6 +1171,14 @@ class PlayerFragment : Fragment() { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private fun getSubs(): List<SubtitleFile>? { | ||||||
|  |         return try { | ||||||
|  |             allEpisodesSubs[getEpisode()?.id] | ||||||
|  |         } catch (e: Exception) { | ||||||
|  |             null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private fun getEpisode(): ResultEpisode? { |     private fun getEpisode(): ResultEpisode? { | ||||||
|         return try { |         return try { | ||||||
|             episodes[playerData.episodeIndex] |             episodes[playerData.episodeIndex] | ||||||
|  |  | ||||||
|  | @ -164,7 +164,7 @@ class ResultFragment : Fragment() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// 0 = LOADING, 1 = ERROR LOADING, 2 = LOADED |     /// 0 = LOADING, 1 = ERROR LOADING, 2 = LOADED | ||||||
|     fun updateVisStatus(state: Int) { |     private fun updateVisStatus(state: Int) { | ||||||
|         when (state) { |         when (state) { | ||||||
|             0 -> { |             0 -> { | ||||||
|                 result_loading.visibility = VISIBLE |                 result_loading.visibility = VISIBLE | ||||||
|  | @ -207,10 +207,12 @@ class ResultFragment : Fragment() { | ||||||
|         activity?.fixPaddingStatusbar(result_barstatus) |         activity?.fixPaddingStatusbar(result_barstatus) | ||||||
| 
 | 
 | ||||||
|         val backParameter = result_back.layoutParams as CoordinatorLayout.LayoutParams |         val backParameter = result_back.layoutParams as CoordinatorLayout.LayoutParams | ||||||
|         backParameter.setMargins(backParameter.leftMargin, |         backParameter.setMargins( | ||||||
|  |             backParameter.leftMargin, | ||||||
|             backParameter.topMargin + requireContext().getStatusBarHeight(), |             backParameter.topMargin + requireContext().getStatusBarHeight(), | ||||||
|             backParameter.rightMargin, |             backParameter.rightMargin, | ||||||
|             backParameter.bottomMargin) |             backParameter.bottomMargin | ||||||
|  |         ) | ||||||
|         result_back.layoutParams = backParameter |         result_back.layoutParams = backParameter | ||||||
| 
 | 
 | ||||||
|         if (activity?.isCastApiAvailable() == true) { |         if (activity?.isCastApiAvailable() == true) { | ||||||
|  | @ -299,8 +301,9 @@ class ResultFragment : Fragment() { | ||||||
|                                     currentPoster, |                                     currentPoster, | ||||||
|                                     episodeClick.data.index, |                                     episodeClick.data.index, | ||||||
|                                     eps, |                                     eps, | ||||||
|                                     sortUrls(data.value), |                                     sortUrls(data.value.links), | ||||||
|                                     startTime = episodeClick.data.getRealPosition() |                                     data.value.subs, | ||||||
|  |                                     startTime = episodeClick.data.getRealPosition(), | ||||||
|                                 ) |                                 ) | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  | @ -310,13 +313,18 @@ class ResultFragment : Fragment() { | ||||||
|                 ACTION_PLAY_EPISODE_IN_PLAYER -> { |                 ACTION_PLAY_EPISODE_IN_PLAYER -> { | ||||||
|                     if (buildInPlayer) { |                     if (buildInPlayer) { | ||||||
|                         (requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction() |                         (requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction() | ||||||
|                             .setCustomAnimations(R.anim.enter_anim, |                             .setCustomAnimations( | ||||||
|  |                                 R.anim.enter_anim, | ||||||
|                                 R.anim.exit_anim, |                                 R.anim.exit_anim, | ||||||
|                                 R.anim.pop_enter, |                                 R.anim.pop_enter, | ||||||
|                                 R.anim.pop_exit) |                                 R.anim.pop_exit | ||||||
|                             .add(R.id.homeRoot, |                             ) | ||||||
|                                 PlayerFragment.newInstance(PlayerData(index, null, 0), |                             .add( | ||||||
|                                     episodeClick.data.getRealPosition()) |                                 R.id.homeRoot, | ||||||
|  |                                 PlayerFragment.newInstance( | ||||||
|  |                                     PlayerData(index, null, 0), | ||||||
|  |                                     episodeClick.data.getRealPosition() | ||||||
|  |                                 ) | ||||||
|                             ) |                             ) | ||||||
|                             .commit() |                             .commit() | ||||||
|                     } |                     } | ||||||
|  | @ -333,10 +341,12 @@ class ResultFragment : Fragment() { | ||||||
|                     if (tempUrl != null) { |                     if (tempUrl != null) { | ||||||
|                         viewModel.loadEpisode(episodeClick.data, true) { data -> |                         viewModel.loadEpisode(episodeClick.data, true) { data -> | ||||||
|                             if (data is Resource.Success) { |                             if (data is Resource.Success) { | ||||||
|                                 VideoDownloadManager.DownloadEpisode(requireContext(), |                                 VideoDownloadManager.DownloadEpisode( | ||||||
|  |                                     requireContext(), | ||||||
|                                     tempUrl, |                                     tempUrl, | ||||||
|                                     episodeClick.data, |                                     episodeClick.data, | ||||||
|                                     data.value) |                                     data.value.links | ||||||
|  |                                 ) | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  | @ -447,8 +457,12 @@ class ResultFragment : Fragment() { | ||||||
|                         } |                         } | ||||||
|                         if (d.year != null) metadataInfoArray.add(Pair("Year", d.year.toString())) |                         if (d.year != null) metadataInfoArray.add(Pair("Year", d.year.toString())) | ||||||
|                         val rating = d.rating |                         val rating = d.rating | ||||||
|                         if (rating != null) metadataInfoArray.add(Pair("Rating", |                         if (rating != null) metadataInfoArray.add( | ||||||
|                             "%.1f/10.0".format(rating.toFloat() / 10f).replace(",", "."))) |                             Pair( | ||||||
|  |                                 "Rating", | ||||||
|  |                                 "%.1f/10.0".format(rating.toFloat() / 10f).replace(",", ".") | ||||||
|  |                             ) | ||||||
|  |                         ) | ||||||
|                         val duration = d.duration |                         val duration = d.duration | ||||||
|                         if (duration != null) metadataInfoArray.add(Pair("Duration", duration)) |                         if (duration != null) metadataInfoArray.add(Pair("Duration", duration)) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -70,9 +70,11 @@ class ResultViewModel : ViewModel() { | ||||||
| 
 | 
 | ||||||
|     private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) { |     private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) { | ||||||
|         _episodes.postValue(list) |         _episodes.postValue(list) | ||||||
|         filterEpisodes(context, |         filterEpisodes( | ||||||
|  |             context, | ||||||
|             list, |             list, | ||||||
|             if (selection == -1) context.getResultSeason(localId ?: id.value ?: return) else selection) |             if (selection == -1) context.getResultSeason(localId ?: id.value ?: return) else selection | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun reloadEpisodes(context: Context) { |     fun reloadEpisodes(context: Context) { | ||||||
|  | @ -119,7 +121,8 @@ class ResultViewModel : ViewModel() { | ||||||
|                             if (dataList != null) { |                             if (dataList != null) { | ||||||
|                                 val episodes = ArrayList<ResultEpisode>() |                                 val episodes = ArrayList<ResultEpisode>() | ||||||
|                                 for ((index, i) in dataList.withIndex()) { |                                 for ((index, i) in dataList.withIndex()) { | ||||||
|                                     episodes.add(context.buildResultEpisode( |                                     episodes.add( | ||||||
|  |                                         context.buildResultEpisode( | ||||||
|                                             i.name, |                                             i.name, | ||||||
|                                             i.posterUrl, |                                             i.posterUrl, | ||||||
|                                             index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE |                                             index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE | ||||||
|  | @ -130,7 +133,8 @@ class ResultViewModel : ViewModel() { | ||||||
|                                             index, |                                             index, | ||||||
|                                             i.rating, |                                             i.rating, | ||||||
|                                             i.descript, |                                             i.descript, | ||||||
|                                     )) |                                         ) | ||||||
|  |                                     ) | ||||||
|                                 } |                                 } | ||||||
|                                 updateEpisodes(context, mainId, episodes, -1) |                                 updateEpisodes(context, mainId, episodes, -1) | ||||||
|                             } |                             } | ||||||
|  | @ -139,7 +143,8 @@ class ResultViewModel : ViewModel() { | ||||||
|                         is TvSeriesLoadResponse -> { |                         is TvSeriesLoadResponse -> { | ||||||
|                             val episodes = ArrayList<ResultEpisode>() |                             val episodes = ArrayList<ResultEpisode>() | ||||||
|                             for ((index, i) in d.episodes.withIndex()) { |                             for ((index, i) in d.episodes.withIndex()) { | ||||||
|                                 episodes.add(context.buildResultEpisode( |                                 episodes.add( | ||||||
|  |                                     context.buildResultEpisode( | ||||||
|                                         i.name, |                                         i.name, | ||||||
|                                         //?: (if (i.season != null && i.episode != null) "S${i.season}:E${i.episode}" else null)), // TODO ADD NAMES |                                         //?: (if (i.season != null && i.episode != null) "S${i.season}:E${i.episode}" else null)), // TODO ADD NAMES | ||||||
|                                         i.posterUrl, |                                         i.posterUrl, | ||||||
|  | @ -151,12 +156,15 @@ class ResultViewModel : ViewModel() { | ||||||
|                                         index, |                                         index, | ||||||
|                                         i.rating, |                                         i.rating, | ||||||
|                                         i.descript |                                         i.descript | ||||||
|                                 )) |                                     ) | ||||||
|  |                                 ) | ||||||
|                             } |                             } | ||||||
|                             updateEpisodes(context, mainId, episodes, -1) |                             updateEpisodes(context, mainId, episodes, -1) | ||||||
|                         } |                         } | ||||||
|                         is MovieLoadResponse -> { |                         is MovieLoadResponse -> { | ||||||
|                             updateEpisodes(context, mainId, arrayListOf(context.buildResultEpisode( |                             updateEpisodes( | ||||||
|  |                                 context, mainId, arrayListOf( | ||||||
|  |                                     context.buildResultEpisode( | ||||||
|                                         null, |                                         null, | ||||||
|                                         null, |                                         null, | ||||||
|                                         0, null, |                                         0, null, | ||||||
|  | @ -166,7 +174,9 @@ class ResultViewModel : ViewModel() { | ||||||
|                                         0, |                                         0, | ||||||
|                                         null, |                                         null, | ||||||
|                                         null, |                                         null, | ||||||
|                             )), -1) |                                     ) | ||||||
|  |                                 ), -1 | ||||||
|  |                             ) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -179,17 +189,21 @@ class ResultViewModel : ViewModel() { | ||||||
| 
 | 
 | ||||||
|     private val _allEpisodes: MutableLiveData<HashMap<Int, ArrayList<ExtractorLink>>> = |     private val _allEpisodes: MutableLiveData<HashMap<Int, ArrayList<ExtractorLink>>> = | ||||||
|         MutableLiveData(HashMap()) // LOOKUP BY ID |         MutableLiveData(HashMap()) // LOOKUP BY ID | ||||||
|  |     private val _allEpisodesSubs: MutableLiveData<HashMap<Int, ArrayList<SubtitleFile>>> = | ||||||
|  |         MutableLiveData(HashMap()) // LOOKUP BY ID | ||||||
| 
 | 
 | ||||||
|     val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes |     val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes | ||||||
|  |     val allEpisodesSubs: LiveData<HashMap<Int, ArrayList<SubtitleFile>>> get() = _allEpisodesSubs | ||||||
| 
 | 
 | ||||||
|     private var _apiName: MutableLiveData<String> = MutableLiveData() |     private var _apiName: MutableLiveData<String> = MutableLiveData() | ||||||
|     val apiName: LiveData<String> get() = _apiName |     val apiName: LiveData<String> get() = _apiName | ||||||
| 
 | 
 | ||||||
|  |     data class EpisodeData(val links: ArrayList<ExtractorLink>, val subs: ArrayList<SubtitleFile>) | ||||||
| 
 | 
 | ||||||
|     fun loadEpisode( |     fun loadEpisode( | ||||||
|         episode: ResultEpisode, |         episode: ResultEpisode, | ||||||
|         isCasting: Boolean, |         isCasting: Boolean, | ||||||
|         callback: (Resource<ArrayList<ExtractorLink>>) -> Unit, |         callback: (Resource<EpisodeData>) -> Unit, | ||||||
|     ) { |     ) { | ||||||
|         loadEpisode(episode.id, episode.data, isCasting, callback) |         loadEpisode(episode.id, episode.data, isCasting, callback) | ||||||
|     } |     } | ||||||
|  | @ -198,25 +212,29 @@ class ResultViewModel : ViewModel() { | ||||||
|         id: Int, |         id: Int, | ||||||
|         data: String, |         data: String, | ||||||
|         isCasting: Boolean, |         isCasting: Boolean, | ||||||
|         callback: (Resource<ArrayList<ExtractorLink>>) -> Unit, |         callback: (Resource<EpisodeData>) -> Unit, | ||||||
|     ) = |     ) = | ||||||
|         viewModelScope.launch { |         viewModelScope.launch { | ||||||
|             if (_allEpisodes.value?.contains(id) == true) { |             if (_allEpisodes.value?.contains(id) == true) { | ||||||
|                 _allEpisodes.value?.remove(id) |                 _allEpisodes.value?.remove(id) | ||||||
|             } |             } | ||||||
|             val links = ArrayList<ExtractorLink>() |             val links = ArrayList<ExtractorLink>() | ||||||
|  |             val subs = ArrayList<SubtitleFile>() | ||||||
|             val localData = safeApiCall { |             val localData = safeApiCall { | ||||||
|                 getApiFromName(_apiName.value).loadLinks(data, isCasting,  { subtitleFile ->  }) { |                 getApiFromName(_apiName.value).loadLinks(data, isCasting, { subtitleFile -> | ||||||
|                     for (i in links) { |                     if (!subs.any { it.url == subtitleFile.url }) { | ||||||
|                         if (i.url == it.url) return@loadLinks |                         subs.add(subtitleFile) | ||||||
|  |                         _allEpisodesSubs.value?.set(id, subs) | ||||||
|  |                         _allEpisodesSubs.postValue(_allEpisodesSubs.value) | ||||||
|                     } |                     } | ||||||
| 
 |                 }) { link -> | ||||||
|                     links.add(it) |                     if (!links.any { it.url == link.url }) { | ||||||
|  |                         links.add(link) | ||||||
|                         _allEpisodes.value?.set(id, links) |                         _allEpisodes.value?.set(id, links) | ||||||
|                         _allEpisodes.postValue(_allEpisodes.value) |                         _allEpisodes.postValue(_allEpisodes.value) | ||||||
|                     // _allEpisodes.value?.get(episode.id)?.add(it) |  | ||||||
|                     } |                     } | ||||||
|                 links |                 } | ||||||
|  |                 EpisodeData(links, subs) | ||||||
|             } |             } | ||||||
|             callback.invoke(localData) |             callback.invoke(localData) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -7,15 +7,13 @@ import com.fasterxml.jackson.databind.json.JsonMapper | ||||||
| import com.fasterxml.jackson.module.kotlin.KotlinModule | import com.fasterxml.jackson.module.kotlin.KotlinModule | ||||||
| import com.google.android.exoplayer2.ext.cast.CastPlayer | import com.google.android.exoplayer2.ext.cast.CastPlayer | ||||||
| import com.google.android.exoplayer2.util.MimeTypes | import com.google.android.exoplayer2.util.MimeTypes | ||||||
| import com.google.android.gms.cast.CastStatusCodes | import com.google.android.gms.cast.* | ||||||
| import com.google.android.gms.cast.MediaInfo |  | ||||||
| import com.google.android.gms.cast.MediaMetadata |  | ||||||
| import com.google.android.gms.cast.MediaQueueItem |  | ||||||
| import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF | import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF | ||||||
| import com.google.android.gms.cast.framework.CastContext | import com.google.android.gms.cast.framework.CastContext | ||||||
| import com.google.android.gms.cast.framework.media.RemoteMediaClient | import com.google.android.gms.cast.framework.media.RemoteMediaClient | ||||||
| import com.google.android.gms.common.api.PendingResult | import com.google.android.gms.common.api.PendingResult | ||||||
| import com.google.android.gms.common.images.WebImage | import com.google.android.gms.common.images.WebImage | ||||||
|  | import com.lagradost.cloudstream3.SubtitleFile | ||||||
| import com.lagradost.cloudstream3.ui.MetadataHolder | import com.lagradost.cloudstream3.ui.MetadataHolder | ||||||
| import com.lagradost.cloudstream3.ui.result.ResultEpisode | import com.lagradost.cloudstream3.ui.result.ResultEpisode | ||||||
| import com.lagradost.cloudstream3.utils.Coroutines.main | import com.lagradost.cloudstream3.utils.Coroutines.main | ||||||
|  | @ -27,14 +25,22 @@ object CastHelper { | ||||||
|     private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule()) |     private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule()) | ||||||
|         .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build() |         .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build() | ||||||
| 
 | 
 | ||||||
|     fun getMediaInfo(epData: ResultEpisode, holder: MetadataHolder, index: Int, data: JSONObject?): MediaInfo { |     fun getMediaInfo( | ||||||
|  |         epData: ResultEpisode, | ||||||
|  |         holder: MetadataHolder, | ||||||
|  |         index: Int, | ||||||
|  |         data: JSONObject?, | ||||||
|  |         subtitles: List<SubtitleFile> | ||||||
|  |     ): MediaInfo { | ||||||
|         val link = holder.currentLinks[index] |         val link = holder.currentLinks[index] | ||||||
|         val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) |         val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) | ||||||
|         movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, |         movieMetadata.putString( | ||||||
|  |             MediaMetadata.KEY_SUBTITLE, | ||||||
|             if (holder.isMovie) |             if (holder.isMovie) | ||||||
|                 link.name |                 link.name | ||||||
|             else |             else | ||||||
|                 (epData.name ?: "Episode ${epData.episode}") + " - ${link.name}") |                 (epData.name ?: "Episode ${epData.episode}") + " - ${link.name}" | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         movieMetadata.putString(MediaMetadata.KEY_TITLE, holder.title) |         movieMetadata.putString(MediaMetadata.KEY_TITLE, holder.title) | ||||||
| 
 | 
 | ||||||
|  | @ -43,11 +49,21 @@ object CastHelper { | ||||||
|             movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) |             movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         var subIndex = 0 | ||||||
|  |         val tracks = subtitles.map { | ||||||
|  |             MediaTrack.Builder(subIndex++.toLong(), MediaTrack.TYPE_TEXT) | ||||||
|  |                 .setName(it.lang) | ||||||
|  |                 .setSubtype(MediaTrack.SUBTYPE_SUBTITLES) | ||||||
|  |                 .setContentId(it.url) | ||||||
|  |                 .build() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return MediaInfo.Builder(link.url) |         return MediaInfo.Builder(link.url) | ||||||
|             .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) |             .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) | ||||||
|             .setContentType(MimeTypes.VIDEO_UNKNOWN) |             .setContentType(MimeTypes.VIDEO_UNKNOWN) | ||||||
|             .setCustomData(data) |             .setCustomData(data) | ||||||
|             .setMetadata(movieMetadata) |             .setMetadata(movieMetadata) | ||||||
|  |             .setMediaTracks(tracks) | ||||||
|             .build() |             .build() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -76,39 +92,48 @@ object CastHelper { | ||||||
|         currentEpisodeIndex: Int, |         currentEpisodeIndex: Int, | ||||||
|         episodes: List<ResultEpisode>, |         episodes: List<ResultEpisode>, | ||||||
|         currentLinks: List<ExtractorLink>, |         currentLinks: List<ExtractorLink>, | ||||||
|  |         subtitles: List<SubtitleFile>, | ||||||
|         startIndex: Int? = null, |         startIndex: Int? = null, | ||||||
|         startTime: Long? = null, |         startTime: Long? = null, | ||||||
|     ) { |     ) : Boolean { | ||||||
|         if (episodes.isEmpty()) return |         if (episodes.isEmpty()) return false | ||||||
|  |         if (currentLinks.size <= currentEpisodeIndex) return false | ||||||
| 
 | 
 | ||||||
|         val castContext = CastContext.getSharedInstance(this) |         val castContext = CastContext.getSharedInstance(this) | ||||||
| 
 | 
 | ||||||
|         val epData = episodes[currentEpisodeIndex] |         val epData = episodes[currentEpisodeIndex] | ||||||
| 
 | 
 | ||||||
|         val holder = MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks) |         val holder = | ||||||
|  |             MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks, subtitles) | ||||||
| 
 | 
 | ||||||
|         val index = startIndex ?: 0 |         val index = startIndex ?: 0 | ||||||
|         val mediaItem = |         val mediaItem = | ||||||
|             getMediaInfo(epData, holder, index, JSONObject(mapper.writeValueAsString(holder))) |             getMediaInfo(epData, holder, index, JSONObject(mapper.writeValueAsString(holder)), subtitles) | ||||||
| 
 | 
 | ||||||
|         val castPlayer = CastPlayer(castContext) |         val castPlayer = CastPlayer(castContext) | ||||||
| 
 | 
 | ||||||
|         castPlayer.repeatMode = REPEAT_MODE_REPEAT_OFF |         castPlayer.repeatMode = REPEAT_MODE_REPEAT_OFF | ||||||
| 
 | 
 | ||||||
|         awaitLinks(castPlayer.loadItem( |         awaitLinks( | ||||||
|  |             castPlayer.loadItem( | ||||||
|                 MediaQueueItem.Builder(mediaItem).build(), |                 MediaQueueItem.Builder(mediaItem).build(), | ||||||
|                 startTime ?: 0, |                 startTime ?: 0, | ||||||
|         )) { |             ) | ||||||
|  |         ) { | ||||||
|             if (currentLinks.size > index + 1) |             if (currentLinks.size > index + 1) | ||||||
|                 startCast(apiName, |                 startCast( | ||||||
|  |                     apiName, | ||||||
|                     isMovie, |                     isMovie, | ||||||
|                     title, |                     title, | ||||||
|                     poster, |                     poster, | ||||||
|                     currentEpisodeIndex, |                     currentEpisodeIndex, | ||||||
|                     episodes, |                     episodes, | ||||||
|                     currentLinks, |                     currentLinks, | ||||||
|  |                     subtitles, | ||||||
|                     index + 1, |                     index + 1, | ||||||
|                     startTime) |                     startTime | ||||||
|  |                 ) | ||||||
|         } |         } | ||||||
|  |         return true | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -91,6 +91,6 @@ object DataStore { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     inline fun <reified T : Any> Context.getKey(folder: String, path: String, defVal: T?): T? { |     inline fun <reified T : Any> Context.getKey(folder: String, path: String, defVal: T?): T? { | ||||||
|         return getKey(getFolderName(folder, path), defVal) |         return getKey(getFolderName(folder, path), defVal) ?: defVal | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,29 +1,17 @@ | ||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <LinearLayout | <LinearLayout | ||||||
|         xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|         xmlns:tools="http://schemas.android.com/tools" |         xmlns:tools="http://schemas.android.com/tools" | ||||||
|         android:orientation="vertical" |         android:orientation="vertical" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:background="@null" |         android:background="@null" | ||||||
|         android:layout_height="match_parent"> |         android:layout_height="match_parent"> | ||||||
|     <!--<androidx.cardview.widget.CardView |  | ||||||
|             app:cardCornerRadius="10dp" |  | ||||||
|             android:backgroundTint="?attr/boxItemBackground" |  | ||||||
|             android:layout_width="match_parent" android:layout_height="wrap_content"> |  | ||||||
|         <TextView |  | ||||||
|                 style="@style/AppTextViewStyle" |  | ||||||
|                 android:gravity="center_vertical" |  | ||||||
| 
 | 
 | ||||||
|                 android:paddingTop="5dp" |     <LinearLayout | ||||||
|                 android:paddingBottom="5dp" |             android:layout_width="match_parent" | ||||||
|                 android:paddingLeft="20dp" |             android:layout_height="0dp" | ||||||
|                 android:paddingRight="20dp" |             android:orientation="vertical" | ||||||
|                 android:layout_gravity="center_vertical" |             android:layout_weight="50"> | ||||||
|                 android:text="@string/pick_source" android:textColor="?attr/textColor" android:textSize="18sp" |  | ||||||
|                 android:layout_width="wrap_content" android:layout_height="55dp" |  | ||||||
|         > |  | ||||||
|         </TextView> |  | ||||||
|     </androidx.cardview.widget.CardView>--> |  | ||||||
|         <TextView |         <TextView | ||||||
|                 android:paddingStart="?android:attr/listPreferredItemPaddingStart" |                 android:paddingStart="?android:attr/listPreferredItemPaddingStart" | ||||||
|                 android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" |                 android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" | ||||||
|  | @ -34,15 +22,48 @@ | ||||||
|                 android:textSize="20sp" |                 android:textSize="20sp" | ||||||
|                 android:textColor="?attr/textColor" |                 android:textColor="?attr/textColor" | ||||||
|                 android:layout_width="match_parent" |                 android:layout_width="match_parent" | ||||||
|  |                 android:layout_rowWeight="1" | ||||||
|                 android:layout_height="wrap_content"> |                 android:layout_height="wrap_content"> | ||||||
|         </TextView> |         </TextView> | ||||||
|         <ListView |         <ListView | ||||||
|                 android:layout_marginTop="-10dp" |                 android:layout_marginTop="-10dp" | ||||||
|                 android:paddingTop="10dp" |                 android:paddingTop="10dp" | ||||||
|             android:id="@+id/sort_click" |                 android:id="@+id/sort_providers" | ||||||
|                 android:background="?attr/bitDarkerGrayBackground" |                 android:background="?attr/bitDarkerGrayBackground" | ||||||
|                 tools:listitem="@layout/sort_bottom_single_choice" |                 tools:listitem="@layout/sort_bottom_single_choice" | ||||||
|                 android:layout_width="match_parent" |                 android:layout_width="match_parent" | ||||||
|  |                 android:layout_height="match_parent" | ||||||
|  |                 android:layout_rowWeight="1" | ||||||
|  |         /> | ||||||
|  |     </LinearLayout> | ||||||
|  |     <LinearLayout | ||||||
|  |             android:id="@+id/sort_subtitles_holder" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="0dp" | ||||||
|  |             android:orientation="vertical" | ||||||
|  |             android:layout_weight="50"> | ||||||
|  |         <TextView | ||||||
|  |                 android:paddingStart="?android:attr/listPreferredItemPaddingStart" | ||||||
|  |                 android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" | ||||||
|  |                 android:layout_marginTop="20dp" | ||||||
|  |                 android:layout_marginBottom="10dp" | ||||||
|  |                 android:textStyle="bold" | ||||||
|  |                 android:text="@string/pick_subtitle" | ||||||
|  |                 android:textSize="20sp" | ||||||
|  |                 android:textColor="?attr/textColor" | ||||||
|  |                 android:layout_rowWeight="1" | ||||||
|  |                 android:layout_width="match_parent" | ||||||
|                 android:layout_height="wrap_content"> |                 android:layout_height="wrap_content"> | ||||||
|  |         </TextView> | ||||||
|  |         <ListView | ||||||
|  |                 android:layout_marginTop="-10dp" | ||||||
|  |                 android:paddingTop="10dp" | ||||||
|  |                 android:id="@+id/sort_subtitles" | ||||||
|  |                 android:background="?attr/bitDarkerGrayBackground" | ||||||
|  |                 tools:listitem="@layout/sort_bottom_single_choice" | ||||||
|  |                 android:layout_width="match_parent" | ||||||
|  |                 android:layout_rowWeight="1" | ||||||
|  |                 android:layout_height="match_parent"> | ||||||
|         </ListView> |         </ListView> | ||||||
|  |     </LinearLayout> | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|  |  | ||||||
|  | @ -31,7 +31,8 @@ | ||||||
|     <string name="type_plan_to_watch">Plan to Watch</string> |     <string name="type_plan_to_watch">Plan to Watch</string> | ||||||
|     <string name="type_none">None</string> |     <string name="type_none">None</string> | ||||||
|     <string name="play_movie_button">Play Movie</string> |     <string name="play_movie_button">Play Movie</string> | ||||||
|     <string name="pick_source">Pick Source</string> |     <string name="pick_source">Sources</string> | ||||||
|  |     <string name="pick_subtitle">Subtitles</string> | ||||||
|     <string name="reload_error">Retry connection…</string> |     <string name="reload_error">Retry connection…</string> | ||||||
|     <string name="result_go_back">Go Back</string> |     <string name="result_go_back">Go Back</string> | ||||||
|     <string name="episode_poster">Episode Poster</string> |     <string name="episode_poster">Episode Poster</string> | ||||||
|  |  | ||||||
|  | @ -118,6 +118,15 @@ | ||||||
|     <style name="AlertDialogCustomTransparent" parent="Theme.AppCompat.Dialog.Alert"> |     <style name="AlertDialogCustomTransparent" parent="Theme.AppCompat.Dialog.Alert"> | ||||||
|         <item name="android:windowBackground">@color/transparent</item> |         <item name="android:windowBackground">@color/transparent</item> | ||||||
|     </style> |     </style> | ||||||
|  |     <style name="AlertDialogCustomBlack" parent="Theme.AppCompat.Dialog.Alert"> | ||||||
|  |         <item name="android:windowBackground">@color/bitDarkerGrayBackground</item> | ||||||
|  |         <item name="android:layout_width">fill_parent</item> | ||||||
|  |         <item name="android:layout_height">fill_parent</item> | ||||||
|  |         <!-- No backgrounds, titles or window float --> | ||||||
|  |         <item name="android:windowNoTitle">true</item> | ||||||
|  |         <item name="android:windowIsFloating">false</item> | ||||||
|  |         <item name="android:navigationBarColor">@color/bitDarkerGrayBackground</item> | ||||||
|  |     </style> | ||||||
| 
 | 
 | ||||||
|     <style name="PopupMenu" parent="@android:style/Widget.PopupMenu"> |     <style name="PopupMenu" parent="@android:style/Widget.PopupMenu"> | ||||||
|         <item name="android:backgroundTint">?attr/bitDarkerGrayBackground</item> |         <item name="android:backgroundTint">?attr/bitDarkerGrayBackground</item> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue