Merge remote-tracking branch 'origin/master' into exotime

This commit is contained in:
IndusAryan 2024-01-13 13:44:47 +05:30
commit 4cc33146e3
17 changed files with 304 additions and 146 deletions

View file

@ -1374,6 +1374,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
observeNullable(viewModel.favoriteStatus) observeFavoriteStatus@{ isFavorite ->
resultviewPreviewFavorite.isVisible = isFavorite != null
if (isFavorite == null) return@observeFavoriteStatus
val drawable = if (isFavorite) {
R.drawable.ic_baseline_favorite_24
} else {
R.drawable.ic_baseline_favorite_border_24
}
resultviewPreviewFavorite.setImageResource(drawable)
}
resultviewPreviewFavorite.setOnClickListener{
viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? ->
if (newStatus == null) return@toggleFavoriteStatus
val message = if (newStatus) {
R.string.favorite_added
} else {
R.string.favorite_removed
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(this@MainActivity) ?: ""
showToast(txt(message, name), Toast.LENGTH_SHORT)
}
}
if (!isTvSettings()) // dont want this clickable on tv layout
resultviewPreviewDescription.setOnClickListener { view ->
view.context?.let { ctx ->

View file

@ -18,7 +18,22 @@ open class ContentX : ExtractorApi() {
val i_source = app.get(url, referer=ext_ref).text
val i_extract = Regex("""window\.openPlayer\('([^']+)'""").find(i_source)!!.groups[1]?.value ?: throw ErrorLoadingException("i_extract is null")
val vid_source = app.get("https://contentx.me/source2.php?v=${i_extract}", referer=ext_ref).text
val sub_urls = mutableSetOf<String>()
Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(i_source).forEach {
val (sub_url, sub_lang) = it.destructured
if (sub_url in sub_urls) { return@forEach }
sub_urls.add(sub_url)
subtitleCallback.invoke(
SubtitleFile(
lang = sub_lang.replace("\\u0131", "ı").replace("\\u0130", "İ").replace("\\u00fc", "ü").replace("\\u00e7", "ç"),
url = fixUrl(sub_url.replace("\\", ""))
)
)
}
val vid_source = app.get("${mainUrl}/source2.php?v=${i_extract}", referer=ext_ref).text
val vid_extract = Regex("""file\":\"([^\"]+)""").find(vid_source)!!.groups[1]?.value ?: throw ErrorLoadingException("vid_extract is null")
val m3u_link = vid_extract.replace("\\", "")
@ -35,7 +50,7 @@ open class ContentX : ExtractorApi() {
val i_dublaj = Regex(""",\"([^']+)\",\"Türkçe""").find(i_source)!!.groups[1]?.value
if (i_dublaj != null) {
val dublaj_source = app.get("https://contentx.me/source2.php?v=${i_dublaj}", referer=ext_ref).text
val dublaj_source = app.get("${mainUrl}/source2.php?v=${i_dublaj}", referer=ext_ref).text
val dublaj_extract = Regex("""file\":\"([^\"]+)""").find(dublaj_source)!!.groups[1]?.value ?: throw ErrorLoadingException("dublaj_extract is null")
val dublaj_link = dublaj_extract.replace("\\", "")

View file

@ -6,3 +6,8 @@ class Hotlinger : ContentX() {
override var name = "Hotlinger"
override var mainUrl = "https://hotlinger.com"
}
class FourCX : ContentX() {
override var name = "FourCX"
override var mainUrl = "https://four.contentx.me"
}

View file

@ -9,6 +9,10 @@ class StreamTapeNet : StreamTape() {
override var mainUrl = "https://streamtape.net"
}
class StreamTapeXyz : StreamTape() {
override var mainUrl = "https://streamtape.xyz"
}
class ShaveTape : StreamTape(){
override var mainUrl = "https://shavetape.cash"
}

View file

@ -216,6 +216,9 @@ class HomeParentItemAdapterPreview(
viewModel.click(callback)
return@HomeChildItemAdapter
}
(callback.view.context?.getActivity() as? MainActivity)?.loadPopup(callback.card, load = false)
/*
callback.view.context?.getActivity()?.showOptionSelectStringRes(
callback.view,
callback.card.posterUrl,
@ -261,6 +264,7 @@ class HomeParentItemAdapterPreview(
}
}
}
*/
}

View file

@ -20,11 +20,12 @@ import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView
import androidx.core.view.allViews
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.lagradost.cloudstream3.APIHolder
@ -35,6 +36,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugAssert
@ -80,6 +82,8 @@ data class ProviderLibraryData(
class LibraryFragment : Fragment() {
companion object {
val listLibraryItems = mutableListOf<SyncAPI.LibraryItem>()
fun newInstance() = LibraryFragment()
/**
@ -91,6 +95,7 @@ class LibraryFragment : Fragment() {
private val libraryViewModel: LibraryViewModel by activityViewModels()
var binding: FragmentLibraryBinding? = null
private var toggleRandomButton = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@ -196,6 +201,25 @@ class LibraryFragment : Fragment() {
}
}
//Load value for toggling Random button. Hide at startup
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
toggleRandomButton =
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
) && !SettingsFragment.isTvSettings()
binding?.libraryRandom?.visibility = View.GONE
}
binding?.libraryRandom?.setOnClickListener {
if (listLibraryItems.isNotEmpty()) {
val listLibraryItem = listLibraryItems.random()
libraryViewModel.currentSyncApi?.syncIdName?.let {
loadLibraryItem(it, listLibraryItem.syncId,listLibraryItem)
}
}
}
/**
* Shows a plugin selection dialogue and saves the response
@ -277,8 +301,10 @@ class LibraryFragment : Fragment() {
{ isScrollingDown: Boolean ->
if (isScrollingDown) {
binding?.sortFab?.shrink()
binding?.libraryRandom?.shrink()
} else {
binding?.sortFab?.extend()
binding?.libraryRandom?.extend()
}
}) callback@{ searchClickCallback ->
// To prevent future accidents
@ -303,52 +329,7 @@ class LibraryFragment : Fragment() {
}
SEARCH_ACTION_LOAD -> {
// This basically first selects the individual opener and if that is default then
// selects the whole list opener
val savedListSelection =
getKey<LibraryOpener>("$currentAccount/$LIBRARY_FOLDER", syncName.name)
val savedSelection = getKey<LibraryOpener>(
"$currentAccount/$LIBRARY_FOLDER",
syncId
).takeIf {
it?.openType != LibraryOpenerType.Default
} ?: savedListSelection
when (savedSelection?.openType) {
null, LibraryOpenerType.Default -> {
// Prevents opening MAL/AniList as a provider
if (APIHolder.getApiFromNameNull(searchClickCallback.card.apiName) != null) {
activity?.loadSearchResult(
searchClickCallback.card
)
} else {
// Search when no provider can open
QuickSearchFragment.pushSearch(
activity,
searchClickCallback.card.name
)
}
}
LibraryOpenerType.None -> {}
LibraryOpenerType.Provider ->
savedSelection.providerData?.apiName?.let { apiName ->
activity?.loadResult(
searchClickCallback.card.url,
apiName,
)
}
LibraryOpenerType.Browser ->
openBrowser(searchClickCallback.card.url)
LibraryOpenerType.Search -> {
QuickSearchFragment.pushSearch(
activity,
searchClickCallback.card.name
)
}
}
loadLibraryItem(syncName, syncId, searchClickCallback.card)
}
}
}
@ -414,6 +395,16 @@ class LibraryFragment : Fragment() {
binding?.viewpager?.setCurrentItem(page, false)
}
observe(libraryViewModel.currentPage){
if (toggleRandomButton) {
listLibraryItems.clear()
listLibraryItems.addAll(pages[it].items)
libraryRandom.isVisible = listLibraryItems.isNotEmpty()
} else {
libraryRandom.isGone = true
}
}
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
// Without this there would be a flashing effect:
// loading -> show old viewpager -> black screen -> show new viewpager
@ -512,6 +503,62 @@ class LibraryFragment : Fragment() {
}
})*/
}
private fun loadLibraryItem(
syncName: SyncIdName,
syncId: String,
card: SearchResponse
) {
// This basically first selects the individual opener and if that is default then
// selects the whole list opener
val savedListSelection =
getKey<LibraryOpener>("$currentAccount/$LIBRARY_FOLDER", syncName.name)
val savedSelection = getKey<LibraryOpener>(
"$currentAccount/$LIBRARY_FOLDER",
syncId
).takeIf {
it?.openType != LibraryOpenerType.Default
} ?: savedListSelection
when (savedSelection?.openType) {
null, LibraryOpenerType.Default -> {
// Prevents opening MAL/AniList as a provider
if (APIHolder.getApiFromNameNull(card.apiName) != null) {
activity?.loadSearchResult(
card
)
} else {
// Search when no provider can open
QuickSearchFragment.pushSearch(
activity,
card.name
)
}
}
LibraryOpenerType.None -> {}
LibraryOpenerType.Provider ->
savedSelection.providerData?.apiName?.let { apiName ->
activity?.loadResult(
card.url,
apiName,
)
}
LibraryOpenerType.Browser ->
openBrowser(card.url)
LibraryOpenerType.Search -> {
QuickSearchFragment.pushSearch(
activity,
card.name
)
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
super.onConfigurationChanged(newConfig)

View file

@ -377,7 +377,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
protected fun exitFullscreen() {
activity?.showSystemUI()
//if (lockRotation)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
@ -389,6 +388,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
activity?.window?.attributes = lp
activity?.showSystemUI()
}
override fun onResume() {
@ -1481,6 +1481,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
playerGoBack.setOnClickListener {
activity?.popCurrentPage()
activity?.showSystemUI()
}
playerSourcesBtt.setOnClickListener {

View file

@ -1383,6 +1383,7 @@ class GeneratorPlayer : FullScreenPlayer() {
}
binding?.playerLoadingGoBack?.setOnClickListener {
exitFullscreen()
player.release()
activity?.popCurrentPage()
}

View file

@ -2,6 +2,9 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Rect
@ -31,6 +34,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
@ -77,7 +81,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
open class ResultFragmentPhone : FullScreenPlayer() {
private val gestureRegionsListener = object : PanelsChildGestureRegionObserver.GestureRegionsListener {
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
@ -247,6 +250,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
}
var selectSeason: String? = null
var selectEpisodeRange: String? = null
private fun setUrl(url: String?) {
if (url == null) {
@ -751,6 +755,17 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultLoadingError.isVisible = data is Resource.Failure
resultErrorText.isVisible = data is Resource.Failure
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
resultTitle.setOnLongClickListener {
val titleToCopy = resultTitle.text
val clipboardManager =
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
clipboardManager?.setPrimaryClip(ClipData.newPlainText("Title", titleToCopy))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
showToast(R.string.copyTitle, Toast.LENGTH_SHORT)
}
return@setOnLongClickListener true
}
}
}
@ -1027,6 +1042,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
observeNullable(viewModel.selectedRange) { range ->
resultBinding?.apply {
resultEpisodeSelect.setText(range)
selectEpisodeRange = range?.asStringNull(resultEpisodeSelect.context)
// If Season button is invisible then the bookmark button next focus is episode select
if (resultEpisodeSelect.isVisible && !resultSeasonButton.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultEpisodeSelect)
@ -1060,9 +1077,12 @@ open class ResultFragmentPhone : FullScreenPlayer() {
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
}
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
index to name
}) {
activity?.showDialog(
names.map { it.second },
names.indexOfFirst { it.second == selectEpisodeRange },
"",
false,
{}) { itemId ->
viewModel.changeRange(names[itemId].first)
}
}

View file

@ -1006,6 +1006,7 @@ class ResultViewModel2 : ViewModel() {
removeFavoritesData(currentId)
statusChangedCallback?.invoke(false)
_favoriteStatus.postValue(false)
MainActivity.reloadLibraryEvent(true)
} else {
checkAndWarnDuplicates(
context,
@ -1050,8 +1051,8 @@ class ResultViewModel2 : ViewModel() {
)
_favoriteStatus.postValue(true)
statusChangedCallback?.invoke(true)
MainActivity.reloadLibraryEvent(true)
}
}
}
@ -2604,6 +2605,11 @@ class ResultViewModel2 : ViewModel() {
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
this.tags = searchResponse.tags
}
if (searchResponse is DataStoreHelper.BookmarkedData) {
this.plot = searchResponse.plot
this.rating = searchResponse.rating
this.tags = searchResponse.tags
}
}
val mainId = searchResponse.id ?: response.getId()

View file

@ -264,6 +264,9 @@ class SearchFragment : Fragment() {
builder.setContentView(selectMainpageBinding.root)
builder.show()
builder.let { dialog ->
val previousSelectedApis = selectedApis.toSet()
val previousSelectedSearchTypes = selectedSearchTypes.toSet()
val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
set.size > 1 || set.contains(AllLanguagesName)
}
@ -352,8 +355,10 @@ class SearchFragment : Fragment() {
selectedApis = currentSelectedApis
// run search when dialog is close
if(previousSelectedApis != selectedApis.toSet() || previousSelectedSearchTypes != selectedSearchTypes.toSet()) {
search(binding?.mainSearch?.query?.toString())
}
}
updateList(selectedSearchTypes.toList())
}
}

View file

@ -104,6 +104,7 @@ import com.lagradost.cloudstream3.extractors.TauVideo
import com.lagradost.cloudstream3.extractors.SibNet
import com.lagradost.cloudstream3.extractors.ContentX
import com.lagradost.cloudstream3.extractors.Hotlinger
import com.lagradost.cloudstream3.extractors.FourCX
import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem
import com.lagradost.cloudstream3.extractors.VideoSeyred
@ -151,6 +152,7 @@ import com.lagradost.cloudstream3.extractors.StreamSB8
import com.lagradost.cloudstream3.extractors.StreamSB9
import com.lagradost.cloudstream3.extractors.StreamTape
import com.lagradost.cloudstream3.extractors.StreamTapeNet
import com.lagradost.cloudstream3.extractors.StreamTapeXyz
import com.lagradost.cloudstream3.extractors.StreamhideCom
import com.lagradost.cloudstream3.extractors.StreamhideTo
import com.lagradost.cloudstream3.extractors.Streamhub2
@ -619,6 +621,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
StreamTape(),
StreamTapeNet(),
ShaveTape(),
StreamTapeXyz(),
//mixdrop extractors
MixDropBz(),
@ -683,6 +686,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
SibNet(),
ContentX(),
Hotlinger(),
FourCX(),
HDMomPlayer(),
HDPlayerSystem(),
VideoSeyred(),

View file

@ -35,6 +35,7 @@ import androidx.core.graphics.blue
import androidx.core.graphics.drawable.toBitmapOrNull
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
@ -401,6 +402,18 @@ object UIHelper {
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (window.insetsController != null) {
window!!.insetsController?.hide(WindowInsetsCompat.Type.systemBars())
window!!.insetsController?.systemBarsBehavior =
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
// Set the content to appear under the system bars so that the
@ -411,71 +424,13 @@ object UIHelper {
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
// or View.SYSTEM_UI_FLAG_LOW_PROFILE
)
// window.addFlags(View.KEEP_SCREEN_ON)
}
}
fun FragmentActivity.popCurrentPage() {
this.onBackPressedDispatcher.onBackPressed()
/*val currentFragment = supportFragmentManager.fragments.lastOrNull {
it.isVisible
} ?: return
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
.remove(currentFragment)
.commitAllowingStateLoss()*/
}
/*
fun FragmentActivity.popCurrentPage(isInPlayer: Boolean, isInExpandedView: Boolean, isInResults: Boolean) {
val currentFragment = supportFragmentManager.fragments.lastOrNull {
it.isVisible
}
?: //this.onBackPressedDispatcher.onBackPressed()
return
/*
if (tvActivity == null) {
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}*/
// No fucked animations leaving the player :)
when {
isInPlayer -> {
supportFragmentManager.beginTransaction()
//.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit)
.remove(currentFragment)
.commitAllowingStateLoss()
}
isInExpandedView && !isInResults -> {
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,//R.anim.enter_from_right,
R.anim.exit_anim,//R.anim.exit_to_right,
R.anim.pop_enter,
R.anim.pop_exit
)
.remove(currentFragment)
.commitAllowingStateLoss()
}
else -> {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
.remove(currentFragment)
.commitAllowingStateLoss()
}
}
}*/
fun Context.getStatusBarHeight(): Int {
if (isTvSettings()) {
@ -541,14 +496,27 @@ object UIHelper {
}
fun Activity.changeStatusBarState(hide: Boolean): Int {
@Suppress("DEPRECATION")
return if (hide) {
window?.setFlags(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else {
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
0
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.show(WindowInsets.Type.statusBars())
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
this.getStatusBarHeight()
}
}
@ -556,13 +524,20 @@ object UIHelper {
// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
fun Activity.showSystemUI() {
window.decorView.systemUiVisibility =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (window.insetsController != null) {
window!!.insetsController?.show(WindowInsetsCompat.Type.systemBars())
}
} else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
}
changeStatusBarState(isEmulatorSettings())
// window.clearFlags(View.KEEP_SCREEN_ON)
}
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {

View file

@ -41,18 +41,37 @@
android:layout_marginStart="10dp"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/resultview_preview_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/textColor"
android:textSize="16sp"
android:layout_gravity="start|center_vertical"
android:textStyle="bold"
android:layout_marginEnd="25dp"
tools:text="The Perfect Run">
</TextView>
<ImageView
android:id="@+id/resultview_preview_favorite"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="5dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:elevation="10dp"
android:nextFocusDown="@id/resultview_preview_bookmark"
android:src="@drawable/ic_baseline_favorite_border_24"
app:tint="?attr/textColor" />
</FrameLayout>
<com.lagradost.cloudstream3.widget.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -123,6 +142,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/resultview_preview_bookmark"
android:layout_weight="1"
android:nextFocusUp="@id/resultview_preview_favorite"
android:nextFocusRight="@id/resultview_preview_more_info"
tools:visibility="visible"
@ -136,6 +156,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/resultview_preview_more_info"
android:layout_weight="1"
android:nextFocusUp="@id/resultview_preview_favorite"
android:nextFocusLeft="@id/resultview_preview_bookmark"
tools:visibility="visible"

View file

@ -159,6 +159,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="40dp">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/library_random"
style="@style/ExtendedFloatingActionButton"
android:layout_gravity="bottom|start"
android:text="@string/home_random"
android:textColor="?attr/textColor"
android:visibility="gone"
app:icon="@drawable/ic_baseline_play_arrow_24"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/sort_fab"

View file

@ -190,6 +190,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="40dp">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/library_random"
style="@style/ExtendedFloatingActionButton"
android:layout_gravity="bottom|start"
android:text="@string/home_random"
android:textColor="?attr/textColor"
android:visibility="gone"
app:icon="@drawable/ic_baseline_play_arrow_24"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/sort_fab"

View file

@ -171,6 +171,7 @@
<string name="sort_close">Close</string>
<string name="sort_clear">Clear</string>
<string name="sort_save">Save</string>
<string name="copyTitle">Title copied!</string>
<string name="player_speed">Player Speed</string>
<string name="subtitles_settings">Subtitle Settings</string>
<string name="subs_text_color">Text Color</string>
@ -435,7 +436,7 @@
<string name="pref_category_ui_features">Features</string>
<string name="category_general">General</string>
<string name="random_button_settings">Random Button</string>
<string name="random_button_settings_desc">Show random button on Homepage</string>
<string name="random_button_settings_desc">Show random button on Homepage and Library</string>
<string name="provider_lang_settings">Provider languages</string>
<string name="app_layout">App Layout</string>
<string name="preferred_media_settings">Preferred media</string>