forked from recloudstream/cloudstream
Merge pull request #2 from recloudstream/merge-reduplicated
This commit is contained in:
commit
3a2b7e8639
73 changed files with 4673 additions and 2376 deletions
|
@ -36,7 +36,7 @@ android {
|
|||
targetSdkVersion 30
|
||||
|
||||
versionCode 50
|
||||
versionName "3.0.2"
|
||||
versionName "3.1.2"
|
||||
|
||||
resValue "string", "app_version",
|
||||
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"
|
||||
|
|
|
@ -260,10 +260,10 @@ object CommonActivity {
|
|||
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_REWIND -> {
|
||||
PlayerEventType.SeekBack
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1 -> {
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_N -> {
|
||||
PlayerEventType.NextEpisode
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1 -> {
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_B -> {
|
||||
PlayerEventType.PrevEpisode
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
|
||||
|
@ -294,6 +294,9 @@ object CommonActivity {
|
|||
KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0 -> {
|
||||
PlayerEventType.Resize
|
||||
}
|
||||
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4 -> {
|
||||
PlayerEventType.SkipOp
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
|
||||
PlayerEventType.PlayPauseToggle
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.lagradost.cloudstream3.ui.player.SubtitleData
|
|||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import okhttp3.Interceptor
|
||||
import java.text.SimpleDateFormat
|
||||
|
@ -80,7 +81,7 @@ object APIHolder {
|
|||
return null
|
||||
}
|
||||
|
||||
fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
|
||||
private fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
|
||||
return url.replace(getApiFromName(apiName).mainUrl, "").replace("/", "").hashCode()
|
||||
}
|
||||
|
||||
|
@ -527,6 +528,7 @@ enum class ShowStatus {
|
|||
}
|
||||
|
||||
enum class DubStatus(val id: Int) {
|
||||
None(-1),
|
||||
Dubbed(1),
|
||||
Subbed(0),
|
||||
}
|
||||
|
@ -861,6 +863,10 @@ interface LoadResponse {
|
|||
private val aniListIdPrefix = aniListApi.idPrefix
|
||||
var isTrailersEnabled = true
|
||||
|
||||
fun LoadResponse.isMovie(): Boolean {
|
||||
return this.type.isMovieType()
|
||||
}
|
||||
|
||||
@JvmName("addActorNames")
|
||||
fun LoadResponse.addActors(actors: List<String>?) {
|
||||
this.actors = actors?.map { ActorData(Actor(it)) }
|
||||
|
@ -902,25 +908,71 @@ interface LoadResponse {
|
|||
}
|
||||
|
||||
/**better to call addTrailer with mutible trailers directly instead of calling this multiple times*/
|
||||
suspend fun LoadResponse.addTrailer(trailerUrl: String?, referer: String? = null) {
|
||||
if (!isTrailersEnabled || trailerUrl == null) return
|
||||
suspend fun LoadResponse.addTrailer(
|
||||
trailerUrl: String?,
|
||||
referer: String? = null,
|
||||
addRaw: Boolean = false
|
||||
) {
|
||||
if (!isTrailersEnabled || trailerUrl.isNullOrBlank()) return
|
||||
val links = arrayListOf<ExtractorLink>()
|
||||
val subs = arrayListOf<SubtitleFile>()
|
||||
loadExtractor(trailerUrl, referer, { subs.add(it) }, { links.add(it) })
|
||||
this.trailers.add(TrailerData(links, subs))
|
||||
if (!loadExtractor(
|
||||
trailerUrl,
|
||||
referer,
|
||||
{ subs.add(it) },
|
||||
{ links.add(it) }) && addRaw
|
||||
) {
|
||||
this.trailers.add(
|
||||
TrailerData(
|
||||
listOf(
|
||||
ExtractorLink(
|
||||
"",
|
||||
"Trailer",
|
||||
trailerUrl,
|
||||
referer ?: "",
|
||||
Qualities.Unknown.value,
|
||||
trailerUrl.contains(".m3u8")
|
||||
)
|
||||
), listOf()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
this.trailers.add(TrailerData(links, subs))
|
||||
}
|
||||
}
|
||||
|
||||
fun LoadResponse.addTrailer(newTrailers: List<ExtractorLink>) {
|
||||
trailers.addAll(newTrailers.map { TrailerData(listOf(it)) })
|
||||
}
|
||||
|
||||
suspend fun LoadResponse.addTrailer(trailerUrls: List<String>?, referer: String? = null) {
|
||||
suspend fun LoadResponse.addTrailer(
|
||||
trailerUrls: List<String>?,
|
||||
referer: String? = null,
|
||||
addRaw: Boolean = false
|
||||
) {
|
||||
if (!isTrailersEnabled || trailerUrls == null) return
|
||||
val trailers = trailerUrls.apmap { trailerUrl ->
|
||||
val trailers = trailerUrls.filter { it.isNotBlank() }.apmap { trailerUrl ->
|
||||
val links = arrayListOf<ExtractorLink>()
|
||||
val subs = arrayListOf<SubtitleFile>()
|
||||
loadExtractor(trailerUrl, referer, { subs.add(it) }, { links.add(it) })
|
||||
links to subs
|
||||
if (!loadExtractor(
|
||||
trailerUrl,
|
||||
referer,
|
||||
{ subs.add(it) },
|
||||
{ links.add(it) }) && addRaw
|
||||
) {
|
||||
arrayListOf(
|
||||
ExtractorLink(
|
||||
"",
|
||||
"Trailer",
|
||||
trailerUrl,
|
||||
referer ?: "",
|
||||
Qualities.Unknown.value,
|
||||
trailerUrl.contains(".m3u8")
|
||||
)
|
||||
) to arrayListOf()
|
||||
} else {
|
||||
links to subs
|
||||
}
|
||||
}.map { (links, subs) -> TrailerData(links, subs) }
|
||||
this.trailers.addAll(trailers)
|
||||
}
|
||||
|
@ -1001,6 +1053,7 @@ data class NextAiring(
|
|||
data class SeasonData(
|
||||
val season: Int,
|
||||
val name: String? = null,
|
||||
val displaySeason: Int? = null, // will use season if null
|
||||
)
|
||||
|
||||
interface EpisodeResponse {
|
||||
|
|
|
@ -151,7 +151,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
|
||||
// Fucks up anime info layout since that has its own layout
|
||||
cast_mini_controller_holder?.isVisible =
|
||||
!listOf(R.id.navigation_results, R.id.navigation_player).contains(destination.id)
|
||||
!listOf(
|
||||
R.id.navigation_results_phone,
|
||||
R.id.navigation_results_tv,
|
||||
R.id.navigation_player
|
||||
).contains(destination.id)
|
||||
|
||||
val isNavVisible = listOf(
|
||||
R.id.navigation_home,
|
||||
|
@ -327,6 +331,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
if (str.startsWith("https://cs.repo")) {
|
||||
val realUrl = "https://" + str.substringAfter("?")
|
||||
println("Repository url: $realUrl")
|
||||
val activity = this
|
||||
ioSafe {
|
||||
val repo = RepositoryManager.parseRepository(realUrl) ?: return@ioSafe
|
||||
RepositoryManager.addRepository(
|
||||
|
@ -337,8 +342,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
)
|
||||
main {
|
||||
showToast(
|
||||
this,
|
||||
this.getString(R.string.player_loaded_subtitles, repo.name),
|
||||
activity,
|
||||
getString(R.string.player_loaded_subtitles, repo.name),
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
|
@ -346,6 +351,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
} else if (str.contains(appString)) {
|
||||
for (api in OAuth2Apis) {
|
||||
if (str.contains("/${api.redirectUrl}")) {
|
||||
val activity = this
|
||||
ioSafe {
|
||||
Log.i(TAG, "handleAppIntent $str")
|
||||
val isSuccessful = api.handleRedirect(str)
|
||||
|
@ -356,10 +362,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
Log.i(TAG, "failed to authenticate ${api.name}")
|
||||
}
|
||||
|
||||
this.runOnUiThread {
|
||||
activity.runOnUiThread {
|
||||
try {
|
||||
showToast(
|
||||
this,
|
||||
activity,
|
||||
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
|
||||
api.name
|
||||
)
|
||||
|
|
|
@ -51,6 +51,32 @@ fun <T> LifecycleOwner.observeDirectly(liveData: LiveData<T>, action: (t: T) ->
|
|||
action(currentValue)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> some(value: T?): Some<T> {
|
||||
return if (value == null) {
|
||||
Some.None
|
||||
} else {
|
||||
Some.Success(value)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Some<out T> {
|
||||
data class Success<out T>(val value: T) : Some<T>()
|
||||
object None : Some<Nothing>()
|
||||
|
||||
override fun toString(): String {
|
||||
return when(this) {
|
||||
is None -> "None"
|
||||
is Success -> "Some(${value.toString()})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ResourceSome<out T> {
|
||||
data class Success<out T>(val value: T) : ResourceSome<T>()
|
||||
object None : ResourceSome<Nothing>()
|
||||
data class Loading(val data: Any? = null) : ResourceSome<Nothing>()
|
||||
}
|
||||
|
||||
sealed class Resource<out T> {
|
||||
data class Success<out T>(val value: T) : Resource<T>()
|
||||
data class Failure(
|
||||
|
|
|
@ -31,6 +31,8 @@ class APIRepository(val api: MainAPI) {
|
|||
val mainUrl = api.mainUrl
|
||||
val mainPage = api.mainPage
|
||||
val hasQuickSearch = api.hasQuickSearch
|
||||
val vpnStatus = api.vpnStatus
|
||||
val providerType = api.providerType
|
||||
|
||||
suspend fun load(url: String): Resource<LoadResponse> {
|
||||
return safeApiCall {
|
||||
|
|
|
@ -18,7 +18,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
|
||||
object DownloadButtonSetup {
|
||||
fun handleDownloadClick(activity: Activity?, headerName: String?, click: DownloadClickEvent) {
|
||||
fun handleDownloadClick(activity: Activity?, click: DownloadClickEvent) {
|
||||
val id = click.data.id
|
||||
if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return
|
||||
when (click.action) {
|
||||
|
|
|
@ -84,7 +84,7 @@ class DownloadChildFragment : Fragment() {
|
|||
DownloadChildAdapter(
|
||||
ArrayList(),
|
||||
) { click ->
|
||||
handleDownloadClick(activity, name, click)
|
||||
handleDownloadClick(activity, click)
|
||||
}
|
||||
|
||||
downloadDeleteEventListener = { id: Int ->
|
||||
|
|
|
@ -153,7 +153,7 @@ class DownloadFragment : Fragment() {
|
|||
},
|
||||
{ downloadClickEvent ->
|
||||
if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter
|
||||
handleDownloadClick(activity, downloadClickEvent.data.name, downloadClickEvent)
|
||||
handleDownloadClick(activity, downloadClickEvent)
|
||||
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||
context?.let { ctx ->
|
||||
downloadsViewModel.updateList(ctx)
|
||||
|
|
|
@ -98,7 +98,7 @@ class ParentItemAdapter(
|
|||
recyclerView?.apply {
|
||||
// this loops every viewHolder in the recycle view and checks the position to see if it is within the update range
|
||||
val missingUpdates = (position until (position + count)).toMutableSet()
|
||||
for (i in 0 until mAdapter.itemCount) {
|
||||
for (i in 0 until itemCount) {
|
||||
val viewHolder = getChildViewHolder(getChildAt(i))
|
||||
val absolutePosition = viewHolder.absoluteAdapterPosition
|
||||
if (absolutePosition >= position && absolutePosition < position + count) {
|
||||
|
|
|
@ -1164,6 +1164,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
openOnlineSubPicker(view.context, null) {}
|
||||
}
|
||||
}
|
||||
PlayerEventType.SkipOp -> {
|
||||
skipOp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
private fun loadExtractorJob(extractorLink: ExtractorLink?) {
|
||||
currentVerifyLink?.cancel()
|
||||
|
||||
extractorLink?.let {
|
||||
currentVerifyLink = ioSafe {
|
||||
if (it.extractorData != null) {
|
||||
|
@ -488,7 +489,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
.setView(R.layout.player_select_source_and_subs)
|
||||
|
||||
val sourceDialog = sourceBuilder.create()
|
||||
|
||||
selectSourceDialog = sourceDialog
|
||||
|
||||
sourceDialog.show()
|
||||
val providerList = sourceDialog.sort_providers
|
||||
val subtitleList = sourceDialog.sort_subtitles
|
||||
|
|
|
@ -23,6 +23,7 @@ enum class PlayerEventType(val value: Int) {
|
|||
ShowMirrors(12),
|
||||
Resize(13),
|
||||
SearchSubtitlesOnline(14),
|
||||
SkipOp(15),
|
||||
}
|
||||
|
||||
enum class CSPlayerEvent(val value: Int) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.annotation.LayoutRes
|
|||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.R
|
||||
|
@ -56,11 +57,11 @@ const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
|
|||
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
||||
|
||||
class EpisodeAdapter(
|
||||
var cardList: List<ResultEpisode>,
|
||||
private val hasDownloadSupport: Boolean,
|
||||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
private var cardList: MutableList<ResultEpisode> = mutableListOf()
|
||||
|
||||
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
||||
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
||||
|
@ -74,6 +75,9 @@ class EpisodeAdapter(
|
|||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||
if(holder.itemView.hasFocus()) {
|
||||
holder.itemView.clearFocus()
|
||||
}
|
||||
if (holder is DownloadButtonViewHolder) {
|
||||
holder.downloadButton.dispose()
|
||||
}
|
||||
|
@ -92,15 +96,19 @@ class EpisodeAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
private var layout: Int = 0
|
||||
fun updateLayout() {
|
||||
// layout =
|
||||
// if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
||||
// R.layout.result_episode_large
|
||||
// else R.layout.result_episode
|
||||
fun updateList(newList: List<ResultEpisode>) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
ResultDiffCallback(this.cardList, newList)
|
||||
)
|
||||
|
||||
cardList.clear()
|
||||
cardList.addAll(newList)
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
var layout = R.layout.result_episode_both
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
|
||||
R.layout.result_episode_large
|
||||
|
@ -108,7 +116,7 @@ class EpisodeAdapter(
|
|||
|
||||
return EpisodeCardViewHolder(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.result_episode_both, parent, false),
|
||||
.inflate(layout, parent, false),
|
||||
hasDownloadSupport,
|
||||
clickCallback,
|
||||
downloadClickCallback
|
||||
|
@ -146,6 +154,8 @@ class EpisodeAdapter(
|
|||
fun bind(card: ResultEpisode) {
|
||||
localCard = card
|
||||
|
||||
val isTrueTv = itemView.context?.isTrueTvSettings() == true
|
||||
|
||||
val (parentView, otherView) = if (card.poster == null) {
|
||||
itemView.episode_holder to itemView.episode_holder_large
|
||||
} else {
|
||||
|
@ -196,20 +206,22 @@ class EpisodeAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
episodePoster?.setOnClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||
}
|
||||
if (!isTrueTv) {
|
||||
episodePoster?.setOnClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||
}
|
||||
|
||||
episodePoster?.setOnLongClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
|
||||
return@setOnLongClickListener true
|
||||
episodePoster?.setOnLongClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
parentView.setOnClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||
}
|
||||
|
||||
if (parentView.context.isTrueTvSettings()) {
|
||||
if (isTrueTv) {
|
||||
parentView.isFocusable = true
|
||||
parentView.isFocusableInTouchMode = true
|
||||
parentView.touchscreenBlocksFocus = false
|
||||
|
@ -263,3 +275,19 @@ class EpisodeAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ResultDiffCallback(
|
||||
private val oldList: List<ResultEpisode>,
|
||||
private val newList: List<ResultEpisode>
|
||||
) :
|
||||
DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition].id == newList[newItemPosition].id
|
||||
|
||||
override fun getOldListSize() = oldList.size
|
||||
|
||||
override fun getNewListSize() = newList.size
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,401 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.app.Dialog
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import com.discord.panels.OverlappingPanelsLayout
|
||||
import com.discord.panels.PanelsChildGestureRegionObserver
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||
import kotlinx.android.synthetic.main.fragment_result.*
|
||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||
import kotlinx.android.synthetic.main.fragment_trailer.*
|
||||
import kotlinx.android.synthetic.main.result_recommendations.*
|
||||
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
||||
|
||||
class ResultFragmentPhone : ResultFragment() {
|
||||
var currentTrailers: List<ExtractorLink> = emptyList()
|
||||
var currentTrailerIndex = 0
|
||||
|
||||
override fun nextMirror() {
|
||||
currentTrailerIndex++
|
||||
loadTrailer()
|
||||
}
|
||||
|
||||
override fun hasNextMirror(): Boolean {
|
||||
return currentTrailerIndex + 1 < currentTrailers.size
|
||||
}
|
||||
|
||||
override fun playerError(exception: Exception) {
|
||||
if (player.getIsPlaying()) { // because we dont want random toasts in player
|
||||
super.playerError(exception)
|
||||
} else {
|
||||
nextMirror()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadTrailer(index: Int? = null) {
|
||||
val isSuccess =
|
||||
currentTrailers.getOrNull(index ?: currentTrailerIndex)?.let { trailer ->
|
||||
context?.let { ctx ->
|
||||
player.onPause()
|
||||
player.loadPlayer(
|
||||
ctx,
|
||||
false,
|
||||
trailer,
|
||||
null,
|
||||
startPosition = 0L,
|
||||
subtitles = emptySet(),
|
||||
subtitle = null,
|
||||
autoPlay = false
|
||||
)
|
||||
true
|
||||
} ?: run {
|
||||
false
|
||||
}
|
||||
} ?: run {
|
||||
false
|
||||
}
|
||||
result_trailer_loading?.isVisible = isSuccess
|
||||
result_smallscreen_holder?.isVisible = !isSuccess && !isFullScreenPlayer
|
||||
|
||||
// We don't want the trailer to be focusable if it's not visible
|
||||
result_smallscreen_holder?.descendantFocusability = if (isSuccess) {
|
||||
ViewGroup.FOCUS_AFTER_DESCENDANTS
|
||||
} else {
|
||||
ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
||||
}
|
||||
result_fullscreen_holder?.isVisible = !isSuccess && isFullScreenPlayer
|
||||
}
|
||||
|
||||
override fun setTrailers(trailers: List<ExtractorLink>?) {
|
||||
context?.updateHasTrailers()
|
||||
if (!LoadResponse.isTrailersEnabled) return
|
||||
currentTrailers = trailers?.sortedBy { -it.quality } ?: emptyList()
|
||||
loadTrailer()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
//somehow this still leaks and I dont know why????
|
||||
// todo look at https://github.com/discord/OverlappingPanels/blob/70b4a7cf43c6771873b1e091029d332896d41a1a/sample_app/src/main/java/com/discord/sampleapp/MainActivity.kt
|
||||
PanelsChildGestureRegionObserver.Provider.get().let { obs ->
|
||||
result_cast_items?.let {
|
||||
obs.unregister(it)
|
||||
}
|
||||
obs.removeGestureRegionsUpdateListener(this)
|
||||
}
|
||||
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
var loadingDialog: Dialog? = null
|
||||
var popupDialog: Dialog? = null
|
||||
|
||||
/**
|
||||
* Sets next focus to allow navigation up and down between 2 views
|
||||
* if either of them is null nothing happens.
|
||||
**/
|
||||
private fun setFocusUpAndDown(upper: View?, down: View?) {
|
||||
if (upper == null || down == null) return
|
||||
upper.nextFocusDownId = down.id
|
||||
down.nextFocusUpId = upper.id
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
player_open_source?.setOnClickListener {
|
||||
currentTrailers.getOrNull(currentTrailerIndex)?.let {
|
||||
context?.openBrowser(it.url)
|
||||
}
|
||||
}
|
||||
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
||||
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
||||
|
||||
result_recommendations?.spanCount = 3
|
||||
result_recommendations?.adapter =
|
||||
SearchAdapter(
|
||||
ArrayList(),
|
||||
result_recommendations,
|
||||
) { callback ->
|
||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
||||
}
|
||||
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
|
||||
|
||||
result_cast_items?.let {
|
||||
PanelsChildGestureRegionObserver.Provider.get().register(it)
|
||||
}
|
||||
|
||||
|
||||
result_back?.setOnClickListener {
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
|
||||
result_bookmark_button?.setOnClickListener {
|
||||
it.popupMenuNoIcons(
|
||||
items = WatchType.values()
|
||||
.map { watchType -> Pair(watchType.internalId, watchType.stringRes) },
|
||||
//.map { watchType -> Triple(watchType.internalId, watchType.iconRes, watchType.stringRes) },
|
||||
) {
|
||||
viewModel.updateWatchStatus(WatchType.fromInternalId(this.itemId))
|
||||
}
|
||||
}
|
||||
|
||||
result_mini_sync?.adapter = ImageAdapter(
|
||||
R.layout.result_mini_image,
|
||||
nextFocusDown = R.id.result_sync_set_score,
|
||||
clickCallback = { action ->
|
||||
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
|
||||
if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
|
||||
result_overlapping_panels?.openStartPanel()
|
||||
} else {
|
||||
result_overlapping_panels?.closePanels()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
result_scroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
val dy = scrollY - oldScrollY
|
||||
if (dy > 0) { //check for scroll down
|
||||
result_bookmark_fab?.shrink()
|
||||
} else if (dy < -5) {
|
||||
result_bookmark_fab?.extend()
|
||||
}
|
||||
if (!isFullScreenPlayer && player.getIsPlaying()) {
|
||||
if (scrollY > (player_background?.height ?: scrollY)) {
|
||||
player.handleEvent(CSPlayerEvent.Pause)
|
||||
}
|
||||
}
|
||||
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
|
||||
})
|
||||
|
||||
observe(viewModel.episodesCountText) { count ->
|
||||
result_episodes_text.setText(count)
|
||||
}
|
||||
|
||||
observe(viewModel.selectPopup) { popup ->
|
||||
when (popup) {
|
||||
is Some.Success -> {
|
||||
popupDialog?.dismissSafe(activity)
|
||||
|
||||
popupDialog = activity?.let { act ->
|
||||
val pop = popup.value
|
||||
val options = pop.getOptions(act)
|
||||
val title = pop.getTitle(act)
|
||||
|
||||
act.showBottomDialogInstant(
|
||||
options, title, {
|
||||
popupDialog = null
|
||||
pop.callback(null)
|
||||
}, {
|
||||
popupDialog = null
|
||||
pop.callback(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is Some.None -> {
|
||||
popupDialog?.dismissSafe(activity)
|
||||
popupDialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.loadedLinks) { load ->
|
||||
when (load) {
|
||||
is Some.Success -> {
|
||||
if (loadingDialog?.isShowing != true) {
|
||||
loadingDialog?.dismissSafe(activity)
|
||||
loadingDialog = null
|
||||
}
|
||||
loadingDialog = loadingDialog ?: context?.let { ctx ->
|
||||
val builder =
|
||||
BottomSheetDialog(ctx)
|
||||
builder.setContentView(R.layout.bottom_loading)
|
||||
builder.setOnDismissListener {
|
||||
loadingDialog = null
|
||||
viewModel.cancelLinks()
|
||||
}
|
||||
//builder.setOnCancelListener {
|
||||
// it?.dismiss()
|
||||
//}
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
|
||||
builder.show()
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
is Some.None -> {
|
||||
loadingDialog?.dismissSafe(activity)
|
||||
loadingDialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.selectedSeason) { text ->
|
||||
result_season_button.setText(text)
|
||||
|
||||
// If the season button is visible the result season button will be next focus down
|
||||
if (result_season_button?.isVisible == true)
|
||||
if (result_resume_parent?.isVisible == true)
|
||||
setFocusUpAndDown(result_resume_series_button, result_season_button)
|
||||
else
|
||||
setFocusUpAndDown(result_bookmark_button, result_season_button)
|
||||
}
|
||||
|
||||
observe(viewModel.selectedDubStatus) { status ->
|
||||
result_dub_select?.setText(status)
|
||||
|
||||
if (result_dub_select?.isVisible == true)
|
||||
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
|
||||
if (result_resume_parent?.isVisible == true)
|
||||
setFocusUpAndDown(result_resume_series_button, result_dub_select)
|
||||
else
|
||||
setFocusUpAndDown(result_bookmark_button, result_dub_select)
|
||||
}
|
||||
}
|
||||
observe(viewModel.selectedRange) { range ->
|
||||
result_episode_select.setText(range)
|
||||
|
||||
// If Season button is invisible then the bookmark button next focus is episode select
|
||||
if (result_episode_select?.isVisible == true)
|
||||
if (result_season_button?.isVisible != true) {
|
||||
if (result_resume_parent?.isVisible == true)
|
||||
setFocusUpAndDown(result_resume_series_button, result_episode_select)
|
||||
else
|
||||
setFocusUpAndDown(result_bookmark_button, result_episode_select)
|
||||
}
|
||||
}
|
||||
|
||||
// val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
|
||||
|
||||
observe(viewModel.dubSubSelections) { range ->
|
||||
result_dub_select.setOnClickListener { view ->
|
||||
view?.context?.let { ctx ->
|
||||
view.popupMenuNoIconsAndNoStringRes(range
|
||||
.mapNotNull { (text, status) ->
|
||||
Pair(
|
||||
status.ordinal,
|
||||
text?.asStringNull(ctx) ?: return@mapNotNull null
|
||||
)
|
||||
}) {
|
||||
viewModel.changeDubStatus(DubStatus.values()[itemId])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.rangeSelections) { range ->
|
||||
result_episode_select?.setOnClickListener { view ->
|
||||
view?.context?.let { ctx ->
|
||||
val names = range
|
||||
.mapNotNull { (text, r) ->
|
||||
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
|
||||
}
|
||||
|
||||
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
|
||||
index to name
|
||||
}) {
|
||||
viewModel.changeRange(names[itemId].first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.seasonSelections) { seasonList ->
|
||||
result_season_button?.setOnClickListener { view ->
|
||||
view?.context?.let { ctx ->
|
||||
val names = seasonList
|
||||
.mapNotNull { (text, r) ->
|
||||
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
|
||||
}
|
||||
|
||||
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
|
||||
index to name
|
||||
}) {
|
||||
viewModel.changeSeason(names[itemId].first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
|
||||
result_overlapping_panels?.setChildGestureRegions(gestureRegions)
|
||||
}
|
||||
|
||||
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
||||
val isInvalid = rec.isNullOrEmpty()
|
||||
result_recommendations?.isGone = isInvalid
|
||||
result_recommendations_btt?.isGone = isInvalid
|
||||
result_recommendations_btt?.setOnClickListener {
|
||||
val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
|
||||
result_overlapping_panels?.openEndPanel()
|
||||
R.id.result_recommendations
|
||||
} else {
|
||||
result_overlapping_panels?.closePanels()
|
||||
R.id.result_description
|
||||
}
|
||||
|
||||
result_recommendations_btt?.nextFocusDownId = nextFocusDown
|
||||
result_search?.nextFocusDownId = nextFocusDown
|
||||
result_open_in_browser?.nextFocusDownId = nextFocusDown
|
||||
result_share?.nextFocusDownId = nextFocusDown
|
||||
}
|
||||
result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
|
||||
|
||||
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
|
||||
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
|
||||
// very dirty selection
|
||||
result_recommendations_filter_button?.isVisible = apiNames.size > 1
|
||||
result_recommendations_filter_button?.text = matchAgainst
|
||||
result_recommendations_filter_button?.setOnClickListener { _ ->
|
||||
activity?.showBottomDialog(
|
||||
apiNames,
|
||||
apiNames.indexOf(matchAgainst),
|
||||
getString(R.string.home_change_provider_img_des), false, {}
|
||||
) {
|
||||
setRecommendations(rec, apiNames[it])
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
result_recommendations_filter_button?.isVisible = false
|
||||
}
|
||||
|
||||
result_recommendations?.post {
|
||||
rec?.let { list ->
|
||||
(result_recommendations?.adapter as SearchAdapter?)?.updateList(list.filter { it.apiName == matchAgainst })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.mvvm.ResourceSome
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||
import kotlinx.android.synthetic.main.fragment_result_tv.*
|
||||
|
||||
class ResultFragmentTv : ResultFragment() {
|
||||
override val resultLayout = R.layout.fragment_result_tv
|
||||
|
||||
private var currentRecommendations: List<SearchResponse> = emptyList()
|
||||
|
||||
private fun handleSelection(data: Any) {
|
||||
when (data) {
|
||||
is EpisodeRange -> {
|
||||
viewModel.changeRange(data)
|
||||
}
|
||||
is Int -> {
|
||||
viewModel.changeSeason(data)
|
||||
}
|
||||
is DubStatus -> {
|
||||
viewModel.changeDubStatus(data)
|
||||
}
|
||||
is String -> {
|
||||
setRecommendations(currentRecommendations, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun RecyclerView?.select(index: Int) {
|
||||
(this?.adapter as? SelectAdaptor?)?.select(index, this)
|
||||
}
|
||||
|
||||
private fun RecyclerView?.update(data: List<SelectData>) {
|
||||
(this?.adapter as? SelectAdaptor?)?.updateSelectionList(data)
|
||||
this?.isVisible = data.size > 1
|
||||
}
|
||||
|
||||
private fun RecyclerView?.setAdapter() {
|
||||
this?.adapter = SelectAdaptor { data ->
|
||||
handleSelection(data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasNoFocus(): Boolean {
|
||||
val focus = activity?.currentFocus
|
||||
if (focus == null || !focus.isVisible) return true
|
||||
return focus == this.result_root
|
||||
}
|
||||
|
||||
override fun updateEpisodes(episodes: ResourceSome<List<ResultEpisode>>) {
|
||||
super.updateEpisodes(episodes)
|
||||
if (episodes is ResourceSome.Success && hasNoFocus()) {
|
||||
result_episodes?.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateMovie(data: ResourceSome<Pair<UiText, ResultEpisode>>) {
|
||||
super.updateMovie(data)
|
||||
if (data is ResourceSome.Success && hasNoFocus()) {
|
||||
result_play_movie?.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
||||
currentRecommendations = rec ?: emptyList()
|
||||
val isInvalid = rec.isNullOrEmpty()
|
||||
result_recommendations?.isGone = isInvalid
|
||||
result_recommendations_holder?.isGone = isInvalid
|
||||
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
|
||||
(result_recommendations?.adapter as SearchAdapter?)?.updateList(rec?.filter { it.apiName == matchAgainst }
|
||||
?: emptyList())
|
||||
|
||||
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
|
||||
// very dirty selection
|
||||
result_recommendations_filter_selection?.isVisible = apiNames.size > 1
|
||||
result_recommendations_filter_selection?.update(apiNames.map { txt(it) to it })
|
||||
result_recommendations_filter_selection?.select(apiNames.indexOf(matchAgainst))
|
||||
} ?: run {
|
||||
result_recommendations_filter_selection?.isVisible = false
|
||||
}
|
||||
}
|
||||
var loadingDialog: Dialog? = null
|
||||
var popupDialog: Dialog? = null
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(result_episodes?.adapter as EpisodeAdapter?)?.apply {
|
||||
layout = R.layout.result_episode_both_tv
|
||||
}
|
||||
|
||||
result_season_selection.setAdapter()
|
||||
result_range_selection.setAdapter()
|
||||
result_dub_selection.setAdapter()
|
||||
result_recommendations_filter_selection.setAdapter()
|
||||
|
||||
observe(viewModel.selectPopup) { popup ->
|
||||
when (popup) {
|
||||
is Some.Success -> {
|
||||
popupDialog?.dismissSafe(activity)
|
||||
|
||||
popupDialog = activity?.let { act ->
|
||||
val pop = popup.value
|
||||
val options = pop.getOptions(act)
|
||||
val title = pop.getTitle(act)
|
||||
|
||||
act.showBottomDialogInstant(
|
||||
options, title, {
|
||||
popupDialog = null
|
||||
pop.callback(null)
|
||||
}, {
|
||||
popupDialog = null
|
||||
pop.callback(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is Some.None -> {
|
||||
popupDialog?.dismissSafe(activity)
|
||||
popupDialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.loadedLinks) { load ->
|
||||
when (load) {
|
||||
is Some.Success -> {
|
||||
if (loadingDialog?.isShowing != true) {
|
||||
loadingDialog?.dismissSafe(activity)
|
||||
loadingDialog = null
|
||||
}
|
||||
loadingDialog = loadingDialog ?: context?.let { ctx ->
|
||||
val builder =
|
||||
BottomSheetDialog(ctx)
|
||||
builder.setContentView(R.layout.bottom_loading)
|
||||
builder.setOnDismissListener {
|
||||
loadingDialog = null
|
||||
viewModel.cancelLinks()
|
||||
}
|
||||
//builder.setOnCancelListener {
|
||||
// it?.dismiss()
|
||||
//}
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
|
||||
builder.show()
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
is Some.None -> {
|
||||
loadingDialog?.dismissSafe(activity)
|
||||
loadingDialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
observe(viewModel.episodesCountText) { count ->
|
||||
result_episodes_text.setText(count)
|
||||
}
|
||||
|
||||
observe(viewModel.selectedRangeIndex) { selected ->
|
||||
result_range_selection.select(selected)
|
||||
}
|
||||
observe(viewModel.selectedSeasonIndex) { selected ->
|
||||
result_season_selection.select(selected)
|
||||
}
|
||||
observe(viewModel.selectedDubStatusIndex) { selected ->
|
||||
result_dub_selection.select(selected)
|
||||
}
|
||||
observe(viewModel.rangeSelections) {
|
||||
result_range_selection.update(it)
|
||||
}
|
||||
observe(viewModel.dubSubSelections) {
|
||||
result_dub_selection.update(it)
|
||||
}
|
||||
observe(viewModel.seasonSelections) {
|
||||
result_season_selection.update(it)
|
||||
}
|
||||
|
||||
result_back?.setOnClickListener {
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
|
||||
result_recommendations?.spanCount = 8
|
||||
result_recommendations?.adapter =
|
||||
SearchAdapter(
|
||||
ArrayList(),
|
||||
result_recommendations,
|
||||
) { callback ->
|
||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,631 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromUrlNull
|
||||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
|
||||
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu.getEpisodesDetails
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.player.IGenerator
|
||||
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setBookmarkedData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.getFillerEpisodes
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.collections.set
|
||||
|
||||
const val EPISODE_RANGE_SIZE = 50
|
||||
const val EPISODE_RANGE_OVERLOAD = 60
|
||||
|
||||
class ResultViewModel : ViewModel() {
|
||||
private var repo: APIRepository? = null
|
||||
private var generator: IGenerator? = null
|
||||
|
||||
private val _resultResponse: MutableLiveData<Resource<LoadResponse>> = MutableLiveData()
|
||||
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
||||
private val episodeById: MutableLiveData<HashMap<Int, Int>> =
|
||||
MutableLiveData() // lookup by ID to get Index
|
||||
|
||||
private val _publicEpisodes: MutableLiveData<Resource<List<ResultEpisode>>> = MutableLiveData()
|
||||
private val _publicEpisodesCount: MutableLiveData<Int> = MutableLiveData() // before the sorting
|
||||
private val _rangeOptions: MutableLiveData<List<String>> = MutableLiveData()
|
||||
val selectedRange: MutableLiveData<String> = MutableLiveData()
|
||||
private val selectedRangeInt: MutableLiveData<Int> = MutableLiveData()
|
||||
val rangeOptions: LiveData<List<String>> = _rangeOptions
|
||||
|
||||
val result: LiveData<Resource<LoadResponse>> get() = _resultResponse
|
||||
|
||||
val episodes: LiveData<List<ResultEpisode>> get() = _episodes
|
||||
val publicEpisodes: LiveData<Resource<List<ResultEpisode>>> get() = _publicEpisodes
|
||||
val publicEpisodesCount: LiveData<Int> get() = _publicEpisodesCount
|
||||
|
||||
val dubStatus: LiveData<DubStatus> get() = _dubStatus
|
||||
private val _dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
|
||||
|
||||
val id: MutableLiveData<Int> = MutableLiveData()
|
||||
val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
|
||||
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
|
||||
|
||||
val dubSubSelections: LiveData<Set<DubStatus>> get() = _dubSubSelections
|
||||
private val _dubSubSelections: MutableLiveData<Set<DubStatus>> = MutableLiveData()
|
||||
|
||||
val dubSubEpisodes: LiveData<Map<DubStatus, List<ResultEpisode>>?> get() = _dubSubEpisodes
|
||||
private val _dubSubEpisodes: MutableLiveData<Map<DubStatus, List<ResultEpisode>>?> =
|
||||
MutableLiveData()
|
||||
|
||||
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData()
|
||||
val watchStatus: LiveData<WatchType> get() = _watchStatus
|
||||
|
||||
fun updateWatchStatus(status: WatchType) = viewModelScope.launch {
|
||||
val currentId = id.value ?: return@launch
|
||||
_watchStatus.postValue(status)
|
||||
val resultPage = _resultResponse.value
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
setResultWatchState(currentId, status.internalId)
|
||||
if (resultPage != null && resultPage is Resource.Success) {
|
||||
val resultPageData = resultPage.value
|
||||
val current = getBookmarkedData(currentId)
|
||||
val currentTime = System.currentTimeMillis()
|
||||
setBookmarkedData(
|
||||
currentId,
|
||||
DataStoreHelper.BookmarkedData(
|
||||
currentId,
|
||||
current?.bookmarkedTime ?: currentTime,
|
||||
currentTime,
|
||||
resultPageData.name,
|
||||
resultPageData.url,
|
||||
resultPageData.apiName,
|
||||
resultPageData.type,
|
||||
resultPageData.posterUrl,
|
||||
resultPageData.year
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "RVM"
|
||||
}
|
||||
|
||||
var lastMeta: SyncAPI.SyncResult? = null
|
||||
var lastSync: Map<String, String>? = null
|
||||
|
||||
private suspend fun applyMeta(
|
||||
resp: LoadResponse,
|
||||
meta: SyncAPI.SyncResult?,
|
||||
syncs: Map<String, String>? = null
|
||||
): Pair<LoadResponse, Boolean> {
|
||||
if (meta == null) return resp to false
|
||||
var updateEpisodes = false
|
||||
val out = resp.apply {
|
||||
Log.i(TAG, "applyMeta")
|
||||
|
||||
duration = duration ?: meta.duration
|
||||
rating = rating ?: meta.publicScore
|
||||
tags = tags ?: meta.genres
|
||||
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
|
||||
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
|
||||
actors = actors ?: meta.actors
|
||||
|
||||
if (this is EpisodeResponse) {
|
||||
nextAiring = nextAiring ?: meta.nextAiring
|
||||
}
|
||||
|
||||
for ((k, v) in syncs ?: emptyMap()) {
|
||||
syncData[k] = v
|
||||
}
|
||||
|
||||
val realRecommendations = ArrayList<SearchResponse>()
|
||||
// TODO: fix
|
||||
//val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
|
||||
// meta.recommendations?.forEach { rec ->
|
||||
// apiNames.forEach { name ->
|
||||
// realRecommendations.add(rec.copy(apiName = name))
|
||||
// }
|
||||
// }
|
||||
|
||||
recommendations = recommendations?.union(realRecommendations)?.toList()
|
||||
?: realRecommendations
|
||||
|
||||
argamap({
|
||||
addTrailer(meta.trailers)
|
||||
}, {
|
||||
if (this !is AnimeLoadResponse) return@argamap
|
||||
val map = getEpisodesDetails(getMalId(), getAniListId(), isResponseRequired = false)
|
||||
if (map.isNullOrEmpty()) return@argamap
|
||||
updateEpisodes = DubStatus.values().map { dubStatus ->
|
||||
val current =
|
||||
this.episodes[dubStatus]?.mapIndexed { index, episode ->
|
||||
episode.apply {
|
||||
this.episode = this.episode ?: (index + 1)
|
||||
}
|
||||
}?.sortedBy { it.episode ?: 0 }?.toMutableList()
|
||||
if (current.isNullOrEmpty()) return@map false
|
||||
val episodeNumbers = current.map { ep -> ep.episode!! }
|
||||
var updateCount = 0
|
||||
map.forEach { (episode, node) ->
|
||||
episodeNumbers.binarySearch(episode).let { index ->
|
||||
current.getOrNull(index)?.let { currentEp ->
|
||||
current[index] = currentEp.apply {
|
||||
updateCount++
|
||||
val currentBack = this
|
||||
this.description = this.description ?: node.description?.en
|
||||
this.name = this.name ?: node.titles?.canonical
|
||||
this.episode = this.episode ?: node.num ?: episodeNumbers[index]
|
||||
this.posterUrl = this.posterUrl ?: node.thumbnail?.original?.url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.episodes[dubStatus] = current
|
||||
updateCount > 0
|
||||
}.any { it }
|
||||
})
|
||||
}
|
||||
return out to updateEpisodes
|
||||
}
|
||||
|
||||
fun setMeta(meta: SyncAPI.SyncResult, syncs: Map<String, String>?) =
|
||||
viewModelScope.launch {
|
||||
Log.i(TAG, "setMeta")
|
||||
lastMeta = meta
|
||||
lastSync = syncs
|
||||
val (value, updateEpisodes) = ioWork {
|
||||
(result.value as? Resource.Success<LoadResponse>?)?.value?.let { resp ->
|
||||
return@ioWork applyMeta(resp, meta, syncs)
|
||||
}
|
||||
return@ioWork null to null
|
||||
}
|
||||
_resultResponse.postValue(Resource.Success(value ?: return@launch))
|
||||
if (updateEpisodes ?: return@launch) updateEpisodes(value, lastShowFillers)
|
||||
}
|
||||
|
||||
private fun loadWatchStatus(localId: Int? = null) {
|
||||
val currentId = localId ?: id.value ?: return
|
||||
val currentWatch = getResultWatchState(currentId)
|
||||
_watchStatus.postValue(currentWatch)
|
||||
}
|
||||
|
||||
private fun filterEpisodes(list: List<ResultEpisode>?, selection: Int?, range: Int?) {
|
||||
if (list == null) return
|
||||
val seasonTypes = HashMap<Int?, Boolean>()
|
||||
for (i in list) {
|
||||
if (!seasonTypes.containsKey(i.season)) {
|
||||
seasonTypes[i.season] = true
|
||||
}
|
||||
}
|
||||
val seasons = seasonTypes.toList().map { it.first }.sortedBy { it }
|
||||
seasonSelections.postValue(seasons)
|
||||
if (seasons.isEmpty()) { // WHAT THE FUCK DID YOU DO????? HOW DID YOU DO THIS
|
||||
_publicEpisodes.postValue(Resource.Success(emptyList()))
|
||||
return
|
||||
}
|
||||
|
||||
val realSelection = if (!seasonTypes.containsKey(selection)) seasons.first() else selection
|
||||
val internalId = id.value
|
||||
|
||||
if (internalId != null) setResultSeason(internalId, realSelection)
|
||||
|
||||
selectedSeason.postValue(realSelection ?: -2)
|
||||
|
||||
var currentList = list.filter { it.season == realSelection }
|
||||
_publicEpisodesCount.postValue(currentList.size)
|
||||
|
||||
val rangeList = ArrayList<String>()
|
||||
for (i in currentList.indices step EPISODE_RANGE_SIZE) {
|
||||
if (i + EPISODE_RANGE_SIZE < currentList.size) {
|
||||
rangeList.add("${i + 1}-${i + EPISODE_RANGE_SIZE}")
|
||||
} else {
|
||||
rangeList.add("${i + 1}-${currentList.size}")
|
||||
}
|
||||
}
|
||||
|
||||
val cRange = range ?: if (selection != null) {
|
||||
0
|
||||
} else {
|
||||
selectedRangeInt.value ?: 0
|
||||
}
|
||||
|
||||
val realRange = if (cRange * EPISODE_RANGE_SIZE > currentList.size) {
|
||||
currentList.size / EPISODE_RANGE_SIZE
|
||||
} else {
|
||||
cRange
|
||||
}
|
||||
|
||||
if (currentList.size > EPISODE_RANGE_OVERLOAD) {
|
||||
currentList = currentList.subList(
|
||||
realRange * EPISODE_RANGE_SIZE,
|
||||
minOf(currentList.size, (realRange + 1) * EPISODE_RANGE_SIZE)
|
||||
)
|
||||
_rangeOptions.postValue(rangeList)
|
||||
selectedRangeInt.postValue(realRange)
|
||||
selectedRange.postValue(rangeList[realRange])
|
||||
} else {
|
||||
val allRange = "1-${currentList.size}"
|
||||
_rangeOptions.postValue(listOf(allRange))
|
||||
selectedRangeInt.postValue(0)
|
||||
selectedRange.postValue(allRange)
|
||||
}
|
||||
|
||||
_publicEpisodes.postValue(Resource.Success(currentList))
|
||||
}
|
||||
|
||||
fun changeSeason(selection: Int?) {
|
||||
filterEpisodes(_episodes.value, selection, null)
|
||||
}
|
||||
|
||||
fun changeRange(range: Int?) {
|
||||
filterEpisodes(_episodes.value, null, range)
|
||||
}
|
||||
|
||||
fun changeDubStatus(status: DubStatus?) {
|
||||
if (status == null) return
|
||||
dubSubEpisodes.value?.get(status)?.let { episodes ->
|
||||
id.value?.let {
|
||||
setDub(it, status)
|
||||
}
|
||||
_dubStatus.postValue(status!!)
|
||||
updateEpisodes(null, episodes, null)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadEpisode(
|
||||
episode: ResultEpisode,
|
||||
isCasting: Boolean,
|
||||
clearCache: Boolean = false
|
||||
): Resource<Pair<Set<ExtractorLink>, Set<SubtitleData>>> {
|
||||
return safeApiCall {
|
||||
val index = _episodes.value?.indexOf(episode) ?: episode.index
|
||||
|
||||
val currentLinks = mutableSetOf<ExtractorLink>()
|
||||
val currentSubs = mutableSetOf<SubtitleData>()
|
||||
|
||||
generator?.goto(index)
|
||||
generator?.generateLinks(clearCache, isCasting, {
|
||||
it.first?.let { link ->
|
||||
currentLinks.add(link)
|
||||
}
|
||||
}, { sub ->
|
||||
currentSubs.add(sub)
|
||||
})
|
||||
|
||||
return@safeApiCall Pair(
|
||||
currentLinks.toSet(),
|
||||
currentSubs.toSet()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getGenerator(episode: ResultEpisode): IGenerator? {
|
||||
val index = _episodes.value?.indexOf(episode) ?: episode.index
|
||||
|
||||
generator?.goto(index)
|
||||
return generator
|
||||
}
|
||||
|
||||
private fun updateEpisodes(localId: Int?, list: List<ResultEpisode>, selection: Int?) {
|
||||
_episodes.postValue(list)
|
||||
generator = RepoLinkGenerator(list)
|
||||
|
||||
val set = HashMap<Int, Int>()
|
||||
val range = selectedRangeInt.value
|
||||
|
||||
list.withIndex().forEach { set[it.value.id] = it.index }
|
||||
episodeById.postValue(set)
|
||||
|
||||
filterEpisodes(
|
||||
list,
|
||||
if (selection == -1) getResultSeason(localId ?: id.value ?: return) else selection,
|
||||
range
|
||||
)
|
||||
}
|
||||
|
||||
fun reloadEpisodes() {
|
||||
val current = _episodes.value ?: return
|
||||
val copy = current.map {
|
||||
val posDur = getViewPos(it.id)
|
||||
it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
|
||||
}
|
||||
updateEpisodes(null, copy, selectedSeason.value)
|
||||
}
|
||||
|
||||
private fun filterName(name: String?): String? {
|
||||
if (name == null) return null
|
||||
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
|
||||
if (it.isEmpty())
|
||||
return null
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var lastShowFillers = false
|
||||
private suspend fun updateEpisodes(loadResponse: LoadResponse, showFillers: Boolean) {
|
||||
Log.i(TAG, "updateEpisodes")
|
||||
try {
|
||||
lastShowFillers = showFillers
|
||||
val mainId = loadResponse.getId()
|
||||
|
||||
when (loadResponse) {
|
||||
is AnimeLoadResponse -> {
|
||||
if (loadResponse.episodes.isEmpty()) {
|
||||
_dubSubEpisodes.postValue(emptyMap())
|
||||
return
|
||||
}
|
||||
|
||||
// val status = getDub(mainId)
|
||||
val statuses = loadResponse.episodes.map { it.key }
|
||||
|
||||
// Extremely bruh to have to take in context here, but I'm not sure how to do this in a better way :(
|
||||
val preferDub = context?.getApiDubstatusSettings()
|
||||
?.contains(DubStatus.Dubbed) == true
|
||||
|
||||
// 3 statements because there can be only dub even if you do not prefer it.
|
||||
val dubStatus =
|
||||
if (preferDub && statuses.contains(DubStatus.Dubbed)) DubStatus.Dubbed
|
||||
else if (!preferDub && statuses.contains(DubStatus.Subbed)) DubStatus.Subbed
|
||||
else statuses.first()
|
||||
|
||||
val fillerEpisodes =
|
||||
if (showFillers) safeApiCall { getFillerEpisodes(loadResponse.name) } else null
|
||||
|
||||
val existingEpisodes = HashSet<Int>()
|
||||
val res = loadResponse.episodes.map { ep ->
|
||||
val episodes = ArrayList<ResultEpisode>()
|
||||
val idIndex = ep.key.id
|
||||
for ((index, i) in ep.value.withIndex()) {
|
||||
val episode = i.episode ?: (index + 1)
|
||||
val id = mainId + episode + idIndex * 1000000
|
||||
if (!existingEpisodes.contains(episode)) {
|
||||
existingEpisodes.add(id)
|
||||
episodes.add(buildResultEpisode(
|
||||
loadResponse.name,
|
||||
filterName(i.name),
|
||||
i.posterUrl,
|
||||
episode,
|
||||
i.season,
|
||||
i.data,
|
||||
loadResponse.apiName,
|
||||
id,
|
||||
index,
|
||||
i.rating,
|
||||
i.description,
|
||||
if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let {
|
||||
it.contains(episode) && it[episode] == true
|
||||
} ?: false else false,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Pair(ep.key, episodes)
|
||||
}.toMap()
|
||||
|
||||
// These posts needs to be in this order as to make the preferDub in ResultFragment work
|
||||
_dubSubEpisodes.postValue(res)
|
||||
res[dubStatus]?.let { episodes ->
|
||||
updateEpisodes(mainId, episodes, -1)
|
||||
}
|
||||
|
||||
_dubStatus.postValue(dubStatus)
|
||||
_dubSubSelections.postValue(loadResponse.episodes.keys)
|
||||
}
|
||||
|
||||
is TvSeriesLoadResponse -> {
|
||||
val episodes = ArrayList<ResultEpisode>()
|
||||
val existingEpisodes = HashSet<Int>()
|
||||
for ((index, episode) in loadResponse.episodes.sortedBy {
|
||||
(it.season?.times(10000) ?: 0) + (it.episode ?: 0)
|
||||
}.withIndex()) {
|
||||
val episodeIndex = episode.episode ?: (index + 1)
|
||||
val id =
|
||||
mainId + (episode.season?.times(100000) ?: 0) + episodeIndex + 1
|
||||
if (!existingEpisodes.contains(id)) {
|
||||
existingEpisodes.add(id)
|
||||
episodes.add(
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
filterName(episode.name),
|
||||
episode.posterUrl,
|
||||
episodeIndex,
|
||||
episode.season,
|
||||
episode.data,
|
||||
loadResponse.apiName,
|
||||
id,
|
||||
index,
|
||||
episode.rating,
|
||||
episode.description,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
updateEpisodes(mainId, episodes, -1)
|
||||
}
|
||||
is MovieLoadResponse -> {
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
loadResponse.name,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
loadResponse.dataUrl,
|
||||
loadResponse.apiName,
|
||||
(mainId), // HAS SAME ID
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
).let {
|
||||
updateEpisodes(mainId, listOf(it), -1)
|
||||
}
|
||||
}
|
||||
is LiveStreamLoadResponse -> {
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
loadResponse.name,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
loadResponse.dataUrl,
|
||||
loadResponse.apiName,
|
||||
(mainId), // HAS SAME ID
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
).let {
|
||||
updateEpisodes(mainId, listOf(it), -1)
|
||||
}
|
||||
}
|
||||
is TorrentLoadResponse -> {
|
||||
updateEpisodes(
|
||||
mainId, listOf(
|
||||
buildResultEpisode(
|
||||
loadResponse.name,
|
||||
loadResponse.name,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
loadResponse.torrent ?: loadResponse.magnet ?: "",
|
||||
loadResponse.apiName,
|
||||
(mainId), // HAS SAME ID
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
loadResponse.type,
|
||||
mainId
|
||||
)
|
||||
), -1
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
|
||||
_publicEpisodes.postValue(Resource.Loading())
|
||||
_resultResponse.postValue(Resource.Loading(url))
|
||||
|
||||
val api = getApiFromNameNull(apiName) ?: getApiFromUrlNull(url)
|
||||
if (api == null) {
|
||||
_resultResponse.postValue(
|
||||
Resource.Failure(
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
"This provider does not exist"
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
var validUrlResource = safeApiCall {
|
||||
SyncRedirector.redirect(
|
||||
url,
|
||||
api.mainUrl
|
||||
)
|
||||
}
|
||||
// TODO: fix
|
||||
// val validUrlResource = safeApiCall {
|
||||
// SyncRedirector.redirect(
|
||||
// url,
|
||||
// api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
|
||||
// .replace(GogoanimeProvider().mainUrl, "gogoanime")
|
||||
// )
|
||||
// }
|
||||
|
||||
if (validUrlResource !is Resource.Success) {
|
||||
if (validUrlResource is Resource.Failure) {
|
||||
_resultResponse.postValue(validUrlResource)
|
||||
}
|
||||
|
||||
return@launch
|
||||
}
|
||||
val validUrl = validUrlResource.value
|
||||
|
||||
_resultResponse.postValue(Resource.Loading(validUrl))
|
||||
|
||||
_apiName.postValue(apiName)
|
||||
|
||||
repo = APIRepository(api)
|
||||
|
||||
val data = repo?.load(validUrl) ?: return@launch
|
||||
|
||||
_resultResponse.postValue(data)
|
||||
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
val loadResponse = if (lastMeta != null || lastSync != null) ioWork {
|
||||
applyMeta(data.value, lastMeta, lastSync).first
|
||||
} else data.value
|
||||
_resultResponse.postValue(Resource.Success(loadResponse))
|
||||
val mainId = loadResponse.getId()
|
||||
id.postValue(mainId)
|
||||
loadWatchStatus(mainId)
|
||||
|
||||
setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
mainId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
apiName,
|
||||
validUrl,
|
||||
loadResponse.type,
|
||||
loadResponse.name,
|
||||
loadResponse.posterUrl,
|
||||
mainId,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
updateEpisodes(loadResponse, showFillers)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private var _apiName: MutableLiveData<String> = MutableLiveData()
|
||||
val apiName: LiveData<String> get() = _apiName
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,128 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.ActorData
|
||||
import com.lagradost.cloudstream3.ActorRole
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
|
||||
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
|
||||
import com.lagradost.cloudstream3.ui.settings.AccountAdapter
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import kotlinx.android.synthetic.main.cast_item.view.*
|
||||
import org.schabi.newpipe.extractor.timeago.patterns.it
|
||||
|
||||
typealias SelectData = Pair<UiText?, Any>
|
||||
|
||||
class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
private val selection: MutableList<SelectData> = mutableListOf()
|
||||
private var selectedIndex: Int = -1
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return SelectViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.result_selection, parent, false),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is SelectViewHolder -> {
|
||||
holder.bind(selection[position], position == selectedIndex, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||
if(holder.itemView.hasFocus()) {
|
||||
holder.itemView.clearFocus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return selection.size
|
||||
}
|
||||
|
||||
fun select(newIndex: Int, recyclerView: RecyclerView?) {
|
||||
if(recyclerView == null) return
|
||||
if(newIndex == selectedIndex) return
|
||||
val oldIndex = selectedIndex
|
||||
selectedIndex = newIndex
|
||||
recyclerView.apply {
|
||||
for (i in 0 until itemCount) {
|
||||
val viewHolder = getChildViewHolder( getChildAt(i) ?: continue) ?: continue
|
||||
val pos = viewHolder.absoluteAdapterPosition
|
||||
if (viewHolder is SelectViewHolder) {
|
||||
if (pos == oldIndex) {
|
||||
viewHolder.update(false)
|
||||
} else if (pos == newIndex) {
|
||||
viewHolder.update(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSelectionList(newList: List<SelectData>) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
SelectDataCallback(this.selection, newList)
|
||||
)
|
||||
|
||||
selection.clear()
|
||||
selection.addAll(newList)
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
|
||||
private class SelectViewHolder
|
||||
constructor(
|
||||
itemView: View,
|
||||
) :
|
||||
RecyclerView.ViewHolder(itemView) {
|
||||
private val item: MaterialButton = itemView as MaterialButton
|
||||
|
||||
fun update(isSelected: Boolean) {
|
||||
item.isSelected = isSelected
|
||||
}
|
||||
|
||||
fun bind(
|
||||
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
|
||||
) {
|
||||
val isTrueTv = itemView.context?.isTrueTvSettings() == true
|
||||
if (isTrueTv) {
|
||||
item.isFocusable = true
|
||||
item.isFocusableInTouchMode = true
|
||||
}
|
||||
|
||||
item.isSelected = isSelected
|
||||
item.setText(data.first)
|
||||
item.setOnClickListener {
|
||||
callback.invoke(data.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SelectDataCallback(
|
||||
private val oldList: List<SelectData>,
|
||||
private val newList: List<SelectData>
|
||||
) :
|
||||
DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition].second == newList[newItemPosition].second
|
||||
|
||||
override fun getOldListSize() = oldList.size
|
||||
|
||||
override fun getNewListSize() = newList.size
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
|
@ -4,7 +4,6 @@ import android.util.Log
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -12,8 +11,8 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApi
|
|||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.SyncUtil
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
@ -44,9 +43,13 @@ class SyncViewModel : ViewModel() {
|
|||
|
||||
// prefix, id
|
||||
private var syncs = mutableMapOf<String, String>()
|
||||
private val _syncIds: MutableLiveData<MutableMap<String, String>> =
|
||||
MutableLiveData(mutableMapOf())
|
||||
val syncIds: LiveData<MutableMap<String, String>> get() = _syncIds
|
||||
//private val _syncIds: MutableLiveData<MutableMap<String, String>> =
|
||||
// MutableLiveData(mutableMapOf())
|
||||
//val syncIds: LiveData<MutableMap<String, String>> get() = _syncIds
|
||||
|
||||
fun getSyncs() : Map<String,String> {
|
||||
return syncs
|
||||
}
|
||||
|
||||
private val _currentSynced: MutableLiveData<List<CurrentSynced>> =
|
||||
MutableLiveData(getMissing())
|
||||
|
@ -76,7 +79,7 @@ class SyncViewModel : ViewModel() {
|
|||
Log.i(TAG, "addSync $idPrefix = $id")
|
||||
|
||||
syncs[idPrefix] = id
|
||||
_syncIds.postValue(syncs)
|
||||
//_syncIds.postValue(syncs)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -99,10 +102,12 @@ class SyncViewModel : ViewModel() {
|
|||
|
||||
var hasAddedFromUrl: HashSet<String> = hashSetOf()
|
||||
|
||||
fun addFromUrl(url: String?) = viewModelScope.launch {
|
||||
fun addFromUrl(url: String?) = ioSafe {
|
||||
Log.i(TAG, "addFromUrl = $url")
|
||||
|
||||
if (url == null || hasAddedFromUrl.contains(url)) return@launch
|
||||
if (url == null || hasAddedFromUrl.contains(url)) return@ioSafe
|
||||
if(!url.startsWith("http")) return@ioSafe
|
||||
|
||||
SyncUtil.getIdsFromUrl(url)?.let { (malId, aniListId) ->
|
||||
hasAddedFromUrl.add(url)
|
||||
|
||||
|
@ -166,7 +171,7 @@ class SyncViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun publishUserData() = viewModelScope.launch {
|
||||
fun publishUserData() = ioSafe {
|
||||
Log.i(TAG, "publishUserData")
|
||||
val user = userData.value
|
||||
if (user is Resource.Success) {
|
||||
|
@ -191,7 +196,7 @@ class SyncViewModel : ViewModel() {
|
|||
|
||||
/// modifies the current sync data, return null if you don't want to change it
|
||||
private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) =
|
||||
viewModelScope.launch {
|
||||
ioSafe {
|
||||
syncs.apmap { (prefix, id) ->
|
||||
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
|
||||
if (repo.hasAccount()) {
|
||||
|
@ -209,7 +214,7 @@ class SyncViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun updateUserData() = viewModelScope.launch {
|
||||
fun updateUserData() = ioSafe {
|
||||
Log.i(TAG, "updateUserData")
|
||||
_userDataResponse.postValue(Resource.Loading())
|
||||
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
|
||||
|
@ -219,7 +224,7 @@ class SyncViewModel : ViewModel() {
|
|||
val result = repo.getStatus(id)
|
||||
if (result is Resource.Success) {
|
||||
_userDataResponse.postValue(result)
|
||||
return@launch
|
||||
return@ioSafe
|
||||
} else if (result is Resource.Failure) {
|
||||
Log.e(TAG, "updateUserData error ${result.errorString}")
|
||||
lastError = result
|
||||
|
@ -230,7 +235,7 @@ class SyncViewModel : ViewModel() {
|
|||
_userDataResponse.postValue(lastError)
|
||||
}
|
||||
|
||||
private fun updateMetadata() = viewModelScope.launch {
|
||||
private fun updateMetadata() = ioSafe {
|
||||
Log.i(TAG, "updateMetadata")
|
||||
|
||||
_metaResponse.postValue(Resource.Loading())
|
||||
|
@ -253,7 +258,7 @@ class SyncViewModel : ViewModel() {
|
|||
val result = repo.getResult(id)
|
||||
if (result is Resource.Success) {
|
||||
_metaResponse.postValue(result)
|
||||
return@launch
|
||||
return@ioSafe
|
||||
} else if (result is Resource.Failure) {
|
||||
Log.e(
|
||||
TAG,
|
||||
|
|
172
app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
Normal file
172
app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt
Normal file
|
@ -0,0 +1,172 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
||||
sealed class UiText {
|
||||
companion object {
|
||||
const val TAG = "UiText"
|
||||
}
|
||||
|
||||
data class DynamicString(val value: String) : UiText() {
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
class StringResource(
|
||||
@StringRes val resId: Int,
|
||||
val args: List<Any>
|
||||
) : UiText() {
|
||||
override fun toString(): String =
|
||||
"resId = $resId\nargs = ${args.toList().map { "(${it::class} = $it)" }}"
|
||||
}
|
||||
|
||||
fun asStringNull(context: Context?): String? {
|
||||
try {
|
||||
return asString(context ?: return null)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Got invalid data from $this")
|
||||
logError(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun asString(context: Context): String {
|
||||
return when (this) {
|
||||
is DynamicString -> value
|
||||
is StringResource -> {
|
||||
val str = context.getString(resId)
|
||||
if (args.isEmpty()) {
|
||||
str
|
||||
} else {
|
||||
str.format(*args.map {
|
||||
when (it) {
|
||||
is UiText -> it.asString(context)
|
||||
else -> it
|
||||
}
|
||||
}.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class UiImage {
|
||||
data class Image(
|
||||
val url: String,
|
||||
val headers: Map<String, String>? = null,
|
||||
@DrawableRes val errorDrawable: Int? = null
|
||||
) : UiImage()
|
||||
|
||||
data class Drawable(@DrawableRes val resId: Int) : UiImage()
|
||||
}
|
||||
|
||||
fun ImageView?.setImage(value: UiImage?) {
|
||||
when (value) {
|
||||
is UiImage.Image -> setImageImage(value)
|
||||
is UiImage.Drawable -> setImageDrawable(value)
|
||||
null -> {
|
||||
this?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ImageView?.setImageImage(value: UiImage.Image) {
|
||||
if (this == null) return
|
||||
this.isVisible = setImage(value.url, value.headers, value.errorDrawable)
|
||||
}
|
||||
|
||||
fun ImageView?.setImageDrawable(value: UiImage.Drawable) {
|
||||
if (this == null) return
|
||||
this.isVisible = true
|
||||
setImageResource(value.resId)
|
||||
}
|
||||
|
||||
@JvmName("imgNull")
|
||||
fun img(
|
||||
url: String?,
|
||||
headers: Map<String, String>? = null,
|
||||
@DrawableRes errorDrawable: Int? = null
|
||||
): UiImage? {
|
||||
if (url.isNullOrBlank()) return null
|
||||
return UiImage.Image(url, headers, errorDrawable)
|
||||
}
|
||||
|
||||
fun img(
|
||||
url: String,
|
||||
headers: Map<String, String>? = null,
|
||||
@DrawableRes errorDrawable: Int? = null
|
||||
): UiImage {
|
||||
return UiImage.Image(url, headers, errorDrawable)
|
||||
}
|
||||
|
||||
fun img(@DrawableRes drawable: Int): UiImage {
|
||||
return UiImage.Drawable(drawable)
|
||||
}
|
||||
|
||||
fun txt(value: String): UiText {
|
||||
return UiText.DynamicString(value)
|
||||
}
|
||||
|
||||
@JvmName("txtNull")
|
||||
fun txt(value: String?): UiText? {
|
||||
return UiText.DynamicString(value ?: return null)
|
||||
}
|
||||
|
||||
fun txt(@StringRes resId: Int, vararg args: Any): UiText {
|
||||
return UiText.StringResource(resId, args.toList())
|
||||
}
|
||||
|
||||
@JvmName("txtNull")
|
||||
fun txt(@StringRes resId: Int?, vararg args: Any?): UiText? {
|
||||
if (resId == null || args.any { it == null }) {
|
||||
return null
|
||||
}
|
||||
return UiText.StringResource(resId, args.filterNotNull().toList())
|
||||
}
|
||||
|
||||
fun TextView?.setText(text: UiText?) {
|
||||
if (this == null) return
|
||||
if (text == null) {
|
||||
this.isVisible = false
|
||||
} else {
|
||||
val str = text.asStringNull(context)?.let {
|
||||
if (this.maxLines == 1) {
|
||||
it.replace("\n", " ")
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
this.isGone = str.isNullOrBlank()
|
||||
this.text = str
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView?.setTextHtml(text: UiText?) {
|
||||
if (this == null) return
|
||||
if (text == null) {
|
||||
this.isVisible = false
|
||||
} else {
|
||||
val str = text.asStringNull(context)
|
||||
this.isGone = str.isNullOrBlank()
|
||||
this.text = str.html()
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView?.setTextHtml(text: Some<UiText>?) {
|
||||
setTextHtml(if (text is Some.Success) text.value else null)
|
||||
}
|
||||
|
||||
fun TextView?.setText(text: Some<UiText>?) {
|
||||
setText(if (text is Some.Success) text.value else null)
|
||||
}
|
|
@ -27,7 +27,7 @@ object SearchHelper {
|
|||
} else {
|
||||
if (card.isFromDownload) {
|
||||
handleDownloadClick(
|
||||
activity, card.name, DownloadClickEvent(
|
||||
activity, DownloadClickEvent(
|
||||
DOWNLOAD_ACTION_PLAY_FILE,
|
||||
VideoDownloadHelper.DownloadEpisodeCached(
|
||||
card.name,
|
||||
|
|
|
@ -44,6 +44,9 @@ import com.lagradost.cloudstream3.isMovieType
|
|||
import com.lagradost.cloudstream3.mapper
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir
|
||||
import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load
|
||||
|
@ -187,21 +190,21 @@ object AppUtils {
|
|||
@WorkerThread
|
||||
fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val context = this
|
||||
ioSafe {
|
||||
data.forEach { episodeInfo ->
|
||||
try {
|
||||
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, this)
|
||||
val nextProgram = buildWatchNextProgramUri(this, episodeInfo)
|
||||
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context)
|
||||
val nextProgram = buildWatchNextProgramUri(context, episodeInfo)
|
||||
|
||||
// If the program is already in the Watch Next row, update it
|
||||
if (program != null && id != null) {
|
||||
PreviewChannelHelper(this).updateWatchNextProgram(
|
||||
PreviewChannelHelper(context).updateWatchNextProgram(
|
||||
nextProgram,
|
||||
id,
|
||||
)
|
||||
} else {
|
||||
PreviewChannelHelper(this)
|
||||
PreviewChannelHelper(context)
|
||||
.publishWatchNextProgram(nextProgram)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -313,6 +316,14 @@ object AppUtils {
|
|||
|
||||
//private val viewModel: ResultViewModel by activityViewModels()
|
||||
|
||||
private fun getResultsId(context: Context) : Int {
|
||||
return if(context.isTrueTvSettings()) {
|
||||
R.id.global_to_navigation_results_tv
|
||||
} else {
|
||||
R.id.global_to_navigation_results_phone
|
||||
}
|
||||
}
|
||||
|
||||
fun AppCompatActivity.loadResult(
|
||||
url: String,
|
||||
apiName: String,
|
||||
|
@ -322,7 +333,7 @@ object AppUtils {
|
|||
this.runOnUiThread {
|
||||
// viewModelStore.clear()
|
||||
this.navigate(
|
||||
R.id.global_to_navigation_results,
|
||||
getResultsId(this.applicationContext ?: return@runOnUiThread),
|
||||
ResultFragment.newInstance(url, apiName, startAction, startValue)
|
||||
)
|
||||
}
|
||||
|
@ -336,7 +347,7 @@ object AppUtils {
|
|||
this?.runOnUiThread {
|
||||
// viewModelStore.clear()
|
||||
this.navigate(
|
||||
R.id.global_to_navigation_results,
|
||||
getResultsId(this),
|
||||
ResultFragment.newInstance(card, startAction, startValue)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -89,7 +89,9 @@ object BackupUtils {
|
|||
val newFile = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
||||
put(MediaStore.MediaColumns.TITLE, displayName)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "application/json")
|
||||
// While it a json file we store as txt because not
|
||||
// all file managers support mimetype json
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
|
||||
//put(MediaStore.MediaColumns.RELATIVE_PATH, folder)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ object Coroutines {
|
|||
}
|
||||
}
|
||||
|
||||
fun ioSafe(work: suspend (() -> Unit)): Job {
|
||||
fun ioSafe(work: suspend (CoroutineScope.() -> Unit)): Job {
|
||||
return CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
work()
|
||||
|
@ -22,7 +22,7 @@ object Coroutines {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun <T> ioWork(work: suspend (() -> T)): T {
|
||||
suspend fun <T> ioWork(work: suspend (CoroutineScope.() -> T)): T {
|
||||
return withContext(Dispatchers.IO) {
|
||||
work()
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
|||
const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes
|
||||
const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching"
|
||||
const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated"
|
||||
const val RESULT_EPISODE = "result_episode"
|
||||
const val RESULT_SEASON = "result_season"
|
||||
const val RESULT_DUB = "result_dub"
|
||||
|
||||
|
@ -163,7 +164,7 @@ object DataStoreHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun getLastWatchedOld(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
||||
private fun getLastWatchedOld(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
||||
if (id == null) return null
|
||||
return getKey(
|
||||
"$currentAccount/$RESULT_RESUME_WATCHING_OLD",
|
||||
|
@ -192,8 +193,9 @@ object DataStoreHelper {
|
|||
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
|
||||
}
|
||||
|
||||
fun getDub(id: Int): DubStatus {
|
||||
return DubStatus.values()[getKey("$currentAccount/$RESULT_DUB", id.toString()) ?: 0]
|
||||
fun getDub(id: Int): DubStatus? {
|
||||
return DubStatus.values()
|
||||
.getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1)
|
||||
}
|
||||
|
||||
fun setDub(id: Int, status: DubStatus) {
|
||||
|
@ -221,14 +223,22 @@ object DataStoreHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun getResultSeason(id: Int): Int {
|
||||
return getKey("$currentAccount/$RESULT_SEASON", id.toString()) ?: -1
|
||||
fun getResultSeason(id: Int): Int? {
|
||||
return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null)
|
||||
}
|
||||
|
||||
fun setResultSeason(id: Int, value: Int?) {
|
||||
setKey("$currentAccount/$RESULT_SEASON", id.toString(), value)
|
||||
}
|
||||
|
||||
fun getResultEpisode(id: Int): Int? {
|
||||
return getKey("$currentAccount/$RESULT_EPISODE", id.toString(), null)
|
||||
}
|
||||
|
||||
fun setResultEpisode(id: Int, value: Int?) {
|
||||
setKey("$currentAccount/$RESULT_EPISODE", id.toString(), value)
|
||||
}
|
||||
|
||||
fun addSync(id: Int, idPrefix: String, url: String) {
|
||||
setKey("${idPrefix}_sync", id.toString(), url)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import kotlinx.android.synthetic.main.add_account_input.*
|
||||
import kotlinx.android.synthetic.main.add_account_input.text1
|
||||
import kotlinx.android.synthetic.main.bottom_selection_dialog_direct.*
|
||||
|
||||
object SingleSelectionHelper {
|
||||
fun Activity?.showOptionSelectStringRes(
|
||||
|
@ -21,7 +24,7 @@ object SingleSelectionHelper {
|
|||
tvOptions: List<Int> = listOf(),
|
||||
callback: (Pair<Boolean, Int>) -> Unit
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
this.showOptionSelect(
|
||||
view,
|
||||
|
@ -39,7 +42,7 @@ object SingleSelectionHelper {
|
|||
tvOptions: List<String>,
|
||||
callback: (Pair<Boolean, Int>) -> Unit
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
if (this.isTvSettings()) {
|
||||
val builder =
|
||||
|
@ -86,42 +89,44 @@ object SingleSelectionHelper {
|
|||
showApply: Boolean,
|
||||
isMultiSelect: Boolean,
|
||||
callback: (List<Int>) -> Unit,
|
||||
dismissCallback: () -> Unit
|
||||
dismissCallback: () -> Unit,
|
||||
itemLayout: Int = R.layout.sort_bottom_single_choice
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val realShowApply = showApply || isMultiSelect
|
||||
val listView = dialog.findViewById<ListView>(R.id.listview1)!!
|
||||
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
||||
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
|
||||
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
|
||||
val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!!
|
||||
val listView = dialog.listview1//.findViewById<ListView>(R.id.listview1)!!
|
||||
val textView = dialog.text1//.findViewById<TextView>(R.id.text1)!!
|
||||
val applyButton = dialog.apply_btt//.findViewById<TextView>(R.id.apply_btt)
|
||||
val cancelButton = dialog.cancel_btt//findViewById<TextView>(R.id.cancel_btt)
|
||||
val applyHolder = dialog.apply_btt_holder//.findViewById<LinearLayout>(R.id.apply_btt_holder)
|
||||
|
||||
applyHolder.isVisible = realShowApply
|
||||
applyHolder?.isVisible = realShowApply
|
||||
if (!realShowApply) {
|
||||
val params = listView.layoutParams as LinearLayout.LayoutParams
|
||||
params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0)
|
||||
listView.layoutParams = params
|
||||
}
|
||||
|
||||
textView.text = name
|
||||
textView?.text = name
|
||||
textView?.isGone = name.isBlank()
|
||||
|
||||
val arrayAdapter = ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
|
||||
val arrayAdapter = ArrayAdapter<String>(this, itemLayout)
|
||||
arrayAdapter.addAll(items)
|
||||
|
||||
listView.adapter = arrayAdapter
|
||||
listView?.adapter = arrayAdapter
|
||||
if (isMultiSelect) {
|
||||
listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||
listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||
} else {
|
||||
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||
listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||
}
|
||||
|
||||
for (select in selectedIndex) {
|
||||
listView.setItemChecked(select, true)
|
||||
listView?.setItemChecked(select, true)
|
||||
}
|
||||
|
||||
selectedIndex.minOrNull()?.let {
|
||||
listView.setSelection(it)
|
||||
listView?.setSelection(it)
|
||||
}
|
||||
|
||||
// var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1
|
||||
|
@ -130,7 +135,7 @@ object SingleSelectionHelper {
|
|||
dismissCallback.invoke()
|
||||
}
|
||||
|
||||
listView.setOnItemClickListener { _, _, which, _ ->
|
||||
listView?.setOnItemClickListener { _, _, which, _ ->
|
||||
// lastSelectedIndex = which
|
||||
if (realShowApply) {
|
||||
if (!isMultiSelect) {
|
||||
|
@ -142,7 +147,7 @@ object SingleSelectionHelper {
|
|||
}
|
||||
}
|
||||
if (realShowApply) {
|
||||
applyButton.setOnClickListener {
|
||||
applyButton?.setOnClickListener {
|
||||
val list = ArrayList<Int>()
|
||||
for (index in 0 until listView.count) {
|
||||
if (listView.checkedItemPositions[index])
|
||||
|
@ -151,7 +156,7 @@ object SingleSelectionHelper {
|
|||
callback.invoke(list)
|
||||
dialog.dismissSafe(this)
|
||||
}
|
||||
cancelButton.setOnClickListener {
|
||||
cancelButton?.setOnClickListener {
|
||||
dialog.dismissSafe(this)
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +171,7 @@ object SingleSelectionHelper {
|
|||
callback: (String) -> Unit,
|
||||
dismissCallback: () -> Unit
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
|
||||
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
||||
|
@ -205,7 +210,7 @@ object SingleSelectionHelper {
|
|||
dismissCallback: () -> Unit,
|
||||
callback: (List<Int>) -> Unit,
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val builder =
|
||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||
|
@ -224,7 +229,7 @@ object SingleSelectionHelper {
|
|||
dismissCallback: () -> Unit,
|
||||
callback: (Int) -> Unit,
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val builder =
|
||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||
|
@ -271,6 +276,31 @@ object SingleSelectionHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun Activity.showBottomDialogInstant(
|
||||
items: List<String>,
|
||||
name: String,
|
||||
dismissCallback: () -> Unit,
|
||||
callback: (Int) -> Unit,
|
||||
): BottomSheetDialog {
|
||||
val builder =
|
||||
BottomSheetDialog(this)
|
||||
builder.setContentView(R.layout.bottom_selection_dialog_direct)
|
||||
|
||||
builder.show()
|
||||
showDialog(
|
||||
builder,
|
||||
items,
|
||||
listOf(),
|
||||
name,
|
||||
showApply = false,
|
||||
isMultiSelect = false,
|
||||
callback = { if (it.isNotEmpty()) callback.invoke(it.first()) },
|
||||
dismissCallback = dismissCallback,
|
||||
itemLayout = R.layout.sort_bottom_single_choice_no_checkmark
|
||||
)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun Activity.showNginxTextInputDialog(
|
||||
name: String,
|
||||
value: String,
|
||||
|
|
5
app/src/main/res/color/selectable_black.xml
Normal file
5
app/src/main/res/color/selectable_black.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true" android:color="?attr/iconGrayBackground" />
|
||||
<item android:color="?attr/textColor" />
|
||||
</selector>
|
5
app/src/main/res/color/selectable_white.xml
Normal file
5
app/src/main/res/color/selectable_white.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true" android:color="?attr/textColor"/>
|
||||
<item android:color="?attr/iconGrayBackground"/>
|
||||
</selector>
|
4
app/src/main/res/drawable/outline_drawable_less.xml
Normal file
4
app/src/main/res/drawable/outline_drawable_less.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true" android:drawable="@drawable/outline_less" /> <!-- focused -->
|
||||
</selector>
|
10
app/src/main/res/drawable/outline_less.xml
Normal file
10
app/src/main/res/drawable/outline_less.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<stroke android:width="2dp"
|
||||
android:color="?attr/white"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="@dimen/rounded_button_radius"
|
||||
android:bottomRightRadius="@dimen/rounded_button_radius"
|
||||
android:topLeftRadius="@dimen/rounded_button_radius"
|
||||
android:topRightRadius="@dimen/rounded_button_radius" />
|
||||
</shape>
|
33
app/src/main/res/layout/bottom_loading.xml
Normal file
33
app/src/main/res/layout/bottom_loading.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:text="@string/loading"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:layout_marginBottom="-6.5dp"
|
||||
android:indeterminate="true"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminateTint="?attr/colorPrimary"
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:progressTint="?attr/colorPrimary"
|
||||
android:layout_height="15dp">
|
||||
</androidx.core.widget.ContentLoadingProgressBar>
|
||||
</LinearLayout>
|
34
app/src/main/res/layout/bottom_selection_dialog_direct.xml
Normal file
34
app/src/main/res/layout/bottom_selection_dialog_direct.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
tools:text="Test"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ListView
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
|
||||
android:id="@+id/listview1"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:paddingTop="10dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
tools:listitem="@layout/sort_bottom_single_choice_no_checkmark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_rowWeight="1" />
|
||||
</LinearLayout>
|
|
@ -12,7 +12,7 @@
|
|||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/loading_chromecast"
|
||||
android:text="@string/loading"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/textColor"
|
||||
android:textSize="20sp"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
android:id="@+id/result_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
style="@style/DarkFragment"
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
@ -290,15 +291,15 @@
|
|||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/result_poster_holder"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/result_poster"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="140dp"
|
||||
android:contentDescription="@string/result_poster_img_des"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:scaleType="centerCrop"
|
||||
|
@ -465,6 +466,15 @@
|
|||
android:textSize="15sp"
|
||||
tools:text="@string/provider_info_meta" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_no_episodes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/no_episodes_found" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_tag_holder"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -669,7 +679,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_series_parent"
|
||||
android:id="@+id/result_resume_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
|
@ -835,7 +845,6 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/result_next_airing_holder"
|
||||
android:layout_gravity="start"
|
||||
android:paddingBottom="15dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
style="@style/AlertDialogCustom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
|
788
app/src/main/res/layout/fragment_result_tv.xml
Normal file
788
app/src/main/res/layout/fragment_result_tv.xml
Normal file
|
@ -0,0 +1,788 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/result_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
style="@style/DarkFragment"
|
||||
android:background="?attr/primaryBlackBackground">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/result_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
app:shimmer_auto_start="true"
|
||||
app:shimmer_base_alpha="0.2"
|
||||
app:shimmer_duration="@integer/loading_time"
|
||||
app:shimmer_highlight_alpha="0.3"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/result_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="@dimen/loading_margin"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<include layout="@layout/loading_poster" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/loading_margin"
|
||||
android:layout_marginEnd="@dimen/loading_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/loading_line" />
|
||||
|
||||
<include layout="@layout/loading_line" />
|
||||
|
||||
<include layout="@layout/loading_line" />
|
||||
|
||||
<include layout="@layout/loading_line" />
|
||||
|
||||
<include layout="@layout/loading_line_short" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_loading_error"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="gone">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_reload_connectionerror"
|
||||
style="@style/WhiteButton"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="5dp"
|
||||
android:minWidth="200dp"
|
||||
android:text="@string/reload_error"
|
||||
app:icon="@drawable/ic_baseline_autorenew_24" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_reload_connection_open_in_browser"
|
||||
style="@style/BlackButton"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="5dp"
|
||||
android:minWidth="200dp"
|
||||
android:text="@string/result_open_in_browser"
|
||||
app:icon="@drawable/ic_baseline_public_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_error_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="5dp"
|
||||
android:gravity="center"
|
||||
android:textColor="?attr/textColor" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_finish_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/result_scroll"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/primaryGrayBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
tools:visibility="gone"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/result_trailer_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
app:shimmer_auto_start="true"
|
||||
app:shimmer_base_alpha="0.2"
|
||||
app:shimmer_duration="@integer/loading_time"
|
||||
app:shimmer_highlight_alpha="0.3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/result_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:background="@color/grayShimmer"
|
||||
app:cardCornerRadius="@dimen/loading_radius"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="150dp"
|
||||
android:foreground="@drawable/outline_drawable" />
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:id="@+id/result_smallscreen_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/fragment_trailer" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/result_padding"
|
||||
android:paddingEnd="@dimen/result_padding">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="visible">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_height="30dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginEnd="10dp"
|
||||
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
|
||||
android:id="@+id/result_back"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
|
||||
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||
android:contentDescription="@string/go_back"
|
||||
app:tint="?attr/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="The Perfect Run The Perfect Run" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:itemSpacing="10dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_meta_site"
|
||||
style="@style/SmallBlackButton"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:text="Gogoanime" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_type"
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="Movie" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_year"
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="2022" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_rating"
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="Rated: 8.5/10.0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_status"
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="Ongoing" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_duration"
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="121min" />
|
||||
</com.lagradost.cloudstream3.widget.FlowLayout>
|
||||
|
||||
<!--
|
||||
This has half margin and half padding to make TV focus on description look better.
|
||||
The focus outline now settles between the poster and text.
|
||||
-->
|
||||
<TextView
|
||||
android:padding="5dp"
|
||||
android:maxLength="1000"
|
||||
android:ellipsize="end"
|
||||
android:id="@+id/result_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_bookmark_button"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="Ryan Quicksave Romano is an eccentric adventurer with a strange power: he can create a save-point in time and redo his life whenever he dies. Arriving in New Rome, the glitzy capital of sin of a rebuilding Europe, he finds the city torn between mega-corporations, sponsored heroes, superpowered criminals, and true monsters. It's a time of chaos, where potions can grant the power to rule the world and dangers lurk everywhere. " />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_cast_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="Cast: Joe Ligma" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
tools:visibility="visible"
|
||||
android:nextFocusUp="@id/result_bookmark_button"
|
||||
android:nextFocusDown="@id/result_play_movie"
|
||||
|
||||
android:id="@+id/result_cast_items"
|
||||
android:layout_width="match_parent"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadingEdge="horizontal"
|
||||
android:focusableInTouchMode="false"
|
||||
android:focusable="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="5dp"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="2"
|
||||
tools:listitem="@layout/cast_item" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_vpn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/vpn_torrent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/provider_info_meta" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_no_episodes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/no_episodes_found" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_tag_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/result_tags"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="normal"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
android:id="@+id/result_tag"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_coming_soon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingTop="50dp"
|
||||
android:text="@string/coming_soon"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_data_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_add_sync"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/add_sync"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_add_24" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_movie_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:nextFocusRight="@id/result_download_movie"
|
||||
android:nextFocusUp="@id/result_cast_items"
|
||||
android:nextFocusDown="@id/result_resume_series_button_play"
|
||||
|
||||
android:id="@+id/result_play_movie"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/play_movie_button"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24">
|
||||
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:minWidth="250dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:id="@+id/result_movie_progress_downloaded_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:nextFocusLeft="@id/result_play_movie"
|
||||
android:nextFocusUp="@id/result_cast_items"
|
||||
android:nextFocusDown="@id/result_resume_series_button_play"
|
||||
|
||||
android:id="@+id/result_download_movie"
|
||||
style="@style/BlackButton"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
|
||||
android:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/result_movie_progress_downloaded"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/circle_shape"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:progress="30"
|
||||
android:progressDrawable="@drawable/circular_progress_bar_filled"
|
||||
android:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/result_movie_download_icon"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/download"
|
||||
android:src="@drawable/ic_baseline_play_arrow_24"
|
||||
android:visibility="visible"
|
||||
app:tint="?attr/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_movie_download_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.09"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Downloading" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_movie_download_text_precentage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.09"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:text="68%" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_resume_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_next_series_button"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:nextFocusUp="@id/result_bookmark_button"
|
||||
android:nextFocusDown="@id/result_download_movie"
|
||||
android:text="@string/next_episode"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/cast_ic_mini_controller_skip_next" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/result_resume_series_button_play"
|
||||
android:nextFocusUp="@id/result_play_movie"
|
||||
android:nextFocusDown="@id/result_season_selection"
|
||||
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/download"
|
||||
android:src="@drawable/ic_baseline_play_arrow_24"
|
||||
android:visibility="visible"
|
||||
app:tint="?attr/white" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:id="@+id/result_resume_series_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="S1E1 Episode 1" />
|
||||
|
||||
<TextView
|
||||
android:maxLines="1"
|
||||
android:id="@+id/result_resume_series_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:ignore="RtlSymmetry"
|
||||
tools:text="69m remaining" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_resume_progress_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="10dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/result_resume_series_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
android:visibility="visible"
|
||||
tools:progress="50"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:nextFocusUp="@id/result_resume_series_button_play"
|
||||
android:nextFocusDown="@id/result_range_selection"
|
||||
android:id="@+id/result_season_selection"
|
||||
|
||||
android:paddingBottom="10dp"
|
||||
tools:listitem="@layout/result_selection"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:nextFocusUp="@id/result_season_selection"
|
||||
android:nextFocusDown="@id/result_dub_selection"
|
||||
android:id="@+id/result_range_selection"
|
||||
|
||||
android:paddingBottom="10dp"
|
||||
tools:listitem="@layout/result_selection"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:nextFocusUp="@id/result_range_selection"
|
||||
android:nextFocusDown="@id/result_episodes"
|
||||
android:id="@+id/result_dub_selection"
|
||||
|
||||
android:paddingBottom="10dp"
|
||||
tools:listitem="@layout/result_selection"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_marginBottom="5dp"
|
||||
android:id="@+id/result_next_airing_holder"
|
||||
android:layout_gravity="start"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:id="@+id/result_episodes_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="normal"
|
||||
tools:text="8 Episodes" />
|
||||
<TextView
|
||||
android:gravity="center"
|
||||
|
||||
android:id="@+id/result_next_airing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="normal"
|
||||
tools:text="Episode 1022 will be released in" />
|
||||
|
||||
<TextView
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingStart="5dp"
|
||||
android:gravity="center"
|
||||
android:id="@+id/result_next_airing_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="normal"
|
||||
tools:text="5d 3h 30m" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
tools:visibility="gone"
|
||||
android:id="@+id/result_episode_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="15dp"
|
||||
android:orientation="vertical"
|
||||
app:shimmer_auto_start="true"
|
||||
app:shimmer_base_alpha="0.2"
|
||||
app:shimmer_duration="@integer/loading_time"
|
||||
app:shimmer_highlight_alpha="0.3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
|
||||
<include layout="@layout/loading_episode" />
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
<!--<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/result_episode_loading"
|
||||
|
||||
style="@style/Widget.AppCompat.ProgressBar"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp" />-->
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:nextFocusUp="@id/result_dub_selection"
|
||||
android:nextFocusDown="@id/result_recommendations_filter_selection"
|
||||
android:id="@+id/result_episodes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/result_episode" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_recommendations_holder"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
tools:itemCount="2"
|
||||
android:nextFocusUp="@id/result_episodes"
|
||||
android:nextFocusDown="@id/result_recommendations"
|
||||
android:id="@+id/result_recommendations_filter_selection"
|
||||
android:layout_marginEnd="10dp"
|
||||
|
||||
tools:listitem="@layout/result_selection"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textSize="17sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:text="@string/recommended"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
android:nextFocusUp="@id/result_recommendations_filter_selection"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
app:spanCount="8"
|
||||
android:id="@+id/result_recommendations"
|
||||
tools:listitem="@layout/search_result_grid"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/DarkFragment"
|
||||
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/quick_search_root"
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
android:nextFocusLeft="@id/result_episode_download"
|
||||
android:nextFocusRight="@id/result_episode_download"
|
||||
|
||||
android:id="@+id/episode_holder"
|
||||
|
@ -15,6 +13,10 @@
|
|||
app:cardElevation="0dp"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:layout_marginBottom="5dp">
|
||||
<!--
|
||||
android:nextFocusLeft="@id/result_episode_download"
|
||||
-->
|
||||
|
||||
<!-- IDK BUT THIS DOES NOT SEAM LIKE A GOOD WAY OF DOING IT -->
|
||||
<!--<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
|
@ -110,9 +112,10 @@
|
|||
android:progress="0"
|
||||
android:visibility="visible" />
|
||||
|
||||
<!--
|
||||
android:nextFocusRight="@id/episode_holder"-->
|
||||
<ImageView
|
||||
android:nextFocusLeft="@id/episode_holder"
|
||||
android:nextFocusRight="@id/episode_holder"
|
||||
app:tint="?attr/white"
|
||||
android:id="@+id/result_episode_download"
|
||||
android:visibility="visible"
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<include android:visibility="gone" layout="@layout/result_episode" />
|
||||
<include android:visibility="gone" layout="@layout/result_episode_large" />
|
||||
<include android:visibility="visible" layout="@layout/result_episode_large" />
|
||||
</FrameLayout>
|
20
app/src/main/res/layout/result_episode_both_tv.xml
Normal file
20
app/src/main/res/layout/result_episode_both_tv.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<include
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
layout="@layout/result_episode_tv" />
|
||||
|
||||
<include
|
||||
tools:visibility="gone"
|
||||
android:visibility="gone"
|
||||
android:layout_width="450dp"
|
||||
android:layout_height="wrap_content"
|
||||
layout="@layout/result_episode_large_tv" />
|
||||
</FrameLayout>
|
|
@ -3,11 +3,10 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
android:nextFocusLeft="@id/episode_poster"
|
||||
android:nextFocusRight="@id/result_episode_download"
|
||||
android:id="@+id/episode_holder_large"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:cardBackgroundColor="?attr/boxItemBackground"
|
||||
|
@ -19,7 +18,7 @@
|
|||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:padding="10dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
|
@ -33,8 +32,7 @@
|
|||
android:foreground="@drawable/outline_drawable">
|
||||
|
||||
<ImageView
|
||||
android:nextFocusLeft="@id/result_episode_download"
|
||||
android:nextFocusRight="@id/episode_holder"
|
||||
android:nextFocusRight="@id/result_episode_download"
|
||||
|
||||
android:id="@+id/episode_poster"
|
||||
tools:src="@drawable/example_poster"
|
||||
|
@ -126,7 +124,6 @@
|
|||
|
||||
<ImageView
|
||||
android:nextFocusLeft="@id/episode_poster"
|
||||
android:nextFocusRight="@id/episode_holder"
|
||||
android:id="@+id/result_episode_download"
|
||||
|
||||
android:visibility="visible"
|
||||
|
|
113
app/src/main/res/layout/result_episode_large_tv.xml
Normal file
113
app/src/main/res/layout/result_episode_large_tv.xml
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
android:nextFocusRight="@id/result_episode_download"
|
||||
android:id="@+id/episode_holder_large"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:cardBackgroundColor="?attr/boxItemBackground"
|
||||
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:padding="10dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content">
|
||||
<!--app:cardCornerRadius="@dimen/roundedImageRadius"-->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="126dp"
|
||||
android:layout_height="72dp"
|
||||
android:foreground="@drawable/outline_drawable">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/episode_poster"
|
||||
tools:src="@drawable/example_poster"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/episode_poster_img_des" />
|
||||
|
||||
<ImageView
|
||||
android:src="@drawable/play_button"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:contentDescription="@string/play_episode" />
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:layout_marginBottom="-1.5dp"
|
||||
android:id="@+id/episode_progress"
|
||||
android:progressTint="?attr/colorPrimary"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
tools:progress="50"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_height="5dp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_marginStart="15dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginEnd="50dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_gravity="start"
|
||||
style="@style/SmallBlackButton"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:text="@string/filler"
|
||||
android:id="@+id/episode_filler" />
|
||||
|
||||
<TextView
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/episode_text"
|
||||
tools:text="1. Jobless"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/episode_rating"
|
||||
tools:text="Rated: 8.8"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:maxLines="4"
|
||||
android:ellipsize="end"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:id="@+id/episode_descript"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart."
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
62
app/src/main/res/layout/result_episode_tv.xml
Normal file
62
app/src/main/res/layout/result_episode_tv.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:nextFocusRight="@id/result_episode_download"
|
||||
|
||||
android:id="@+id/episode_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:cardBackgroundColor="@color/transparent"
|
||||
app:cardElevation="0dp"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:layout_marginBottom="5dp">
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:layout_marginBottom="-1.5dp"
|
||||
android:id="@+id/episode_progress"
|
||||
android:progressTint="?attr/colorPrimary"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
tools:progress="50"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_height="5dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:gravity="center"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!--marquee_forever-->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_marginEnd="10dp"
|
||||
tools:visibility="visible"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"
|
||||
style="@style/SmallBlackButton"
|
||||
android:text="@string/filler"
|
||||
android:id="@+id/episode_filler" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/episode_text"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center"
|
||||
tools:text="Episode 1"
|
||||
android:textColor="?attr/textColor"
|
||||
|
||||
android:scrollHorizontally="true"
|
||||
android:ellipsize="marquee"
|
||||
|
||||
android:marqueeRepeatLimit="0"
|
||||
android:singleLine="true"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -12,6 +12,6 @@
|
|||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/default_cover"
|
||||
android:background="#fffff0"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
android:contentDescription="@string/poster_image" />
|
||||
</LinearLayout>
|
7
app/src/main/res/layout/result_selection.xml
Normal file
7
app/src/main/res/layout/result_selection.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/SelectableButton"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
tools:text="Season 1" />
|
|
@ -0,0 +1,22 @@
|
|||
<!--<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeightSmall"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
tools:text="Example Text"
|
||||
android:background="?attr/bitDarkerGrayBackground"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorSingle"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"/>
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/NoCheckLabel"
|
||||
tools:text="hello"
|
||||
android:textStyle="normal"
|
||||
android:textColor="?attr/textColor"
|
||||
android:id="@android:id/text1" />
|
|
@ -4,10 +4,35 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/mobile_navigation"
|
||||
app:startDestination="@+id/navigation_home">
|
||||
|
||||
<action
|
||||
android:id="@+id/global_to_navigation_results"
|
||||
app:destination="@id/navigation_results"
|
||||
android:id="@+id/global_to_navigation_results_tv"
|
||||
app:destination="@id/navigation_results_tv"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim">
|
||||
<argument
|
||||
android:name="url"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="apiName"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="startAction"
|
||||
android:defaultValue="0"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="startValue"
|
||||
android:defaultValue="0"
|
||||
app:argType="integer" />
|
||||
<argument
|
||||
android:name="restart"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
</action>
|
||||
<action
|
||||
android:id="@+id/global_to_navigation_results_phone"
|
||||
app:destination="@id/navigation_results_phone"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
|
@ -216,13 +241,6 @@
|
|||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_home">
|
||||
<action
|
||||
android:id="@+id/action_navigation_home_to_navigation_results"
|
||||
app:destination="@id/navigation_results"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_home_to_navigation_quick_search"
|
||||
app:destination="@id/navigation_quick_search"
|
||||
|
@ -241,13 +259,6 @@
|
|||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_search">
|
||||
<action
|
||||
android:id="@+id/action_navigation_search_to_navigation_results"
|
||||
app:destination="@id/navigation_results"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -274,13 +285,6 @@
|
|||
android:name="folder"
|
||||
app:argType="string" />
|
||||
</action>
|
||||
<action
|
||||
android:id="@+id/action_navigation_downloads_to_navigation_results"
|
||||
app:destination="@id/navigation_results"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_downloads_to_navigation_player"
|
||||
app:destination="@id/navigation_player"
|
||||
|
@ -402,6 +406,56 @@
|
|||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_results_phone"
|
||||
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentPhone"
|
||||
android:layout_height="match_parent"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_result_swipe">
|
||||
<action
|
||||
android:id="@+id/action_navigation_results_phone_to_navigation_quick_search"
|
||||
app:destination="@id/navigation_quick_search"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_results_phone_to_navigation_player"
|
||||
app:destination="@id/navigation_player"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_results_tv"
|
||||
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentTv"
|
||||
android:layout_height="match_parent"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim"
|
||||
tools:layout="@layout/fragment_result_swipe">
|
||||
<action
|
||||
android:id="@+id/action_navigation_results_tv_to_navigation_quick_search"
|
||||
app:destination="@id/navigation_quick_search"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_results_tv_to_navigation_player"
|
||||
app:destination="@id/navigation_player"
|
||||
app:enterAnim="@anim/enter_anim"
|
||||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
</fragment>
|
||||
|
||||
<!--<fragment
|
||||
android:id="@+id/navigation_results"
|
||||
android:name="com.lagradost.cloudstream3.ui.result.ResultFragment"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -424,7 +478,7 @@
|
|||
app:exitAnim="@anim/exit_anim"
|
||||
app:popEnterAnim="@anim/enter_anim"
|
||||
app:popExitAnim="@anim/exit_anim" />
|
||||
</fragment>
|
||||
</fragment>-->
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_player"
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<string name="result_share">شارك</string>
|
||||
<string name="result_open_in_browser">فتح في الويب </string>
|
||||
<string name="skip_loading">تخطي التحميل</string>
|
||||
<string name="loading_chromecast">…تحميل</string>
|
||||
<string name="loading">…تحميل</string>
|
||||
|
||||
<string name="type_watching">مشاهدة</string>
|
||||
<string name="type_on_hold">في الانتظار</string>
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
<string name="result_share">Compartilhar</string>
|
||||
<string name="result_open_in_browser">Abrir no Navegador</string>
|
||||
<string name="skip_loading">Pular Carregamento</string>
|
||||
<string name="loading_chromecast">Carregando…</string>
|
||||
<string name="loading">Carregando…</string>
|
||||
|
||||
<string name="type_watching">Assistindo</string>
|
||||
<string name="type_on_hold">Em espera</string>
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
<string name="result_share">Sdílet</string>
|
||||
<string name="result_open_in_browser">Otevřít v prohlížeči</string>
|
||||
<string name="skip_loading">Přeskočit načítání</string>
|
||||
<string name="loading_chromecast">Načítání…</string>
|
||||
<string name="loading">Načítání…</string>
|
||||
|
||||
<string name="type_watching">Sledování</string>
|
||||
<string name="type_on_hold">Pozastaveno</string>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<string name="result_share">Teilen</string>
|
||||
<string name="result_open_in_browser">Im Browser öffnen</string>
|
||||
<string name="skip_loading">Buffern überspringen</string>
|
||||
<string name="loading_chromecast">Lädt…</string>
|
||||
<string name="loading">Lädt…</string>
|
||||
<string name="type_watching">Am schauen</string>
|
||||
<string name="type_on_hold">Pausiert</string>
|
||||
<string name="type_completed">Abgeschlossen</string>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<string name="result_share">Μοίρασε</string>
|
||||
<string name="result_open_in_browser">Άνοιγμα στον περιηγητή</string>
|
||||
<string name="skip_loading">Προσπέραση φορτώματος</string>
|
||||
<string name="loading_chromecast">Φόρτωση…</string>
|
||||
<string name="loading">Φόρτωση…</string>
|
||||
|
||||
<string name="type_watching">Watching</string>
|
||||
<string name="type_on_hold">On-Hold</string>
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<string name="result_share">Compartir</string>
|
||||
<string name="result_open_in_browser">Abrir en el navegador</string>
|
||||
<string name="skip_loading">Omitir carga</string>
|
||||
<string name="loading_chromecast">Cargando…</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
|
||||
<string name="type_watching">Viendo</string>
|
||||
<string name="type_on_hold">En espera</string>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<string name="result_share">Partager</string>
|
||||
<string name="result_open_in_browser">Ouvrir dans le naviguateur</string>
|
||||
<string name="skip_loading">Passer le chargement</string>
|
||||
<string name="loading_chromecast">Chargement…</string>
|
||||
<string name="loading">Chargement…</string>
|
||||
<string name="type_watching">En visionnage</string>
|
||||
<string name="type_on_hold">En pose</string>
|
||||
<string name="type_completed">Terminé</string>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<string name="result_share">Bagikan</string>
|
||||
<string name="result_open_in_browser">Buka Di Browser</string>
|
||||
<string name="skip_loading">Skip Loading</string>
|
||||
<string name="loading_chromecast">Loading…</string>
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
<string name="type_watching">Sedang Menonton</string>
|
||||
<string name="type_on_hold">Tertahan</string>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<string name="result_share">Condividi</string>
|
||||
<string name="result_open_in_browser">Apri nel browser</string>
|
||||
<string name="skip_loading">Salta caricamento</string>
|
||||
<string name="loading_chromecast">Caricamento…</string>
|
||||
<string name="loading">Caricamento…</string>
|
||||
|
||||
<string name="type_watching">Guardando</string>
|
||||
<string name="type_on_hold">In attesa</string>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string name="result_share">Сподели</string>
|
||||
<string name="result_open_in_browser">Отвори во прелистувач</string>
|
||||
<string name="skip_loading">Прескокни вчитување</string>
|
||||
<string name="loading_chromecast">Вчитување…</string>
|
||||
<string name="loading">Вчитување…</string>
|
||||
|
||||
<string name="type_watching">Моментални гледања</string>
|
||||
<string name="type_on_hold">Ставено на чекање</string>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<string name="result_share">aauuh</string>
|
||||
<string name="result_open_in_browser">oooohh oooohhhaaaoouuh</string>
|
||||
<string name="skip_loading">oooohhooooo</string>
|
||||
<string name="loading_chromecast">ooh aaahhu</string>
|
||||
<string name="loading">ooh aaahhu</string>
|
||||
<string name="type_watching">aaaghh ooo-ahah</string>
|
||||
<string name="type_on_hold">aaahhu</string>
|
||||
<string name="type_completed">ahhahooo</string>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<string name="result_share">Deel</string>
|
||||
<string name="result_open_in_browser">Openen in Browser</string>
|
||||
<string name="skip_loading">Laden overslaan</string>
|
||||
<string name="loading_chromecast">Laden…</string>
|
||||
<string name="loading">Laden…</string>
|
||||
|
||||
<string name="type_watching">Aan het kijken</string>
|
||||
<string name="type_on_hold">In de wacht</string>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<string name="result_share">Dele</string>
|
||||
<string name="result_open_in_browser">Åpne i nettleseren</string>
|
||||
<string name="skip_loading">Hopp over</string>
|
||||
<string name="loading_chromecast">Laster inn…</string>
|
||||
<string name="loading">Laster inn…</string>
|
||||
|
||||
<string name="type_watching">Ser på</string>
|
||||
<string name="type_on_hold">På vent</string>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<string name="result_share">Udostępnij</string>
|
||||
<string name="result_open_in_browser">Otwórz w przeglądarce</string>
|
||||
<string name="skip_loading">Pomiń ładowanie</string>
|
||||
<string name="loading_chromecast">Ładowanie…</string>
|
||||
<string name="loading">Ładowanie…</string>
|
||||
|
||||
<string name="type_watching">W trakcie</string>
|
||||
<string name="type_on_hold">Zawieszone</string>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<string name="result_share">Compartir</string>
|
||||
<string name="result_open_in_browser">Abrir no Navegador</string>
|
||||
<string name="skip_loading">Saltar Carga</string>
|
||||
<string name="loading_chromecast">Cargando…</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
|
||||
<string name="type_watching">Assistindo</string>
|
||||
<string name="type_on_hold">Em espera</string>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<string name="result_share">Distribuie</string>
|
||||
<string name="result_open_in_browser">Deschide în browser</string>
|
||||
<string name="skip_loading">Săriți încărcarea</string>
|
||||
<string name="loading_chromecast">Se încarcă...</string>
|
||||
<string name="loading">Se încarcă...</string>
|
||||
|
||||
<string name="type_watching">În curs de vizualizare</string>
|
||||
<string name="type_on_hold">În așteptare</string>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string name="result_share">Dela</string>
|
||||
<string name="result_open_in_browser">Öppna i webbläsaren</string>
|
||||
<string name="skip_loading">Hoppa över</string>
|
||||
<string name="loading_chromecast">Laddar…</string>
|
||||
<string name="loading">Laddar…</string>
|
||||
|
||||
<string name="type_watching">Tittar på</string>
|
||||
<string name="type_on_hold">Pausad</string>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<string name="result_share">I-share</string>
|
||||
<string name="result_open_in_browser">Buksan sa browser</string>
|
||||
<string name="skip_loading">Skip Loading…</string>
|
||||
<string name="loading_chromecast">Loading…</string>
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
<string name="type_watching">Pinapanood</string>
|
||||
<string name="type_on_hold">Inihinto</string>
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<string name="result_share">Paylaş</string>
|
||||
<string name="result_open_in_browser">Tarayıcıda aç</string>
|
||||
<string name="skip_loading">Yüklemeyi atla</string>
|
||||
<string name="loading_chromecast">Yükleniyor…</string>
|
||||
<string name="loading">Yükleniyor…</string>
|
||||
|
||||
<string name="type_watching">İzleniyor</string>
|
||||
<string name="type_on_hold">Beklemede</string>
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
<string name="result_share">Chia sẻ</string>
|
||||
<string name="result_open_in_browser">Mở bằng trình duyệt</string>
|
||||
<string name="skip_loading">Bỏ qua</string>
|
||||
<string name="loading_chromecast">Đang tải…</string>
|
||||
<string name="loading">Đang tải…</string>
|
||||
|
||||
<string name="type_watching">Đang xem</string>
|
||||
<string name="type_on_hold">Đang chờ</string>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<string name="result_share">分享</string>
|
||||
<string name="result_open_in_browser">在浏览器中打开</string>
|
||||
<string name="skip_loading">跳过加载</string>
|
||||
<string name="loading_chromecast">正在加载…</string>
|
||||
<string name="loading">正在加载…</string>
|
||||
|
||||
<string name="type_watching">正在观看</string>
|
||||
<string name="type_on_hold">暂时搁置</string>
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="rounded_image_radius">10dp</dimen>
|
||||
<dimen name="rounded_button_radius">4dp</dimen>
|
||||
|
||||
<dimen name="navbar_height">0dp</dimen>
|
||||
<dimen name="card_corner_radius">2dp</dimen>
|
||||
<dimen name="result_padding">15dp</dimen>
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
<string name="result_share">Share</string>
|
||||
<string name="result_open_in_browser">Open In Browser</string>
|
||||
<string name="skip_loading">Skip Loading</string>
|
||||
<string name="loading_chromecast">Loading…</string>
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
<string name="type_watching">Watching</string>
|
||||
<string name="type_on_hold">On-Hold</string>
|
||||
|
@ -283,9 +283,12 @@
|
|||
</string>
|
||||
|
||||
<string name="season">Season</string>
|
||||
<string name="season_format">%s %d</string>
|
||||
<string name="no_season">No Season</string>
|
||||
<string name="episode">Episode</string>
|
||||
<string name="episodes">Episodes</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="season_short">S</string>
|
||||
<string name="episode_short">E</string>
|
||||
<string name="no_episodes_found">No Episodes found</string>
|
||||
|
|
|
@ -266,6 +266,7 @@
|
|||
</style>
|
||||
|
||||
<style name="AppBottomSheetDialogTheme">
|
||||
<item name="android:navigationBarColor">?attr/boxItemBackground</item>
|
||||
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowAnimationStyle">@style/Animation.Design.BottomSheetDialog</item>
|
||||
|
@ -278,7 +279,7 @@
|
|||
<item name="behavior_skipCollapsed">true</item>
|
||||
<item name="shapeAppearance">@null</item>
|
||||
<item name="shapeAppearanceOverlay">@null</item>
|
||||
<item name="backgroundTint">?android:attr/colorBackground</item>
|
||||
<item name="backgroundTint">@color/transparent</item>
|
||||
<item name="android:background">@drawable/rounded_dialog</item>
|
||||
<item name="behavior_peekHeight">512dp</item>
|
||||
</style>
|
||||
|
@ -334,6 +335,10 @@
|
|||
<item name="tabMode">scrollable</item>-->
|
||||
</style>
|
||||
|
||||
<style name="DarkFragment" parent="AppTheme">
|
||||
<item name="android:navigationBarColor">?attr/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="AlertDialogCustom" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:textColor">?attr/textColor</item>
|
||||
|
@ -427,12 +432,12 @@
|
|||
<item name="android:textAllCaps">false</item>
|
||||
<item name="iconGravity">textStart</item>
|
||||
<item name="iconSize">20dp</item>
|
||||
<item name="cornerRadius">4dp</item>
|
||||
<item name="cornerRadius">@dimen/rounded_button_radius</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
|
||||
<item name="android:insetTop">0dp</item>
|
||||
<item name="android:insetBottom">0dp</item>
|
||||
<item name="android:foreground">@drawable/outline_drawable</item>
|
||||
<item name="android:foreground">@drawable/outline_drawable_less</item>
|
||||
</style>
|
||||
|
||||
<style name="WhiteButton" parent="NiceButton">
|
||||
|
@ -448,7 +453,15 @@
|
|||
<item name="android:textColor">?attr/textColor</item>
|
||||
</style>
|
||||
|
||||
<style name="CheckLabel" parent="@style/AppTextViewStyle">
|
||||
<style name="CheckLabel" parent="@style/NoCheckLabel">
|
||||
|
||||
<!-- <item name="drawableTint">@color/check_selection_color</item>-->
|
||||
<!-- Set color in the drawable instead of tint to allow multiple drawables-->
|
||||
<item name="android:checkMark">?android:attr/listChoiceIndicatorSingle</item>
|
||||
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
|
||||
</style>
|
||||
|
||||
<style name="NoCheckLabel" parent="@style/AppTextViewStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
|
||||
|
@ -458,15 +471,12 @@
|
|||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:checkMark">?android:attr/listChoiceIndicatorSingle</item>
|
||||
<item name="android:ellipsize">marquee</item>
|
||||
<item name="android:foreground">?attr/selectableItemBackgroundBorderless</item>
|
||||
<item name="android:drawablePadding">20dp</item>
|
||||
<!-- <item name="drawableTint">@color/check_selection_color</item>-->
|
||||
<!-- Set color in the drawable instead of tint to allow multiple drawables-->
|
||||
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="BlackButton" parent="NiceButton">
|
||||
<item name="strokeColor">?attr/textColor</item>
|
||||
<item name="backgroundTint">?attr/iconGrayBackground</item>
|
||||
|
@ -534,6 +544,23 @@
|
|||
<style name="MultiSelectButton" parent="BlackButton">
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
|
||||
<item name="strokeColor">?attr/textColor</item>
|
||||
<item name="backgroundTint">?attr/iconGrayBackground</item>
|
||||
<item name="iconTint">?attr/textColor</item>
|
||||
<item name="android:textColor">?attr/textColor</item>
|
||||
<item name="rippleColor">?attr/textColor</item>
|
||||
</style>
|
||||
|
||||
<style name="SelectableButton" parent="NiceButton">
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
|
||||
<item name="strokeColor">@color/selectable_black</item>
|
||||
<item name="backgroundTint">@color/selectable_white</item>
|
||||
<item name="iconTint">@color/selectable_black</item>
|
||||
<item name="android:textColor">@color/selectable_black</item>
|
||||
<item name="rippleColor">@color/selectable_black</item>
|
||||
</style>
|
||||
|
||||
<style name="VideoButton">
|
||||
|
|
Loading…
Reference in a new issue