fixed sync stuff and tv stuff

This commit is contained in:
LagradOst 2022-04-03 23:41:28 +02:00
parent 10c945f497
commit 7cbdc1fc6c
24 changed files with 209 additions and 160 deletions

View file

@ -13,6 +13,8 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.animeproviders.*
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
import com.lagradost.cloudstream3.movieproviders.*
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.ExtractorLink
import okhttp3.Interceptor
@ -703,8 +705,12 @@ interface LoadResponse {
var recommendations: List<SearchResponse>?
var actors: List<ActorData>?
var comingSoon: Boolean
var syncData: MutableMap<String, String>
companion object {
val malIdPrefix = malApi.idPrefix
val aniListIdPrefix = aniListApi.idPrefix
@JvmName("addActorNames")
fun LoadResponse.addActors(actors: List<String>?) {
this.actors = actors?.map { ActorData(Actor(it)) }
@ -725,6 +731,30 @@ interface LoadResponse {
this.actors = actors?.map { actor -> ActorData(actor) }
}
fun LoadResponse.addMalId(id: Int?) {
this.syncData[malIdPrefix] = (id ?: return).toString()
}
fun LoadResponse.addAniListId(id: Int?) {
this.syncData[aniListIdPrefix] = (id ?: return).toString()
}
fun LoadResponse.addImdbUrl(url : String?) {
addImdbId(imdbUrlToIdNullable(url))
}
fun LoadResponse.addImdbId(id: String?) {
// TODO add imdb sync
}
fun LoadResponse.addTrackId(id: String?) {
// TODO add trackt sync
}
fun LoadResponse.addkitsuId(id: String?) {
// TODO add kitsu sync
}
fun LoadResponse.setDuration(input: String?) {
val cleanInput = input?.trim()?.replace(" ", "") ?: return
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
@ -789,6 +819,7 @@ data class TorrentLoadResponse(
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
override var comingSoon: Boolean = false,
override var syncData: MutableMap<String, String> = mutableMapOf(),
) : LoadResponse
data class AnimeLoadResponse(
@ -802,21 +833,20 @@ data class AnimeLoadResponse(
override var posterUrl: String? = null,
override var year: Int? = null,
var episodes: HashMap<DubStatus, List<AnimeEpisode>> = hashMapOf(),
var episodes: MutableMap<DubStatus, List<AnimeEpisode>> = mutableMapOf(),
var showStatus: ShowStatus? = null,
override var plot: String? = null,
override var tags: List<String>? = null,
var synonyms: List<String>? = null,
var malId: Int? = null,
var anilistId: Int? = null,
override var rating: Int? = null,
override var duration: Int? = null,
override var trailerUrl: String? = null,
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
override var comingSoon: Boolean = false,
override var syncData: MutableMap<String, String> = mutableMapOf(),
) : LoadResponse
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<AnimeEpisode>?) {
@ -864,7 +894,6 @@ data class MovieLoadResponse(
override var year: Int? = null,
override var plot: String? = null,
var imdbId: String? = null,
override var rating: Int? = null,
override var tags: List<String>? = null,
override var duration: Int? = null,
@ -872,6 +901,7 @@ data class MovieLoadResponse(
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
override var comingSoon: Boolean = false,
override var syncData: MutableMap<String, String> = mutableMapOf(),
) : LoadResponse
fun MainAPI.newMovieLoadResponse(
@ -916,7 +946,6 @@ data class TvSeriesLoadResponse(
override var plot: String? = null,
var showStatus: ShowStatus? = null,
var imdbId: String? = null,
override var rating: Int? = null,
override var tags: List<String>? = null,
override var duration: Int? = null,
@ -924,6 +953,7 @@ data class TvSeriesLoadResponse(
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
override var comingSoon: Boolean = false,
override var syncData: MutableMap<String, String> = mutableMapOf(),
) : LoadResponse
fun MainAPI.newTvSeriesLoadResponse(

View file

@ -275,21 +275,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (str.contains("/${api.redirectUrl}")) {
ioSafe {
Log.i(TAG, "handleAppIntent $str")
if (api.handleRedirect(str)) {
val isSuccessful = api.handleRedirect(str)
if (isSuccessful) {
Log.i(TAG, "authenticated ${api.name}")
} else {
Log.i(TAG, "failed to authenticate ${api.name}")
}
this.runOnUiThread {
try {
showToast(
this,
getString(R.string.authenticated_user).format(api.name)
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
api.name
)
)
} catch (e: Exception) {
logError(e) // format might fail
}
}
} else {
Log.i(TAG, "failed to authenticate ${api.name}")
}
}
}
}

View file

@ -3,6 +3,8 @@ package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.network.AppResponse
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -305,8 +307,8 @@ class AnimePaheProvider : MainAPI() {
null
}
this.malId = malId
this.anilistId = anilistId
addMalId(malId)
addAniListId(anilistId)
this.trailerUrl = trailer
}
}

View file

@ -1,11 +1,13 @@
package com.lagradost.cloudstream3.animeproviders
import java.util.*
import org.json.JSONObject
import org.jsoup.nodes.Element
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import org.json.JSONObject
import org.jsoup.nodes.Element
import java.util.*
class AnimeWorldProvider : MainAPI() {
override var mainUrl = "https://www.animeworld.tv"
@ -170,8 +172,8 @@ class AnimeWorldProvider : MainAPI() {
showStatus = status
plot = description
tags = genres
this.malId = malId
this.anilistId = anlId
addMalId(malId)
addAniListId(anlId)
this.rating = rating
this.duration = duration
this.trailerUrl = trailerUrl

View file

@ -4,6 +4,8 @@ import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.runSflixExtractorVerifierJob
import com.lagradost.cloudstream3.network.Requests.Companion.await
@ -291,8 +293,8 @@ class ZoroProvider : MainAPI() {
this.tags = tags
this.recommendations = recommendations
this.actors = actors
this.malId = syncData?.malId?.toIntOrNull()
this.anilistId = syncData?.aniListId?.toIntOrNull()
addMalId(syncData?.malId?.toIntOrNull())
addAniListId(syncData?.aniListId?.toIntOrNull())
}
}

View file

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.metaproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.uwetrottmann.tmdb2.Tmdb
import com.uwetrottmann.tmdb2.entities.*
@ -135,7 +136,8 @@ open class TmdbProvider : MainAPI() {
}.get(Calendar.YEAR)
}
plot = overview
imdbId = external_ids?.imdb_id
addImdbId(external_ids?.imdb_id)
tags = genres?.mapNotNull { it.name }
duration = episode_run_time?.average()?.toInt()
rating = this@toLoadResponse.rating
@ -163,7 +165,7 @@ open class TmdbProvider : MainAPI() {
}.get(Calendar.YEAR)
}
plot = overview
imdbId = external_ids?.imdb_id
addImdbId(external_ids?.imdb_id)
tags = genres?.mapNotNull { it.name }
duration = runtime
rating = this@toLoadResponse.rating
@ -251,7 +253,8 @@ open class TmdbProvider : MainAPI() {
val found = idRegex.find(url)
val isTvSeries = found?.groupValues?.getOrNull(1).equals("tv", ignoreCase = true)
val id = found?.groupValues?.getOrNull(2)?.toIntOrNull() ?: throw ErrorLoadingException("No id found")
val id = found?.groupValues?.getOrNull(2)?.toIntOrNull()
?: throw ErrorLoadingException("No id found")
return if (useMetaLoadResponse) {
return if (isTvSeries) {

View file

@ -167,7 +167,6 @@ class AllMoviesForYouProvider : MainAPI() {
year?.toIntOrNull(),
descipt,
null,
null,
rating
)
} else {

View file

@ -107,7 +107,6 @@ class AsiaFlixProvider : MainAPI() {
synopsis,
getStatus(tvStatus ?: ""),
null,
null,
genre?.split(",")?.map { it.trim() }
)
}

View file

@ -286,7 +286,6 @@ open class BflixProvider() : MainAPI() {
year?.toIntOrNull(),
description,
null,
null,
rating,
tags,
recommendations = recommendations,
@ -303,7 +302,6 @@ open class BflixProvider() : MainAPI() {
poster,
year?.toIntOrNull(),
description,
null,
rating,
tags,
recommendations = recommendations,

View file

@ -182,7 +182,6 @@ class IHaveNoTvProvider : MainAPI() {
)?.destructured?.component1()?.toIntOrNull(),
description,
null,
null,
soup.selectFirst(".videoDetails").select("a[href*=\"/category/\"]")
.map { it.text().trim() }
))
@ -204,7 +203,6 @@ class IHaveNoTvProvider : MainAPI() {
description,
null,
null,
null,
categories.toList()
) else (episodes?.first() as MovieLoadResponse)
}

View file

@ -235,7 +235,6 @@ class LookMovieProvider : MainAPI() {
poster,
year,
descript,
null,
rating
)
} else {
@ -292,7 +291,6 @@ class LookMovieProvider : MainAPI() {
year,
descript,
null,
null,
rating
)
}

View file

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.movieproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbUrl
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -144,17 +145,17 @@ class MeloMovieProvider : MainAPI() {
if (type == 1) { // MOVIE
val serialize = document.selectFirst("table.accordion__list")
?: throw ErrorLoadingException("No links found")
return MovieLoadResponse(
return newMovieLoadResponse(
title,
url,
this.name,
TvType.Movie,
serializeData(serialize),
poster,
year,
plot,
imdbUrlToIdNullable(imdbUrl)
)
serializeData(serialize)
) {
this.posterUrl = poster
this.year = year
this.plot = plot
addImdbUrl(imdbUrl)
}
} else if (type == 2) {
val episodes = ArrayList<TvSeriesEpisode>()
val seasons = document.select("div.accordion__card")
@ -175,18 +176,17 @@ class MeloMovieProvider : MainAPI() {
}
}
episodes.reverse()
return TvSeriesLoadResponse(
return newTvSeriesLoadResponse(
title,
url,
this.name,
TvType.TvSeries,
episodes,
poster,
year,
plot,
null,
imdbUrlToIdNullable(imdbUrl)
)
episodes
) {
this.posterUrl = poster
this.year = year
this.plot = plot
addImdbUrl(imdbUrl)
}
}
return null
}

View file

@ -163,7 +163,6 @@ class PelisflixProvider : MainAPI() {
year?.toIntOrNull(),
descipt2,
null,
null,
rating
)
} else {

View file

@ -1,9 +1,9 @@
package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
import org.jsoup.nodes.Element
import java.util.*
class PelisplusHDProvider:MainAPI() {
override var mainUrl = "https://pelisplushd.net"
@ -137,7 +137,6 @@ class PelisplusHDProvider:MainAPI() {
description,
null,
null,
null,
tags,
)
}
@ -152,7 +151,6 @@ class PelisplusHDProvider:MainAPI() {
year,
description,
null,
null,
tags,
)
}

View file

@ -160,7 +160,6 @@ class SeriesflixProvider : MainAPI() {
year?.toIntOrNull(),
descipt,
null,
null,
rating
)
} else {

View file

@ -76,7 +76,6 @@ class VfFilmProvider : MainAPI() {
val title = document?.selectFirst("div.SubTitle")?.text()
?: throw ErrorLoadingException("Service might be unavailable")
val year = document.select("span.Date").text()?.toIntOrNull()
val rating = document.select("span.AAIco-star").text()
@ -88,7 +87,6 @@ class VfFilmProvider : MainAPI() {
val descript = document.selectFirst("div.Description > p").text()
val players = document.select("ul.TPlayerNv > li")
var number_player = 0
var found = false
@ -108,17 +106,17 @@ class VfFilmProvider : MainAPI() {
val data = getDirect("$mainUrl/?trembed=$i&trid=$trid&trtype=1")
return MovieLoadResponse(
return newMovieLoadResponse(
title,
url,
this.name,
TvType.Movie,
data,
poster,
year,
descript,
rating,
duration
)
data
) {
this.posterUrl = poster
this.year = year
this.plot = descript
//this.rating = rating
this.duration = duration
}
}
}

View file

@ -160,7 +160,6 @@ class VfSerieProvider : MainAPI() {
year,
descript,
null,
null,
rating
)
}

View file

@ -78,7 +78,6 @@ class FrenchStreamProvider : MainAPI() {
poster,
date,
description,
null,
ratingAverage,
tagsList,
null,

View file

@ -606,13 +606,15 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
result_recommendations_btt?.isGone = isInvalid
result_recommendations_btt?.setOnClickListener {
if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
result_recommendations_btt?.nextFocusDownId = R.id.result_recommendations
result_overlapping_panels?.openEndPanel()
} else {
result_recommendations_btt?.nextFocusDownId = R.id.result_description
result_overlapping_panels?.closePanels()
}
}
result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
result_recommendations.post {
result_recommendations?.post {
rec?.let { list ->
(result_recommendations?.adapter as SearchAdapter?)?.updateList(list)
}
@ -1337,6 +1339,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
result_sync_loading_shimmer?.stopShimmer()
result_sync_loading_shimmer?.isVisible = false
result_sync_holder?.isVisible = false
closed = true
}
is Resource.Loading -> {
result_sync_loading_shimmer?.startShimmer()
@ -1420,6 +1423,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
result_series_parent?.isVisible = isSeriesVisible
if (isSeriesVisible && activity?.currentFocus?.id == R.id.result_back && context?.isTrueTvSettings() == true) {
result_resume_series_button?.requestFocus()
}
if (isSeriesVisible) {
val down = when {
result_season_button?.isVisible == true -> result_season_button
@ -1634,16 +1641,13 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setRecommendations(d.recommendations)
setActors(d.actors)
if (SettingsFragment.accountEnabled)
if (d is AnimeLoadResponse) {
// don't inline these variables as it will cause them to not be called
val addedMal = setMalSync(d.malId)
val addedAniList = setAniListSync(d.anilistId)
if (
addedMal
||
addedAniList
) {
if (SettingsFragment.accountEnabled) {
var isValid = false
for ((prefix, id) in d.syncData) {
isValid = isValid || syncModel.addSync(prefix, id)
}
if (isValid) {
syncModel.updateMetaAndUser()
syncModel.updateSynced()
} else {
@ -1658,6 +1662,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
result_poster?.setImage(posterImageLink)
result_poster_blur?.setImageBlur(posterImageLink, 10, 3)
//Full screen view of Poster image
if (context?.isTrueTvSettings() == false) // Poster not clickable on tv
result_poster_holder?.setOnClickListener {
try {
context?.let { ctx ->
@ -1680,6 +1685,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
logError(e)
}
}
} else {
result_poster?.setImageResource(R.drawable.default_cover)
result_poster_blur?.setImageResource(R.drawable.default_cover)
@ -1698,16 +1704,16 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
if (syno.length > MAX_SYNO_LENGH) {
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
}
result_descript.setOnClickListener {
result_description.setOnClickListener {
val builder: AlertDialog.Builder =
AlertDialog.Builder(requireContext())
builder.setMessage(d.plot)
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
.show()
}
result_descript.text = syno
result_description.text = syno
} else {
result_descript.text =
result_description.text =
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
R.string.normal_no_plot
)
@ -1727,12 +1733,13 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
//result_tag_holder?.visibility = GONE
} else {
//result_tag_holder?.visibility = VISIBLE
val isOnTv = context?.isTrueTvSettings() == true
for ((index, tag) in tags.withIndex()) {
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
btt.text = tag
btt.isFocusable = !isOnTv
btt.isClickable = !isOnTv
result_tag?.addView(viewBtt, index)
}
}
@ -1980,9 +1987,15 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
}
// bloats the navigation on tv
if (context?.isTrueTvSettings() == false) {
result_meta_site?.setOnClickListener {
it.context?.openBrowser(tempUrl)
}
result_meta_site?.isFocusable = true
} else {
result_meta_site?.isFocusable = false
}
if (restart || viewModel.result.value == null) {
//viewModel.clear()

View file

@ -65,18 +65,19 @@ class SyncViewModel : ViewModel() {
_currentSynced.postValue(getMissing())
}
fun setMalId(id: String?) : Boolean {
if(syncIds[malApi.idPrefix] == id ?: return false) return false
syncIds[malApi.idPrefix] = id
Log.i(TAG, "setMalId = $id")
fun addSync(idPrefix: String, id : String) : Boolean {
if(syncIds[idPrefix] == id) return false
Log.i(TAG, "addSync $idPrefix = $id")
syncIds[idPrefix] = id
return true
}
fun setMalId(id: String?) : Boolean {
return addSync(malApi.idPrefix,id ?: return false)
}
fun setAniListId(id: String?) : Boolean {
if(syncIds[aniListApi.idPrefix] == id ?: return false) return false
syncIds[aniListApi.idPrefix] = id
Log.i(TAG, "setAniListId = $id")
return true
return addSync(aniListApi.idPrefix,id ?: return false)
}
var hasAddedFromUrl: HashSet<String> = hashSetOf()
@ -164,8 +165,9 @@ class SyncViewModel : ViewModel() {
_userDataResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
for ((prefix, id) in syncIds) {
repos.firstOrNull { it.idPrefix == prefix }?.let {
val result = it.getStatus(id)
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if(repo.hasAccount()) {
val result = repo.getStatus(id)
if (result is Resource.Success) {
_userDataResponse.postValue(result)
return@launch
@ -174,6 +176,7 @@ class SyncViewModel : ViewModel() {
}
}
}
}
_userDataResponse.postValue(lastError)
}
@ -183,8 +186,9 @@ class SyncViewModel : ViewModel() {
_metaResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncResult> = Resource.Failure(false, null, null, "No data")
for ((prefix, id) in syncIds) {
repos.firstOrNull { it.idPrefix == prefix }?.let {
val result = it.getResult(id)
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if(repo.hasAccount()) {
val result = repo.getResult(id)
if (result is Resource.Success) {
_metaResponse.postValue(result)
return@launch
@ -193,6 +197,7 @@ class SyncViewModel : ViewModel() {
}
}
}
}
_metaResponse.postValue(lastError)
setEpisodesDelta(0)
}

View file

@ -352,7 +352,7 @@
android:layout_height="match_parent">
<TextView
android:id="@+id/result_descript"
android:id="@+id/result_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="@drawable/outline_drawable"
@ -390,7 +390,7 @@
android:minWidth="100dp"
android:nextFocusLeft="@id/result_back"
android:nextFocusRight="@id/result_search"
android:nextFocusUp="@id/result_descript"
android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_play_movie"
android:paddingTop="0dp"
@ -751,7 +751,7 @@
android:layout_marginStart="0dp"
android:nextFocusLeft="@id/result_episode_select"
android:nextFocusRight="@id/result_episode_select"
android:nextFocusUp="@id/result_descript"
android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_episodes"
android:visibility="gone"
tools:text="Season 1"
@ -766,7 +766,7 @@
android:nextFocusLeft="@id/result_season_button"
android:nextFocusRight="@id/result_season_button"
android:nextFocusUp="@id/result_descript"
android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_episodes"
android:visibility="gone"
tools:text="50-100"
@ -781,7 +781,7 @@
android:nextFocusLeft="@id/result_season_button"
android:nextFocusRight="@id/result_season_button"
android:nextFocusUp="@id/result_descript"
android:nextFocusUp="@id/result_description"
android:nextFocusDown="@id/result_episodes"
android:visibility="gone"
tools:text="Dubbed"

View file

@ -70,7 +70,7 @@
<ImageView
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_descript"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_add_sync"
android:nextFocusRight="@id/result_open_in_browser"
@ -88,7 +88,7 @@
<ImageView
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_descript"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_share"
android:nextFocusRight="@id/result_search"
@ -106,7 +106,7 @@
<ImageView
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_descript"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_open_in_browser"
android:nextFocusRight="@id/result_recommendations_btt"
@ -122,9 +122,10 @@
android:contentDescription="@string/result_open_in_browser"
app:tint="?attr/textColor" />
<ImageView
tools:visibility="visible"
android:visibility="gone"
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_descript"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_search"
android:nextFocusRight="@id/result_bookmark_button"

View file

@ -401,6 +401,8 @@
<string name="sync_total_episodes_none">/??</string>
<string name="sync_total_episodes_some" formatted="true">/%d</string>
<string name="authenticated_user" formatted="true">Authenticated %s</string>
<string name="authenticated_user_fail" formatted="true">Failed to authenticate to %s</string>
<!-- ============ -->
<string name="none">None</string>
<string name="normal">Normal</string>