This commit is contained in:
LagradOst 2021-05-16 01:37:42 +02:00
parent 1549f67cd7
commit 22f2a8810e
26 changed files with 827 additions and 16 deletions

View file

@ -1,6 +1,7 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-android-extensions'
} }
android { android {
@ -59,4 +60,7 @@ dependencies {
implementation("com.google.android.material:material:1.3.0") implementation("com.google.android.material:material:1.3.0")
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'jp.wasabeef:glide-transformations:4.0.0'
} }

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lagradost.cloudstream3"> package="com.lagradost.cloudstream3">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:allowBackup="true" android:allowBackup="true"

View file

@ -0,0 +1,45 @@
package com.lagradost.cloudstream3
import com.lagradost.cloudstream3.APIHolder.apis
class AllProvider : MainAPI() {
override val name: String
get() = "All Sources"
var providersActive = HashSet<String>()
override fun search(query: String): ArrayList<Any>? {
val list = apis.filter { a ->
a.name != this.name && (providersActive.size == 0 || providersActive.contains(a.name))
}.pmap { a ->
a.search(query)
}
var maxCount = 0
var providerCount = 0
for (res in list) {
if (res != null) {
if (res.size > maxCount) {
maxCount = res.size
}
providerCount++
}
}
if (providerCount == 0) return null
if (maxCount == 0) return ArrayList()
val result = ArrayList<Any>()
for (i in 0..maxCount) {
for (res in list) {
if (res != null) {
if (i < res.size) {
result.add(res[i])
}
}
}
}
return result
}
}

View file

@ -1,5 +1,7 @@
package com.lagradost.cloudstream3 package com.lagradost.cloudstream3
import android.app.Activity
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
@ -13,9 +15,20 @@ val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!! .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
object APIHolder { object APIHolder {
val allApi = AllProvider()
private const val defProvider = 0
val apis = arrayListOf<MainAPI>( val apis = arrayListOf<MainAPI>(
ShiroProvider() ShiroProvider()
) )
fun Activity.getApiSettings(): HashSet<String> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager.getStringSet(this.getString(R.string.search_providers_list_key),
setOf(apis[defProvider].name))?.toHashSet() ?: hashSetOf(apis[defProvider].name)
}
} }
@ -35,6 +48,17 @@ abstract class MainAPI {
} }
} }
fun MainAPI.fixUrl(url: String): String {
if (url.startsWith('/')) {
return mainUrl + url
}
else if(!url.startsWith("http") && !url.startsWith("//")) {
return "$mainUrl/$url"
}
return url
}
data class Link( data class Link(
val name: String, val name: String,
val url: String, val url: String,

View file

@ -0,0 +1,28 @@
package com.lagradost.cloudstream3
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
//https://stackoverflow.com/questions/34697828/parallel-operations-on-kotlin-collections
fun <T, R> Iterable<T>.pmap(
numThreads: Int = maxOf(Runtime.getRuntime().availableProcessors() - 2, 1),
exec: ExecutorService = Executors.newFixedThreadPool(numThreads),
transform: (T) -> R,
): List<R> {
// default size is just an inlined version of kotlin.collections.collectionSizeOrDefault
val defaultSize = if (this is Collection<*>) this.size else 10
val destination = Collections.synchronizedList(ArrayList<R>(defaultSize))
for (item in this) {
exec.submit { destination.add(transform(item)) }
}
exec.shutdown()
exec.awaitTermination(1, TimeUnit.DAYS)
return ArrayList<R>(destination)
}

View file

@ -1,20 +1,52 @@
package com.lagradost.cloudstream3 package com.lagradost.cloudstream3
import android.app.Activity import android.app.Activity
import android.content.res.Resources
import android.view.View import android.view.View
import androidx.preference.PreferenceManager
object UIHelper { object UIHelper {
fun Activity.getStatusBarHeight(): Int { val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
val Float.toPx: Float get() = (this * Resources.getSystem().displayMetrics.density)
val Int.toDp: Int get() = (this / Resources.getSystem().displayMetrics.density).toInt()
val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density)
fun Activity.loadResult(url: String, apiName: String) {
/*this.runOnUiThread {
this.supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
.add(R.id.homeRoot, ResultFragment().newInstance(url, apiName))
.commit()
}*/
}
private fun Activity.getStatusBarHeight(): Int {
var result = 0 var result = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) { if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId) result = resources.getDimensionPixelSize(resourceId)
} }
return result return result
} }
fun Activity.fixPaddingStatusbar(v: View) { fun Activity.fixPaddingStatusbar(v: View) {
v.setPadding(v.paddingLeft, v.paddingTop + getStatusBarHeight(), v.paddingRight, v.paddingBottom) v.setPadding(v.paddingLeft, v.paddingTop + getStatusBarHeight(), v.paddingRight, v.paddingBottom)
} }
private fun Activity.getGridFormat(): String {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager.getString(getString(R.string.grid_format_key), "grid")!!
}
fun Activity.getGridFormatId(): Int {
return when (getGridFormat()) {
"list" -> R.layout.search_result_compact
"compact_list" -> R.layout.search_result_super_compact
else -> R.layout.search_result_grid
}
}
fun Activity.getGridIsCompact(): Boolean {
return getGridFormat() != "grid"
}
} }

View file

@ -3,9 +3,9 @@ package com.lagradost.cloudstream3.animeproviders
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.* import com.lagradost.cloudstream3.*
import org.jsoup.Jsoup
import java.lang.Exception
import java.net.URLEncoder import java.net.URLEncoder
import java.util.*
class ShiroProvider : MainAPI() { class ShiroProvider : MainAPI() {
companion object { companion object {
@ -42,6 +42,12 @@ class ShiroProvider : MainAPI() {
@JsonProperty("_id") val _id: String, @JsonProperty("_id") val _id: String,
@JsonProperty("slug") val slug: String, @JsonProperty("slug") val slug: String,
@JsonProperty("name") val name: String, @JsonProperty("name") val name: String,
@JsonProperty("episodeCount") val episodeCount: String,
@JsonProperty("language") val language: String,
@JsonProperty("type") val type: String,
@JsonProperty("year") val year: String,
@JsonProperty("canonicalTitle") val canonicalTitle: String,
@JsonProperty("english") val english: String?,
) )
data class ShiroSearchResponse( data class ShiroSearchResponse(
@ -67,16 +73,50 @@ class ShiroProvider : MainAPI() {
) )
override fun search(query: String): ArrayList<Any>? { override fun search(query: String): ArrayList<Any>? {
if (!autoLoadToken()) return null try {
val returnValue: ArrayList<Any> = ArrayList() if (!autoLoadToken()) return null
val response = khttp.get("https://tapi.shiro.is/advanced?search=${ val returnValue: ArrayList<Any> = ArrayList()
URLEncoder.encode( val response = khttp.get("https://tapi.shiro.is/advanced?search=${
query, URLEncoder.encode(
"UTF-8" query,
) "UTF-8"
}&token=$token") )
val mapped = response.let { mapper.readValue<ShiroSearchResponse>(it.text) } }&token=$token".replace("+", "%20"))
println(response.text)
val mapped = response.let { mapper.readValue<ShiroFullSearchResponse>(it.text) }
for (i in mapped.data.nav.currentPage.items) {
return returnValue val type = when (i.type) {
"TV" -> TvType.Anime
"OVA" -> TvType.ONA
"movie" -> TvType.Movie
else -> TvType.Anime
}
val isDubbed = i.language == "dubbed"
val set: EnumSet<DubStatus> = EnumSet.noneOf(DubStatus::class.java)
if (isDubbed)
set.add(DubStatus.HasDub)
else
set.add(DubStatus.HasSub)
val episodeCount = i.episodeCount.toInt()
returnValue.add(AnimeSearchResponse(
i.english ?: i.canonicalTitle,
"$mainUrl/${i.slug}",
this.name,
type,
"https://cdn.shiro.is/${i.image}",
i.year.toInt(),
i.canonicalTitle,
set,
if (isDubbed) episodeCount else null,
if (!isDubbed) episodeCount else null,
))
}
return returnValue
} catch (e: Exception) {
return null
}
} }
} }

View file

@ -0,0 +1,124 @@
package com.lagradost.cloudstream3.ui.search
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.UIHelper.getGridFormatId
import com.lagradost.cloudstream3.UIHelper.getGridIsCompact
import com.lagradost.cloudstream3.UIHelper.loadResult
import com.lagradost.cloudstream3.UIHelper.toPx
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import kotlinx.android.synthetic.main.search_result_compact.view.*
import kotlinx.android.synthetic.main.search_result_compact.view.backgroundCard
import kotlinx.android.synthetic.main.search_result_compact.view.imageText
import kotlinx.android.synthetic.main.search_result_compact.view.imageView
import kotlinx.android.synthetic.main.search_result_grid.view.*
import kotlin.math.roundToInt
class SearchAdapter(
activity: Activity,
animeList: ArrayList<Any>,
resView: AutofitRecyclerView,
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var cardList = animeList
private var activity: Activity = activity
var resView: AutofitRecyclerView? = resView
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = activity.getGridFormatId()
return CardViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false),
activity,
resView!!
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is CardViewHolder -> {
holder.bind(cardList[position])
}
}
}
override fun getItemCount(): Int {
return cardList.size
}
class CardViewHolder
constructor(itemView: View, _activity: Activity, resView: AutofitRecyclerView) : RecyclerView.ViewHolder(itemView) {
val activity = _activity
val cardView: ImageView = itemView.imageView
val cardText: TextView = itemView.imageText
val text_type: TextView? = itemView.text_type
val text_is_dub: TextView? = itemView.text_is_dub
val text_is_sub: TextView? = itemView.text_is_sub
//val cardTextExtra: TextView? = itemView.imageTextExtra
//val imageTextProvider: TextView? = itemView.imageTextProvider
val bg = itemView.backgroundCard
val compactView = activity.getGridIsCompact()
private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
fun bind(card: Any) {
if (card is SearchResponse) { // GENERIC
if (!compactView) {
cardView.apply {
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
coverHeight
)
}
}
text_type?.text = when (card.type) {
TvType.Anime -> "Anime"
TvType.Movie -> "Movie"
TvType.ONA -> "ONA"
TvType.TvSeries -> "TV"
}
text_is_dub?.visibility = View.GONE
text_is_sub?.visibility = View.GONE
cardText.text = card.name
//imageTextProvider.text = card.apiName
val glideUrl =
GlideUrl(card.posterUrl)
activity.let {
Glide.with(it)
.load(glideUrl)
.into(cardView)
}
bg.setOnClickListener {
activity.loadResult(card.url, card.apiName)
}
when (card) {
is AnimeSearchResponse -> {
if (card.dubStatus?.contains(DubStatus.HasDub) == true) {
text_is_dub?.visibility = View.VISIBLE
}
if (card.dubStatus?.contains(DubStatus.HasSub) == true) {
text_is_sub?.visibility = View.VISIBLE
}
}
}
}
}
}
}

View file

@ -1,15 +1,29 @@
package com.lagradost.cloudstream3.ui.search package com.lagradost.cloudstream3.ui.search
import android.content.DialogInterface
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.APIHolder.allApi
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiSettings
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getGridIsCompact
import kotlinx.android.synthetic.main.fragment_search.*
import kotlin.concurrent.thread
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
@ -18,7 +32,7 @@ class SearchFragment : Fragment() {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?,
): View? { ): View? {
searchViewModel = searchViewModel =
ViewModelProvider(this).get(SearchViewModel::class.java) ViewModelProvider(this).get(SearchViewModel::class.java)
@ -29,6 +43,91 @@ class SearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// activity?.fixPaddingStatusbar(searchRoot) activity?.fixPaddingStatusbar(searchRoot)
val compactView = activity?.getGridIsCompact() ?: false
val spanCountLandscape = if (compactView) 2 else 6
val spanCountPortrait = if (compactView) 1 else 3
val orientation = resources.configuration.orientation
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
cardSpace.spanCount = spanCountLandscape
} else {
cardSpace.spanCount = spanCountPortrait
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let {
SearchAdapter(
it,
ArrayList(),
cardSpace,
)
}
cardSpace.adapter = adapter
search_loading_bar.alpha = 0f
val search_exit_icon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
val search_mag_icon = main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
search_mag_icon.scaleX = 0.65f
search_mag_icon.scaleY = 0.65f
search_filter.setOnClickListener {
val apiNamesSetting = activity?.getApiSettings()
if (apiNamesSetting != null) {
val apiNames = apis.map { it.name }
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
builder.setMultiChoiceItems(apiNames.toTypedArray(),
apiNames.map { a -> apiNamesSetting.contains(a) }.toBooleanArray()
) { _, position: Int, checked: Boolean ->
val apiNamesSettingLocal = activity?.getApiSettings()
if (apiNamesSettingLocal != null) {
val settingsManagerLocal = PreferenceManager.getDefaultSharedPreferences(activity)
if (checked) {
apiNamesSettingLocal.add(apiNames[position])
} else {
apiNamesSettingLocal.remove(apiNames[position])
}
val edit = settingsManagerLocal.edit()
edit.putStringSet(getString(R.string.search_providers_list_key),
apiNames.filter { a -> apiNamesSettingLocal.contains(a) }.toSet())
edit.apply()
allApi.providersActive = apiNamesSettingLocal
}
}
builder.setTitle("Search Providers")
builder.setNegativeButton("Cancel") { _, _ -> }
builder.show()
}
}
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
search_exit_icon.alpha = 0f
search_loading_bar.alpha = 1f
thread {
val data = allApi.search(query)//MainActivity.activeAPI.search(query)
activity?.runOnUiThread {
if (data == null) {
Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
} else {
(cardSpace.adapter as SearchAdapter).cardList = data
(cardSpace.adapter as SearchAdapter).notifyDataSetChanged()
}
search_exit_icon.alpha = 1f
search_loading_bar.alpha = 0f
}
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
return true
}
})
main_search.onActionViewExpanded()
} }
} }

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromXScale="0.9"
android:toXScale="1.0"
android:fromYScale="0.9"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
>
</scale>
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromXScale="1.0"
android:toXScale="0.9"
android:fromYScale="1.0"
android:toYScale="0.9"
android:pivotX="50%"
android:pivotY="50%"
>
</scale>
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromXScale="0.9"
android:toXScale="1.0"
android:fromYScale="0.9"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
>
</scale>
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromXScale="1.0"
android:toXScale="0.9"
android:fromYScale="1.0"
android:toYScale="0.9"
android:pivotX="50%"
android:pivotY="50%"
>
</scale>
<alpha
android:interpolator="@android:anim/linear_interpolator"
android:duration="@integer/config_navAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/dubColorBg"/>
<corners android:radius="@dimen/roundedImageRadius"/>
<stroke android:color="@color/dubColor" android:width="1dp"/>
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/subColorBg"/>
<corners android:radius="@dimen/roundedImageRadius"/>
<stroke android:color="@color/subColor" android:width="1dp"/>
</shape>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@android:color/transparent"
android:endColor="#000"
android:angle="-90">
</gradient>
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/typeColorBg"/>
<corners android:radius="@dimen/roundedImageRadius"/>
<stroke android:color="@color/typeColor" android:width="1dp"/>
</shape>

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginBottom="0dp"
android:layoutDirection="ltr"
>
<androidx.cardview.widget.CardView
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:id="@+id/backgroundCard"
android:layout_margin="2dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="2dp"
android:elevation="0dp"
app:cardCornerRadius="@dimen/roundedImageRadius"
app:cardBackgroundColor="@color/itemBackground"
android:clickable="true"
android:focusable="true"
>
<androidx.cardview.widget.CardView
android:elevation="0dp"
app:cardCornerRadius="@dimen/roundedImageRadius"
android:layout_width="54dp"
android:layout_height="match_parent">
<ImageView
android:layout_gravity="left"
android:id="@+id/imageView"
android:scaleType="centerCrop"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
tools:ignore="RtlHardcoded"/>
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_marginStart="64dp">
<TextView
tools:text="@string/no_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"
android:textColor="@color/textColor"
android:id="@+id/imageText"
android:textStyle="bold"
android:maxLines="3"
/>
<TextView
tools:text="@string/no_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/lightGrayTextColor"
android:id="@+id/imageTextExtra"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="3"
/>
<TextView
tools:text="Rated 4.13"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:id="@+id/imageTextProvider"
android:maxLines="1"
/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:focusable="true"
android:clickable="true"
android:id="@+id/search_result_root"
>
<androidx.cardview.widget.CardView
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_margin="2dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="2dp"
android:elevation="10dp"
app:cardCornerRadius="@dimen/roundedImageRadius"
android:id="@+id/backgroundCard"
app:cardBackgroundColor="@color/darkBackground"
>
<!-- USING CROP RATIO (182/268), centerCrop for fill -->
<ImageView
android:duplicateParentState="true"
android:id="@+id/imageView"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/search_poster_descript"/>
<ImageView
android:focusable="false"
android:clickable="false"
android:layout_width="match_parent"
android:layout_height="50dp"
android:src="@drawable/title_shadow"
android:layout_gravity="bottom" android:contentDescription="@string/shadow_descript">
</ImageView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="bottom"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:textColor="@color/textColor"
android:id="@+id/imageText"
android:textStyle="bold"
android:maxLines="2"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:ellipsize="end"
/>
<TextView
android:text="Movie"
android:visibility="gone"
android:id="@+id/text_type"
android:textColor="@color/textColor"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingTop="4dp"
android:layout_marginBottom="5dp"
android:layout_gravity="start"
android:paddingBottom="4dp"
android:minWidth="50dp"
android:gravity="center"
android:background="@drawable/type_bg_color"
android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
<LinearLayout
android:orientation="vertical"
android:layout_gravity="end"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Dub"
android:id="@+id/text_is_dub"
android:textColor="@color/textColor"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingTop="4dp"
android:layout_marginBottom="5dp"
android:layout_gravity="end"
android:paddingBottom="4dp"
android:minWidth="50dp"
android:gravity="center"
android:background="@drawable/dub_bg_color"
android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
<TextView
android:id="@+id/text_is_sub"
android:text="Sub"
android:layout_gravity="end"
android:textColor="@color/textColor"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:minWidth="50dp"
android:gravity="center"
android:background="@drawable/sub_bg_color"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false">
</TextView>
</LinearLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="3dp"
>
<androidx.cardview.widget.CardView
android:foreground="?android:attr/selectableItemBackgroundBorderless"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/backgroundCard"
android:layout_margin="2dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:elevation="0dp"
app:cardCornerRadius="@dimen/roundedImageRadius"
app:cardBackgroundColor="@color/itemBackground"
android:clickable="true"
android:focusable="true"
>
<!-- USING CROP RATIO (182/268), centerCrop for fill -->
<androidx.cardview.widget.CardView
app:cardCornerRadius="@dimen/roundedImageRadius"
android:layout_width="35dp"
android:elevation="0dp"
android:layout_height="match_parent">
<ImageView
android:layout_gravity="left"
android:id="@+id/imageView"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
tools:ignore="RtlHardcoded" android:contentDescription="@string/search_poster_descript"/>
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_marginStart="45dp">
<TextView
tools:text="@string/no_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="17sp"
android:textColor="@color/textColor"
android:id="@+id/imageText"
android:textStyle="normal"
android:maxLines="3"
/>
<TextView
tools:text="@string/no_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:textColor="@color/lightGrayTextColor"
android:id="@+id/imageTextExtra"
android:textSize="13sp"
android:textStyle="bold"
android:maxLines="3"
/>
<TextView
tools:text="Rated 4.13"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorPrimary"
android:id="@+id/imageTextProvider"
android:maxLines="1"
/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View file

@ -21,4 +21,10 @@
<color name="transparent">#00000000</color> <color name="transparent">#00000000</color>
<color name="white">#FFF</color> <color name="white">#FFF</color>
<color name="dubColor">#3b65f5</color>
<color name="dubColorBg">#4D3B65F5</color>
<color name="subColor">#F53B66</color>
<color name="subColorBg">#4DF53B66</color>
<color name="typeColor">#F54A3B</color>
<color name="typeColorBg">#4DF54A3B</color>
</resources> </resources>

View file

@ -5,4 +5,9 @@
<string name="title_downloads">Downloads</string> <string name="title_downloads">Downloads</string>
<string name="search_hint">Search...</string> <string name="search_hint">Search...</string>
<string name="change_providers_descript">Change Providers</string> <string name="change_providers_descript">Change Providers</string>
<string name="search_providers_list_key">search_providers_list</string>
<string name="grid_format_key">grid_format</string>
<string name="search_poster_descript">Poster</string>
<string name="no_data">No Data</string>
<string name="shadow_descript">Shadow</string>
</resources> </resources>