mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
added https://aniflix.pro
This commit is contained in:
parent
7bf77cb408
commit
f7b23d6467
13 changed files with 370 additions and 27 deletions
|
@ -134,6 +134,7 @@ object APIHolder {
|
|||
//MultiAnimeProvider(),
|
||||
NginxProvider(),
|
||||
OlgplyProvider(),
|
||||
AniflixProvider(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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?,
|
||||
)
|
||||
}
|
|
@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
import kotlinx.android.synthetic.main.fragment_downloads.*
|
||||
|
||||
|
||||
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
|
||||
|
||||
class DownloadFragment : Fragment() {
|
||||
|
@ -165,8 +166,22 @@ class DownloadFragment : Fragment() {
|
|||
|
||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||
|
||||
download_list.adapter = adapter
|
||||
download_list.layoutManager = GridLayoutManager(context, 1)
|
||||
download_list?.adapter = adapter
|
||||
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())
|
||||
|
||||
context?.fixPaddingStatusbar(download_root)
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
|
|||
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||
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.VideoDownloadHelper
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
|
@ -106,7 +107,8 @@ class EpisodeAdapter(
|
|||
else R.layout.result_episode*/
|
||||
|
||||
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,
|
||||
clickCallback,
|
||||
downloadClickCallback
|
||||
|
@ -193,7 +195,7 @@ class EpisodeAdapter(
|
|||
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
|
||||
|
||||
episodeDescript?.apply {
|
||||
text = card.description ?: ""
|
||||
text = card.description.html()
|
||||
isGone = text.isNullOrBlank()
|
||||
setOnClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.widget.*
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
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.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
|
||||
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.isCastApiAvailable
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
||||
|
@ -103,9 +105,6 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
const val MAX_SYNO_LENGH = 1000
|
||||
|
||||
const val START_ACTION_NORMAL = 0
|
||||
const val START_ACTION_RESUME_LATEST = 1
|
||||
const val START_ACTION_LOAD_EP = 2
|
||||
|
@ -1908,21 +1907,18 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
)*/
|
||||
//result_plot_header?.text =
|
||||
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
|
||||
if (!d.plot.isNullOrEmpty()) {
|
||||
var syno = d.plot!!
|
||||
if (syno.length > MAX_SYNO_LENGH) {
|
||||
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
|
||||
}
|
||||
result_description.setOnClickListener {
|
||||
val syno = d.plot
|
||||
if (!syno.isNullOrEmpty()) {
|
||||
result_description?.setOnClickListener {
|
||||
val builder: AlertDialog.Builder =
|
||||
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)
|
||||
.show()
|
||||
}
|
||||
result_description.text = syno
|
||||
result_description?.text = syno.html()
|
||||
} else {
|
||||
result_description.text =
|
||||
result_description?.text =
|
||||
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
|
||||
R.string.normal_no_plot
|
||||
)
|
||||
|
|
|
@ -18,11 +18,14 @@ import android.os.Build
|
|||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import android.text.Spanned
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
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.TvContractCompat
|
||||
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")
|
||||
private fun buildWatchNextProgramUri(
|
||||
context: Context,
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.utils
|
|||
import android.util.Log
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.animeproviders.AniflixProvider
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mapper
|
||||
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"
|
||||
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
|
||||
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(
|
||||
|
|
|
@ -35,9 +35,10 @@ class FlowLayout : ViewGroup {
|
|||
measureChild(child, widthMeasureSpec, heightMeasureSpec)
|
||||
val childWidth = child.measuredWidth
|
||||
val childHeight = child.measuredHeight
|
||||
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
|
||||
|
||||
//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
|
||||
currentWidth = max(currentWidth, currentChildHookPointx)
|
||||
|
||||
|
@ -47,8 +48,8 @@ class FlowLayout : ViewGroup {
|
|||
}
|
||||
val nextChildHookPointx =
|
||||
currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing
|
||||
|
||||
val nextChildHookPointy = currentChildHookPointy
|
||||
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
|
||||
val lp = child.layoutParams as LayoutParams
|
||||
lp.x = currentChildHookPointx
|
||||
lp.y = currentChildHookPointy
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
app:cardBackgroundColor="?attr/boxItemBackground"
|
||||
android:id="@+id/episode_holder"
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:layout_marginBottom="10dp">
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
tools:layout_height="100dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!--
|
||||
|
@ -124,7 +125,6 @@
|
|||
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:padding="10dp"
|
||||
android:id="@+id/download_list"
|
||||
android:layout_width="match_parent"
|
||||
tools:listitem="@layout/download_header_episode"
|
||||
|
@ -154,6 +154,7 @@
|
|||
</ProgressBar>-->
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
tools:visibility="gone"
|
||||
android:id="@+id/download_loading"
|
||||
app:shimmer_base_alpha="0.2"
|
||||
app:shimmer_highlight_alpha="0.3"
|
||||
|
@ -185,4 +186,13 @@
|
|||
<include layout="@layout/loading_downloads" />
|
||||
</LinearLayout>
|
||||
</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>
|
|
@ -363,6 +363,8 @@
|
|||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:maxLength="1000"
|
||||
android:ellipsize="end"
|
||||
android:id="@+id/result_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -832,6 +834,7 @@
|
|||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:gravity="center"
|
||||
|
||||
|
@ -842,6 +845,7 @@
|
|||
android:textSize="17sp"
|
||||
android:textStyle="normal"
|
||||
android:text="Episode 1022 will be released in" />
|
||||
|
||||
<TextView
|
||||
android:paddingStart="5dp"
|
||||
android:gravity="center"
|
||||
|
|
41
app/src/main/res/layout/stream_input.xml
Normal file
41
app/src/main/res/layout/stream_input.xml
Normal 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>
|
|
@ -136,6 +136,7 @@
|
|||
<string name="download_canceled">Download Canceled</string>
|
||||
<string name="download_done">Download Done</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="download_storage_text">Internal Storage</string>
|
||||
|
|
Loading…
Reference in a new issue