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…
Reference in a new issue