Merge branch 'recloudstream:master' into master

This commit is contained in:
SANCTI-afk 2022-09-13 18:32:31 +02:00 committed by GitHub
commit d5faa4e469
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 391 additions and 158 deletions

View file

@ -170,7 +170,7 @@ dependencies {
// Networking // Networking
// implementation "com.squareup.okhttp3:okhttp:4.9.2" // implementation "com.squareup.okhttp3:okhttp:4.9.2"
// implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1" // implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1"
implementation 'com.github.Blatzar:NiceHttp:0.3.2' implementation 'com.github.Blatzar:NiceHttp:0.3.3'
// Util to skip the URI file fuckery 🙏 // Util to skip the URI file fuckery 🙏
implementation "com.github.tachiyomiorg:unifile:17bec43" implementation "com.github.tachiyomiorg:unifile:17bec43"

View file

@ -1118,6 +1118,11 @@ data class NextAiring(
val unixTime: Long, val unixTime: Long,
) )
/**
* @param season To be mapped with episode season, not shown in UI if displaySeason is defined
* @param name To be shown next to the season like "Season $displaySeason $name" but if displaySeason is null then "$name"
* @param displaySeason What to be displayed next to the season name, if null then the name is the only thing shown.
* */
data class SeasonData( data class SeasonData(
val season: Int, val season: Int,
val name: String? = null, val name: String? = null,
@ -1198,9 +1203,12 @@ data class AnimeLoadResponse(
override var backgroundPosterUrl: String? = null, override var backgroundPosterUrl: String? = null,
) : LoadResponse, EpisodeResponse ) : LoadResponse, EpisodeResponse
/**
* If episodes already exist appends the list.
* */
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<Episode>?) { fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<Episode>?) {
if (episodes.isNullOrEmpty()) return if (episodes.isNullOrEmpty()) return
this.episodes[status] = episodes this.episodes[status] = (this.episodes[status] ?: emptyList()) + episodes
} }
suspend fun MainAPI.newAnimeLoadResponse( suspend fun MainAPI.newAnimeLoadResponse(

View file

@ -7,6 +7,10 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
class DoodWfExtractor : DoodLaExtractor() {
override var mainUrl = "https://dood.wf"
}
class DoodCxExtractor : DoodLaExtractor() { class DoodCxExtractor : DoodLaExtractor() {
override var mainUrl = "https://dood.cx" override var mainUrl = "https://dood.cx"
} }

View file

@ -0,0 +1,178 @@
package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import org.jsoup.nodes.Element
import java.security.DigestException
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class Gdriveplayerapi: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayerapi.com"
}
class Gdriveplayerapp: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.app"
}
class Gdriveplayerfun: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.fun"
}
class Gdriveplayerio: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.io"
}
class Gdriveplayerme: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.me"
}
class Gdriveplayerbiz: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.biz"
}
class Gdriveplayerorg: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.org"
}
class Gdriveplayerus: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.us"
}
class Gdriveplayerco: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.co"
}
open class Gdriveplayer : ExtractorApi() {
override val name = "Gdrive"
override val mainUrl = "https://gdriveplayer.to"
override val requiresReferer = false
private fun unpackJs(script: Element): String? {
return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") }
?.data()?.let { getAndUnpack(it) }
}
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
// https://stackoverflow.com/a/41434590/8166854
private fun GenerateKeyAndIv(
password: ByteArray,
salt: ByteArray,
hashAlgorithm: String = "MD5",
keyLength: Int = 32,
ivLength: Int = 16,
iterations: Int = 1
): List<ByteArray>? {
val md = MessageDigest.getInstance(hashAlgorithm)
val digestLength = md.digestLength
val targetKeySize = keyLength + ivLength
val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
val generatedData = ByteArray(requiredLength)
var generatedLength = 0
try {
md.reset()
while (generatedLength < targetKeySize) {
if (generatedLength > 0)
md.update(
generatedData,
generatedLength - digestLength,
digestLength
)
md.update(password)
md.update(salt, 0, 8)
md.digest(generatedData, generatedLength, digestLength)
for (i in 1 until iterations) {
md.update(generatedData, generatedLength, digestLength)
md.digest(generatedData, generatedLength, digestLength)
}
generatedLength += digestLength
}
return listOf(
generatedData.copyOfRange(0, keyLength),
generatedData.copyOfRange(keyLength, targetKeySize)
)
} catch (e: DigestException) {
return null
}
}
private fun cryptoAESHandler(
data: AesData,
pass: ByteArray,
encrypt: Boolean = true
): String? {
val (key, iv) = GenerateKeyAndIv(pass, data.s.decodeHex()) ?: return null
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
return if (!encrypt) {
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
String(cipher.doFinal(base64DecodeArray(data.ct)))
} else {
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
base64Encode(cipher.doFinal(data.ct.toByteArray()))
}
}
private fun Regex.first(str: String): String? {
return find(str)?.groupValues?.getOrNull(1)
}
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val document = app.get(url).document
val eval = unpackJs(document)?.replace("\\", "") ?: return
val data = AppUtils.tryParseJson<AesData>(Regex("data='(\\S+?)'").first(eval)) ?: return
val password = Regex("null,['|\"](\\w+)['|\"]").first(eval)
?.split(Regex("\\D+"))
?.joinToString("") {
Char(it.toInt()).toString()
}.let { Regex("var pass = \"(\\S+?)\"").first(it ?: return)?.toByteArray() }
?: throw ErrorLoadingException("can't find password")
val decryptedData =
cryptoAESHandler(data, password, false)?.let { getAndUnpack(it) }?.replace("\\", "")
?.substringAfter("sources:[")?.substringBefore("],")
Regex("\"file\":\"(\\S+?)\".*?res=(\\d+)").findAll(decryptedData ?: return).map {
it.groupValues[1] to it.groupValues[2]
}.toList().distinctBy { it.second }.map { (link, quality) ->
callback.invoke(
ExtractorLink(
source = this.name,
name = this.name,
url = "${httpsify(link)}&res=$quality",
referer = mainUrl,
quality = quality.toIntOrNull() ?: Qualities.Unknown.value,
headers = mapOf("Range" to "bytes=0-")
)
)
}
}
data class AesData(
@JsonProperty("ct") val ct: String,
@JsonProperty("iv") val iv: String,
@JsonProperty("s") val s: String
)
}

View file

@ -7,6 +7,11 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
class Sbflix : StreamSB() {
override var mainUrl = "https://sbflix.xyz"
override var name = "Sbflix"
}
class Vidgomunime : StreamSB() { class Vidgomunime : StreamSB() {
override var mainUrl = "https://vidgomunime.xyz" override var mainUrl = "https://vidgomunime.xyz"
} }
@ -111,7 +116,7 @@ open class StreamSB : ExtractorApi() {
}.first() }.first()
val bytes = id.toByteArray() val bytes = id.toByteArray()
val bytesToHex = bytesToHex(bytes) val bytesToHex = bytesToHex(bytes)
val master = "$mainUrl/sources43/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" val master = "$mainUrl/sources44/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362"
val headers = mapOf( val headers = mapOf(
"watchsb" to "streamsb", "watchsb" to "streamsb",
) )

View file

@ -7,6 +7,7 @@ import com.bumptech.glide.load.HttpException
import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.ErrorLoadingException
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.InterruptedIOException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.net.ssl.SSLHandshakeException import javax.net.ssl.SSLHandshakeException
@ -157,7 +158,7 @@ suspend fun <T> safeApiCall(
} }
safeFail(throwable) safeFail(throwable)
} }
is SocketTimeoutException -> { is SocketTimeoutException, is InterruptedIOException -> {
Resource.Failure( Resource.Failure(
true, true,
null, null,
@ -192,7 +193,7 @@ suspend fun <T> safeApiCall(
true, true,
null, null,
null, null,
(throwable.message ?: "SSLHandshakeException") + "\nTry again later." (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS."
) )
} }
else -> safeFail(throwable) else -> safeFail(throwable)

View file

@ -5,15 +5,12 @@ import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.getCookies
import com.lagradost.nicehttp.ignoreAllSSLErrors import com.lagradost.nicehttp.ignoreAllSSLErrors
import okhttp3.Cache import okhttp3.Cache
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders import okhttp3.Headers.Companion.toHeaders
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit
fun Requests.initClient(context: Context): OkHttpClient { fun Requests.initClient(context: Context): OkHttpClient {

View file

@ -1483,15 +1483,20 @@ class ResultViewModel2 : ViewModel() {
0 -> txt(R.string.no_season) 0 -> txt(R.string.no_season)
else -> { else -> {
val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames
val seasonData = val seasonData = seasonNames.getSeason(indexer.season)
seasonNames.getSeason(indexer.season)
val suffix = seasonData?.name?.let { " $it" } ?: "" // If displaySeason is null then only show the name!
txt( if (seasonData?.name != null && seasonData.displaySeason == null) {
R.string.season_format, txt(seasonData.name)
txt(R.string.season), } else {
seasonData?.displaySeason ?: indexer.season, val suffix = seasonData?.name?.let { " $it" } ?: ""
suffix txt(
) R.string.season_format,
txt(R.string.season),
seasonData?.displaySeason ?: indexer.season,
suffix
)
}
} }
} }
) )
@ -1587,8 +1592,8 @@ class ResultViewModel2 : ViewModel() {
val idIndex = ep.key.id val idIndex = ep.key.id
for ((index, i) in ep.value.withIndex()) { for ((index, i) in ep.value.withIndex()) {
val episode = i.episode ?: (index + 1) val episode = i.episode ?: (index + 1)
val id = mainId + episode + idIndex * 1000000 val id = mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000) ?: 0)
if (!existingEpisodes.contains(episode)) { if (!existingEpisodes.contains(id)) {
existingEpisodes.add(id) existingEpisodes.add(id)
val seasonData = loadResponse.seasonNames.getSeason(i.season) val seasonData = loadResponse.seasonNames.getSeason(i.season)
val eps = val eps =
@ -1597,8 +1602,8 @@ class ResultViewModel2 : ViewModel() {
filterName(i.name), filterName(i.name),
i.posterUrl, i.posterUrl,
episode, episode,
null, seasonData?.season ?: i.season,
seasonData?.displaySeason ?: i.season, if (seasonData != null) seasonData.displaySeason else i.season,
i.data, i.data,
loadResponse.apiName, loadResponse.apiName,
id, id,
@ -1610,7 +1615,7 @@ class ResultViewModel2 : ViewModel() {
mainId mainId
) )
val season = eps.season ?: 0 val season = eps.seasonIndex ?: 0
val indexer = EpisodeIndexer(ep.key, season) val indexer = EpisodeIndexer(ep.key, season)
episodes[indexer]?.add(eps) ?: run { episodes[indexer]?.add(eps) ?: run {
episodes[indexer] = mutableListOf(eps) episodes[indexer] = mutableListOf(eps)
@ -1625,15 +1630,14 @@ class ResultViewModel2 : ViewModel() {
mutableMapOf() mutableMapOf()
val existingEpisodes = HashSet<Int>() val existingEpisodes = HashSet<Int>()
for ((index, episode) in loadResponse.episodes.sortedBy { for ((index, episode) in loadResponse.episodes.sortedBy {
(it.season?.times(10000) ?: 0) + (it.episode ?: 0) (it.season?.times(10_000) ?: 0) + (it.episode ?: 0)
}.withIndex()) { }.withIndex()) {
val episodeIndex = episode.episode ?: (index + 1) val episodeIndex = episode.episode ?: (index + 1)
val id = val id =
mainId + (episode.season?.times(100000) ?: 0) + episodeIndex + 1 mainId + (episode.season?.times(100_000) ?: 0) + episodeIndex + 1
if (!existingEpisodes.contains(id)) { if (!existingEpisodes.contains(id)) {
existingEpisodes.add(id) existingEpisodes.add(id)
val seasonIndex = episode.season?.minus(1) val seasonData =
val currentSeason =
loadResponse.seasonNames.getSeason(episode.season) loadResponse.seasonNames.getSeason(episode.season)
val ep = val ep =
@ -1642,8 +1646,8 @@ class ResultViewModel2 : ViewModel() {
filterName(episode.name), filterName(episode.name),
episode.posterUrl, episode.posterUrl,
episodeIndex, episodeIndex,
seasonIndex, seasonData?.season ?: episode.season,
currentSeason?.displaySeason ?: episode.season, if (seasonData != null) seasonData.displaySeason else episode.season,
episode.data, episode.data,
loadResponse.apiName, loadResponse.apiName,
id, id,
@ -1655,7 +1659,7 @@ class ResultViewModel2 : ViewModel() {
mainId mainId
) )
val season = episode.season ?: 0 val season = ep.seasonIndex ?: 0
val indexer = EpisodeIndexer(DubStatus.None, season) val indexer = EpisodeIndexer(DubStatus.None, season)
episodes[indexer]?.add(ep) ?: kotlin.run { episodes[indexer]?.add(ep) ?: kotlin.run {
@ -1747,16 +1751,17 @@ class ResultViewModel2 : ViewModel() {
val seasonData = loadResponse.seasonNames.getSeason(seasonNumber) val seasonData = loadResponse.seasonNames.getSeason(seasonNumber)
val fixedSeasonNumber = seasonData?.displaySeason ?: seasonNumber val fixedSeasonNumber = seasonData?.displaySeason ?: seasonNumber
val suffix = seasonData?.name?.let { " $it" } ?: "" val suffix = seasonData?.name?.let { " $it" } ?: ""
// If displaySeason is null then only show the name!
val name = val name = if (seasonData?.name != null && seasonData.displaySeason == null) {
/*loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { seasonData -> txt(seasonData.name)
txt(seasonData) } else {
} ?:*/txt( txt(
R.string.season_format, R.string.season_format,
txt(R.string.season), txt(R.string.season),
fixedSeasonNumber, fixedSeasonNumber,
suffix suffix
) )
}
name to seasonNumber name to seasonNumber
}) })
} }
@ -1812,7 +1817,12 @@ class ResultViewModel2 : ViewModel() {
} }
private fun loadTrailers(loadResponse: LoadResponse) = ioSafe { private fun loadTrailers(loadResponse: LoadResponse) = ioSafe {
_trailers.postValue(getTrailers(loadResponse, 3)) // we dont want to fetch too many trailers _trailers.postValue(
getTrailers(
loadResponse,
3
)
) // we dont want to fetch too many trailers
} }
private suspend fun getTrailers( private suspend fun getTrailers(

View file

@ -236,6 +236,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Ssbstream(), Ssbstream(),
Sbthe(), Sbthe(),
Vidgomunime(), Vidgomunime(),
Sbflix(),
Fastream(), Fastream(),
@ -269,6 +270,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
DoodWsExtractor(), DoodWsExtractor(),
DoodShExtractor(), DoodShExtractor(),
DoodWatchExtractor(), DoodWatchExtractor(),
DoodWfExtractor(),
AsianLoad(), AsianLoad(),
@ -321,6 +323,17 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Mvidoo(), Mvidoo(),
Streamplay(), Streamplay(),
Gdriveplayerapi(),
Gdriveplayerapp(),
Gdriveplayerfun(),
Gdriveplayerio(),
Gdriveplayerme(),
Gdriveplayerbiz(),
Gdriveplayerorg(),
Gdriveplayerus(),
Gdriveplayerco(),
Gdriveplayer(),
YoutubeExtractor(), YoutubeExtractor(),
YoutubeShortLinkExtractor(), YoutubeShortLinkExtractor(),
YoutubeMobileExtractor(), YoutubeMobileExtractor(),

View file

@ -200,16 +200,23 @@ class InAppUpdater {
private suspend fun Activity.downloadUpdate(url: String): Boolean { private suspend fun Activity.downloadUpdate(url: String): Boolean {
try { try {
Log.d(LOG_TAG, "Downloading update: $url") Log.d(LOG_TAG, "Downloading update: $url")
val appUpdateName = "CloudStream"
val appUpdateSuffix = "apk"
val localContext = this // Delete all old updates
this.cacheDir.listFiles()?.filter {
it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix
}?.forEach {
it.deleteOnExit()
}
val downloadedFile = File.createTempFile("CloudStream", ".apk") val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix")
val sink: BufferedSink = downloadedFile.sink().buffer() val sink: BufferedSink = downloadedFile.sink().buffer()
updateLock.withLock { updateLock.withLock {
sink.writeAll(app.get(url).body.source()) sink.writeAll(app.get(url).body.source())
sink.close() sink.close()
openApk(localContext, Uri.fromFile(downloadedFile)) openApk(this, Uri.fromFile(downloadedFile))
} }
return true return true
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -1,131 +1,134 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_height="match_parent"> android:orientation="vertical">
<FrameLayout
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_rowWeight="1"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Test" />
<com.google.android.material.button.MaterialButton
style="@style/WhiteButton"
android:layout_gravity="center_vertical|end"
app:icon="@drawable/ic_baseline_add_24"
android:text="@string/create_account"
android:id="@+id/create_account"
android:layout_width="wrap_content" />
</FrameLayout>
<LinearLayout <LinearLayout
android:orientation="vertical" android:layout_width="match_parent"
android:layout_marginBottom="60dp" android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp" android:layout_marginTop="20dp"
android:layout_width="match_parent" android:layout_marginBottom="10dp"
android:layout_height="wrap_content"> android:orientation="horizontal">
<EditText <TextView
android:textColorHint="?attr/grayTextColor" android:id="@+id/text1"
android:hint="@string/example_username" android:layout_weight="1"
android:autofillHints="username" android:layout_width="0dp"
android:id="@+id/login_username_input" android:layout_height="wrap_content"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusDown="@id/login_email_input"
android:requiresFadingEdge="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
tools:ignore="LabelFor" />
<EditText android:layout_rowWeight="1"
android:textColorHint="?attr/grayTextColor" android:layout_gravity="center_vertical"
android:autofillHints="emailAddress"
android:hint="@string/example_email"
android:id="@+id/login_email_input"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusUp="@id/login_username_input"
android:nextFocusDown="@id/login_server_input"
android:requiresFadingEdge="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:layout_width="match_parent" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:layout_height="wrap_content" android:textColor="?attr/textColor"
android:inputType="textEmailAddress" android:textSize="20sp"
tools:ignore="LabelFor" /> android:textStyle="bold"
tools:text="Test" />
<EditText <com.google.android.material.button.MaterialButton
android:textColorHint="?attr/grayTextColor" android:id="@+id/create_account"
android:hint="@string/example_ip" style="@style/WhiteButton"
android:id="@+id/login_server_input" android:layout_width="wrap_content"
android:nextFocusRight="@id/cancel_btt" android:layout_gravity="center_vertical|end"
android:nextFocusLeft="@id/apply_btt" android:text="@string/create_account"
android:nextFocusUp="@id/login_email_input" app:icon="@drawable/ic_baseline_add_24" />
android:nextFocusDown="@id/login_password_input"
android:requiresFadingEdge="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
tools:ignore="LabelFor" />
<EditText
android:textColorHint="?attr/grayTextColor"
android:hint="@string/example_password"
android:id="@+id/login_password_input"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusUp="@id/login_server_input"
android:nextFocusDown="@id/apply_btt"
android:requiresFadingEdge="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textVisiblePassword"
tools:ignore="LabelFor"
android:autofillHints="password" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/apply_btt_holder" android:layout_width="match_parent"
android:orientation="horizontal" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_marginHorizontal="10dp"
android:gravity="bottom|end" android:layout_marginBottom="60dp"
android:layout_marginTop="-60dp" android:orientation="vertical">
<EditText
android:id="@+id/login_username_input"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp"> android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/example_username"
android:inputType="text"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusDown="@id/login_email_input"
android:requiresFadingEdge="vertical"
android:textColorHint="?attr/grayTextColor"
tools:ignore="LabelFor" />
<EditText
android:id="@+id/login_email_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="emailAddress"
android:hint="@string/example_email"
android:inputType="textEmailAddress"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/login_username_input"
android:nextFocusDown="@id/login_server_input"
android:requiresFadingEdge="vertical"
android:textColorHint="?attr/grayTextColor"
tools:ignore="LabelFor" />
<EditText
android:id="@+id/login_server_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/example_ip"
android:inputType="textUri"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/login_email_input"
android:nextFocusDown="@id/login_password_input"
android:requiresFadingEdge="vertical"
android:textColorHint="?attr/grayTextColor"
tools:ignore="LabelFor" />
<EditText
android:id="@+id/login_password_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/example_password"
android:inputType="textVisiblePassword"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/login_server_input"
android:nextFocusDown="@id/apply_btt"
android:requiresFadingEdge="vertical"
android:textColorHint="?attr/grayTextColor"
tools:ignore="LabelFor" />
</LinearLayout>
<LinearLayout
android:id="@+id/apply_btt_holder"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:layout_marginTop="-60dp"
android:gravity="bottom|end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
style="@style/WhiteButton" android:id="@+id/apply_btt"
android:layout_gravity="center_vertical|end" style="@style/WhiteButton"
android:text="@string/login" android:layout_width="wrap_content"
android:id="@+id/apply_btt" android:layout_gravity="center_vertical|end"
android:layout_width="wrap_content" /> android:text="@string/login" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
style="@style/BlackButton" android:id="@+id/cancel_btt"
android:layout_gravity="center_vertical|end" style="@style/BlackButton"
android:text="@string/sort_cancel" android:layout_width="wrap_content"
android:id="@+id/cancel_btt" android:layout_gravity="center_vertical|end"
android:layout_width="wrap_content" /> android:text="@string/sort_cancel" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -102,18 +102,21 @@
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/home_select_cartoons" android:nextFocusLeft="@id/home_select_cartoons"
android:nextFocusRight="@id/home_select_livestreams"
android:text="@string/documentaries" /> android:text="@string/documentaries" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/home_select_livestreams" android:id="@+id/home_select_livestreams"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/home_select_documentaries" android:nextFocusLeft="@id/home_select_documentaries"
android:nextFocusRight="@id/home_select_nsfw"
android:text="@string/livestreams" /> android:text="@string/livestreams" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/home_select_nsfw" android:id="@+id/home_select_nsfw"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/home_select_livestreams" android:nextFocusLeft="@id/home_select_livestreams"
android:nextFocusRight="@id/home_select_nsfw"
android:text="@string/nsfw" /> android:text="@string/nsfw" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View file

@ -136,21 +136,23 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/search_select_documentaries" android:id="@+id/search_select_documentaries"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/search_select_cartoons" android:nextFocusLeft="@id/search_select_cartoons"
android:nextFocusRight="@id/search_select_livestreams"
android:text="@string/documentaries" /> android:text="@string/documentaries" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/search_select_livestreams" android:id="@+id/search_select_livestreams"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/search_select_documentaries" android:nextFocusLeft="@id/search_select_documentaries"
android:nextFocusRight="@id/search_select_nsfw"
android:text="@string/livestreams" /> android:text="@string/livestreams" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/search_select_nsfw" android:id="@+id/search_select_nsfw"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/search_select_livestreams" android:nextFocusLeft="@id/search_select_livestreams"
android:nextFocusRight="@id/search_select_others"
android:text="@string/nsfw" /> android:text="@string/nsfw" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View file

@ -31,7 +31,7 @@
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:requiresFadingEdge="horizontal"> android:requiresFadingEdge="horizontal">
<!-- Man what the fuck we need a recyclerview --> <!-- Man what the fuck we need a recyclerview -->
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -93,21 +93,23 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/home_select_documentaries" android:id="@+id/home_select_documentaries"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/home_select_cartoons" android:nextFocusLeft="@id/home_select_cartoons"
android:nextFocusRight="@id/home_select_livestreams"
android:text="@string/documentaries" /> android:text="@string/documentaries" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/home_select_livestreams" android:id="@+id/home_select_livestreams"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/home_select_documentaries" android:nextFocusLeft="@id/home_select_documentaries"
android:nextFocusRight="@id/home_select_nsfw"
android:text="@string/livestreams" /> android:text="@string/livestreams" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/home_select_nsfw" android:id="@+id/home_select_nsfw"
style="@style/RoundedSelectableButton" style="@style/RoundedSelectableButton"
android:nextFocusLeft="@id/home_select_livestreams" android:nextFocusLeft="@id/home_select_livestreams"
android:nextFocusRight="@id/home_select_others"
android:text="@string/nsfw" /> android:text="@string/nsfw" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton