This commit is contained in:
LagradOst 2022-06-29 03:20:23 +02:00
parent 7bf77cb408
commit f7b23d6467
13 changed files with 370 additions and 27 deletions

View file

@ -134,6 +134,7 @@ object APIHolder {
//MultiAnimeProvider(), //MultiAnimeProvider(),
NginxProvider(), NginxProvider(),
OlgplyProvider(), OlgplyProvider(),
AniflixProvider(),
) )

View file

@ -0,0 +1,246 @@
package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
class AniflixProvider : MainAPI() {
override var mainUrl = "https://aniflix.pro"
override var name = "Aniflix"
override val hasMainPage = false
override val supportedTypes = setOf(
TvType.AnimeMovie,
TvType.OVA,
TvType.Anime,
)
companion object {
var token: String? = null
}
private suspend fun getToken(): String {
return token ?: run {
Regex("([^/]*)/_buildManifest\\.js").find(app.get(mainUrl).text)?.groupValues?.getOrNull(
1
)
?.also {
token = it
}
?: throw ErrorLoadingException("No token found")
}
}
private fun Anime.toSearchResponse(): SearchResponse? {
return newAnimeSearchResponse(
title?.english ?: title?.romaji ?: return null,
"$mainUrl/anime/${id ?: return null}"
) {
posterUrl = coverImage?.large ?: coverImage?.medium
}
}
override suspend fun search(query: String): List<SearchResponse>? {
val token = getToken()
val url = "$mainUrl/_next/data/$token/search.json?keyword=$query"
val response = app.get(url)
println("resp: $url ===> ${response.text}")
val searchResponse =
response.parsedSafe<Search>()
?: throw ErrorLoadingException("No Media")
return searchResponse.pageProps?.searchResults?.Page?.media?.mapNotNull { media ->
media.toSearchResponse()
}
}
override suspend fun load(url: String): LoadResponse {
val token = getToken()
val id = Regex("$mainUrl/anime/([0-9]*)").find(url)?.groupValues?.getOrNull(1)
?: throw ErrorLoadingException("Error parsing link for id")
val res = app.get("https://aniflix.pro/_next/data/$token/anime/$id.json?id=$id")
.parsedSafe<AnimeResponsePage>()?.pageProps
?: throw ErrorLoadingException("Invalid Json reponse")
val isMovie = res.anime.format == "MOVIE"
return newAnimeLoadResponse(
res.anime.title?.english ?: res.anime.title?.romaji
?: throw ErrorLoadingException("Invalid title reponse"),
url, if (isMovie) TvType.AnimeMovie else TvType.Anime
) {
recommendations = res.recommended.mapNotNull { it.toSearchResponse() }
tags = res.anime.genres
posterUrl = res.anime.coverImage?.large ?: res.anime.coverImage?.medium
plot = res.anime.description
showStatus = when (res.anime.status) {
"FINISHED" -> ShowStatus.Completed
"RELEASING" -> ShowStatus.Ongoing
else -> null
}
addAniListId(id.toIntOrNull())
// subbed because they are both subbed and dubbed
if (isMovie)
addEpisodes(
DubStatus.Subbed,
listOf(newEpisode("$mainUrl/api/anime/?id=$id&episode=1"))
)
else
addEpisodes(DubStatus.Subbed, res.episodes.episodes?.nodes?.mapNotNull { node ->
val ep = node?.number ?: return@mapNotNull null
//"$mainUrl/_next/data/$token/watch/$id.json?episode=${node.number ?: return@mapNotNull null}&id=$id"
newEpisode("$mainUrl/api/anime?id=$id&episode=${ep}") {
episode = ep
posterUrl = node.thumbnail?.original?.url
name = node.titles?.canonical
}
})
}
}
override suspend fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
return app.get(data).parsed<AniLoadResponse>().let { res ->
val dubReferer = res.dub?.Referer ?: ""
res.dub?.sources?.forEach { source ->
callback(
ExtractorLink(
name,
"${source.label ?: name} (DUB)",
source.file ?: return@forEach,
dubReferer,
getQualityFromName(source.label),
source.type == "hls"
)
)
}
val subReferer = res.dub?.Referer ?: ""
res.sub?.sources?.forEach { source ->
callback(
ExtractorLink(
name,
"${source.label ?: name} (SUB)",
source.file ?: return@forEach,
subReferer,
getQualityFromName(source.label),
source.type == "hls"
)
)
}
!res.dub?.sources.isNullOrEmpty() && !res.sub?.sources.isNullOrEmpty()
}
}
data class AniLoadResponse(
@JsonProperty("sub") val sub: DubSubSource?,
@JsonProperty("dub") val dub: DubSubSource?,
@JsonProperty("episodes") val episodes: Int?
)
data class Sources(
@JsonProperty("file") val file: String?,
@JsonProperty("label") val label: String?,
@JsonProperty("type") val type: String?
)
data class DubSubSource(
@JsonProperty("Referer") var Referer: String?,
@JsonProperty("sources") var sources: ArrayList<Sources> = arrayListOf()
)
data class PageProps(
@JsonProperty("searchResults") val searchResults: SearchResults?
)
data class SearchResults(
@JsonProperty("Page") val Page: Page?
)
data class Page(
@JsonProperty("media") val media: ArrayList<Anime> = arrayListOf()
)
data class CoverImage(
@JsonProperty("color") val color: String?,
@JsonProperty("medium") val medium: String?,
@JsonProperty("large") val large: String?,
)
data class Title(
@JsonProperty("english") val english: String?,
@JsonProperty("romaji") val romaji: String?,
)
data class Search(
@JsonProperty("pageProps") val pageProps: PageProps?,
@JsonProperty("__N_SSP") val _NSSP: Boolean?
)
data class Anime(
@JsonProperty("status") val status: String?,
@JsonProperty("id") val id: Int?,
@JsonProperty("title") val title: Title?,
@JsonProperty("coverImage") val coverImage: CoverImage?,
@JsonProperty("format") val format: String?,
@JsonProperty("duration") val duration: Int?,
@JsonProperty("meanScore") val meanScore: Int?,
@JsonProperty("nextAiringEpisode") val nextAiringEpisode: String?,
@JsonProperty("bannerImage") val bannerImage: String?,
@JsonProperty("description") val description: String?,
@JsonProperty("genres") val genres: ArrayList<String>? = null,
@JsonProperty("season") val season: String?,
@JsonProperty("startDate") val startDate: StartDate?,
)
data class StartDate(
@JsonProperty("year") val year: Int?
)
data class AnimeResponsePage(
@JsonProperty("pageProps") val pageProps: AnimeResponse?,
)
data class AnimeResponse(
@JsonProperty("anime") val anime: Anime,
@JsonProperty("recommended") val recommended: ArrayList<Anime>,
@JsonProperty("episodes") val episodes: EpisodesParent,
)
data class EpisodesParent(
@JsonProperty("id") val id: String?,
@JsonProperty("season") val season: String?,
@JsonProperty("startDate") val startDate: String?,
@JsonProperty("episodeCount") val episodeCount: Int?,
@JsonProperty("episodes") val episodes: Episodes?,
)
data class Episodes(
@JsonProperty("nodes") val nodes: ArrayList<Nodes?> = arrayListOf()
)
data class Nodes(
@JsonProperty("number") val number: Int? = null,
@JsonProperty("titles") val titles: Titles?,
@JsonProperty("thumbnail") val thumbnail: Thumbnail?,
)
data class Titles(
@JsonProperty("canonical") val canonical: String?,
)
data class Original(
@JsonProperty("url") val url: String?,
)
data class Thumbnail(
@JsonProperty("original") val original: Original?,
)
}

View file

@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.fragment_downloads.* import kotlinx.android.synthetic.main.fragment_downloads.*
const val DOWNLOAD_NAVIGATE_TO = "downloadpage" const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
class DownloadFragment : Fragment() { class DownloadFragment : Fragment() {
@ -165,8 +166,22 @@ class DownloadFragment : Fragment() {
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it } downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
download_list.adapter = adapter download_list?.adapter = adapter
download_list.layoutManager = GridLayoutManager(context, 1) download_list?.layoutManager = GridLayoutManager(context, 1)
/*download_stream_button?.isGone = context?.isTvSettings() == true
download_stream_button?.setOnClickListener {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
download_stream_button?.shrink() // hide
} else if (dy < -5) {
download_stream_button?.extend() // show
}
}
}*/
downloadsViewModel.updateList(requireContext()) downloadsViewModel.updateList(requireContext())
context?.fixPaddingStatusbar(download_root) context?.fixPaddingStatusbar(download_root)

View file

@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
@ -106,7 +107,8 @@ class EpisodeAdapter(
else R.layout.result_episode*/ else R.layout.result_episode*/
return EpisodeCardViewHolder( return EpisodeCardViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.result_episode_both, parent, false), LayoutInflater.from(parent.context)
.inflate(R.layout.result_episode_both, parent, false),
hasDownloadSupport, hasDownloadSupport,
clickCallback, clickCallback,
downloadClickCallback downloadClickCallback
@ -144,7 +146,7 @@ class EpisodeAdapter(
fun bind(card: ResultEpisode) { fun bind(card: ResultEpisode) {
localCard = card localCard = card
val (parentView,otherView) = if(card.poster == null) { val (parentView, otherView) = if (card.poster == null) {
itemView.episode_holder to itemView.episode_holder_large itemView.episode_holder to itemView.episode_holder_large
} else { } else {
itemView.episode_holder_large to itemView.episode_holder itemView.episode_holder_large to itemView.episode_holder
@ -193,7 +195,7 @@ class EpisodeAdapter(
episodeRating?.isGone = episodeRating?.text.isNullOrBlank() episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
episodeDescript?.apply { episodeDescript?.apply {
text = card.description ?: "" text = card.description.html()
isGone = text.isNullOrBlank() isGone = text.isNullOrBlank()
setOnClickListener { setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))

View file

@ -24,6 +24,7 @@ import android.widget.*
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.text.HtmlCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
@ -64,6 +65,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueT
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1 import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
@ -103,9 +105,6 @@ import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
const val MAX_SYNO_LENGH = 1000
const val START_ACTION_NORMAL = 0 const val START_ACTION_NORMAL = 0
const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_RESUME_LATEST = 1
const val START_ACTION_LOAD_EP = 2 const val START_ACTION_LOAD_EP = 2
@ -1908,21 +1907,18 @@ class ResultFragment : ResultTrailerPlayer() {
)*/ )*/
//result_plot_header?.text = //result_plot_header?.text =
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot) // if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
if (!d.plot.isNullOrEmpty()) { val syno = d.plot
var syno = d.plot!! if (!syno.isNullOrEmpty()) {
if (syno.length > MAX_SYNO_LENGH) { result_description?.setOnClickListener {
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
}
result_description.setOnClickListener {
val builder: AlertDialog.Builder = val builder: AlertDialog.Builder =
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
builder.setMessage(d.plot) builder.setMessage(syno.html())
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot) .setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
.show() .show()
} }
result_description.text = syno result_description?.text = syno.html()
} else { } else {
result_description.text = result_description?.text =
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString( if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
R.string.normal_no_plot R.string.normal_no_plot
) )

View file

@ -18,11 +18,14 @@ import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.provider.MediaStore import android.provider.MediaStore
import android.text.Spanned
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.core.text.toSpanned
import androidx.tvprovider.media.tv.PreviewChannelHelper import androidx.tvprovider.media.tv.PreviewChannelHelper
import androidx.tvprovider.media.tv.TvContractCompat import androidx.tvprovider.media.tv.TvContractCompat
import androidx.tvprovider.media.tv.WatchNextProgram import androidx.tvprovider.media.tv.WatchNextProgram
@ -61,6 +64,22 @@ object AppUtils {
// ) // )
// } // }
//} //}
fun String?.html(): Spanned {
return getHtmlText(this ?: return "".toSpanned())
}
private fun getHtmlText(text: String): Spanned {
return try {
// I have no idea if this can throw any error, but I dont want to try
HtmlCompat.fromHtml(
text, HtmlCompat.FROM_HTML_MODE_LEGACY
)
} catch (e: Exception) {
logError(e)
text.toSpanned()
}
}
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
private fun buildWatchNextProgramUri( private fun buildWatchNextProgramUri(
context: Context, context: Context,
@ -222,7 +241,7 @@ object AppUtils {
/** Any object as json string */ /** Any object as json string */
fun Any.toJson(): String { fun Any.toJson(): String {
if(this is String) return this if (this is String) return this
return mapper.writeValueAsString(this) return mapper.writeValueAsString(this)
} }

View file

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.utils
import android.util.Log import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.animeproviders.AniflixProvider
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mapper import com.lagradost.cloudstream3.mapper
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
@ -81,7 +82,11 @@ object SyncUtil {
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json" "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>() val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
val pages = response.pages ?: return emptyList() val pages = response.pages ?: return emptyList()
return pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url } val current = pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }.toMutableList()
if(type == "anilist") { // TODO MAKE BETTER
current.add("${AniflixProvider().mainUrl}/anime/$id")
}
return current
} }
data class SyncPage( data class SyncPage(

View file

@ -35,9 +35,10 @@ class FlowLayout : ViewGroup {
measureChild(child, widthMeasureSpec, heightMeasureSpec) measureChild(child, widthMeasureSpec, heightMeasureSpec)
val childWidth = child.measuredWidth val childWidth = child.measuredWidth
val childHeight = child.measuredHeight val childHeight = child.measuredHeight
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
//check if child can be placed in the current row, else go to next line //check if child can be placed in the current row, else go to next line
if (currentChildHookPointx + childWidth - child.marginEnd - child.paddingEnd - itemSpacing > realWidth) { if (currentChildHookPointx + childWidth - child.marginEnd - child.paddingEnd > realWidth) {
//new line //new line
currentWidth = max(currentWidth, currentChildHookPointx) currentWidth = max(currentWidth, currentChildHookPointx)
@ -47,8 +48,8 @@ class FlowLayout : ViewGroup {
} }
val nextChildHookPointx = val nextChildHookPointx =
currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing
val nextChildHookPointy = currentChildHookPointy val nextChildHookPointy = currentChildHookPointy
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
val lp = child.layoutParams as LayoutParams val lp = child.layoutParams as LayoutParams
lp.x = currentChildHookPointx lp.x = currentChildHookPointx
lp.y = currentChildHookPointy lp.y = currentChildHookPointy

View file

@ -8,7 +8,9 @@
app:cardBackgroundColor="?attr/boxItemBackground" app:cardBackgroundColor="?attr/boxItemBackground"
android:id="@+id/episode_holder" android:id="@+id/episode_holder"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:layout_marginBottom="10dp"> android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp">
<LinearLayout <LinearLayout
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"

View file

@ -11,6 +11,7 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:background="?attr/primaryGrayBackground" android:background="?attr/primaryGrayBackground"
tools:layout_height="100dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<!-- <!--
@ -124,7 +125,6 @@
android:background="?attr/primaryBlackBackground" android:background="?attr/primaryBlackBackground"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:padding="10dp"
android:id="@+id/download_list" android:id="@+id/download_list"
android:layout_width="match_parent" android:layout_width="match_parent"
tools:listitem="@layout/download_header_episode" tools:listitem="@layout/download_header_episode"
@ -154,6 +154,7 @@
</ProgressBar>--> </ProgressBar>-->
<com.facebook.shimmer.ShimmerFrameLayout <com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="gone"
android:id="@+id/download_loading" android:id="@+id/download_loading"
app:shimmer_base_alpha="0.2" app:shimmer_base_alpha="0.2"
app:shimmer_highlight_alpha="0.3" app:shimmer_highlight_alpha="0.3"
@ -185,4 +186,13 @@
<include layout="@layout/loading_downloads" /> <include layout="@layout/loading_downloads" />
</LinearLayout> </LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout> </com.facebook.shimmer.ShimmerFrameLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:text="@string/stream"
android:visibility="gone"
android:id="@+id/download_stream_button"
app:icon="@drawable/netflix_play"
style="@style/ExtendedFloatingActionButton"
android:textColor="?attr/textColor"
tools:ignore="ContentDescription" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -363,6 +363,8 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <TextView
android:maxLength="1000"
android:ellipsize="end"
android:id="@+id/result_description" android:id="@+id/result_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -832,6 +834,7 @@
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:gravity="center" android:gravity="center"
@ -842,6 +845,7 @@
android:textSize="17sp" android:textSize="17sp"
android:textStyle="normal" android:textStyle="normal"
android:text="Episode 1022 will be released in" /> android:text="Episode 1022 will be released in" />
<TextView <TextView
android:paddingStart="5dp" android:paddingStart="5dp"
android:gravity="center" android:gravity="center"

View file

@ -0,0 +1,41 @@
<?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="wrap_content">
<EditText
android:layout_weight="20"
android:id="@+id/subtitle_offset_input"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subtitle_offset_hint"
tools:ignore="LabelFor" />
<LinearLayout
android:orientation="horizontal"
android:layout_gravity="bottom"
android:gravity="bottom|end"
android:layout_width="match_parent"
android:layout_height="60dp">
<com.google.android.material.button.MaterialButton
style="@style/WhiteButton"
android:layout_gravity="center_vertical|end"
android:visibility="visible"
android:text="@string/sort_apply"
android:id="@+id/apply_btt"
android:layout_width="wrap_content">
<requestFocus />
</com.google.android.material.button.MaterialButton>
<com.google.android.material.button.MaterialButton
style="@style/BlackButton"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_cancel"
android:id="@+id/cancel_btt"
android:layout_width="wrap_content" />
</LinearLayout>
</LinearLayout>

View file

@ -136,6 +136,7 @@
<string name="download_canceled">Download Canceled</string> <string name="download_canceled">Download Canceled</string>
<string name="download_done">Download Done</string> <string name="download_done">Download Done</string>
<string name="download_format" translatable="false">%s - %s</string> <string name="download_format" translatable="false">%s - %s</string>
<string name="stream">Stream</string>
<string name="error_loading_links_toast">Error Loading Links</string> <string name="error_loading_links_toast">Error Loading Links</string>
<string name="download_storage_text">Internal Storage</string> <string name="download_storage_text">Internal Storage</string>