Compare commits

...

6 Commits

Author SHA1 Message Date
IndusAryan acfb0b9422
Merge f89aad3e95 into 004c481a5e 2024-04-27 18:11:36 +02:00
KingLucius 004c481a5e
feat(ui): Episode Air date & Upcoming countdown (#1058) 2024-04-27 18:11:22 +02:00
b4byhuey e2946cad6b
Added Vidguard Extractor (#1053) 2024-04-27 18:00:40 +02:00
IndusAryan f89aad3e95 Merge remote-tracking branch 'origin/master' into dialog2
# Conflicts:
#	app/src/main/res/values/strings.xml
2024-04-14 11:55:37 +05:30
IndusAryan aff24843a0 fix import 2024-04-03 19:33:10 +05:30
IndusAryan 71b61733e4 new dialog on add repo and redirection 2024-04-03 18:43:51 +05:30
18 changed files with 258 additions and 48 deletions

View File

@ -134,7 +134,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.BiometricAuthenticator import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
@ -294,8 +294,7 @@ var app = Requests(responseParser = object : ResponseParser {
defaultHeaders = mapOf("user-agent" to USER_AGENT) defaultHeaders = mapOf("user-agent" to USER_AGENT)
} }
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
BiometricAuthenticator.BiometricAuthCallback {
companion object { companion object {
const val TAG = "MAINACT" const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false const val ANIMATED_OUTLINE: Boolean = false

View File

@ -0,0 +1,101 @@
package com.lagradost.cloudstream3.extractors
import android.util.Log
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities
import org.mozilla.javascript.Context
import org.mozilla.javascript.NativeJSON
import org.mozilla.javascript.NativeObject
import org.mozilla.javascript.Scriptable
import java.util.Base64
open class Vidguardto : ExtractorApi() {
override val name = "Vidguard"
override val mainUrl = "https://vidguard.to"
override val requiresReferer = false
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val res = app.get(url)
val resc = res.document.select("script:containsData(eval)").firstOrNull()?.data()
resc?.let {
val jsonStr2 = AppUtils.parseJson<SvgObject>(runJS2(it))
val watchlink = sigDecode(jsonStr2.stream)
callback.invoke(
ExtractorLink(
this.name,
name,
watchlink,
this.mainUrl,
Qualities.Unknown.value,
INFER_TYPE
)
)
}
}
private fun sigDecode(url: String): String {
val sig = url.split("sig=")[1].split("&")[0]
var t = ""
for (v in sig.chunked(2)) {
val byteValue = Integer.parseInt(v, 16) xor 2
t += byteValue.toChar()
}
val padding = when (t.length % 4) {
2 -> "=="
3 -> "="
else -> ""
}
val decoded = Base64.getDecoder().decode((t + padding).toByteArray(Charsets.UTF_8))
t = String(decoded).dropLast(5).reversed()
val charArray = t.toCharArray()
for (i in 0 until charArray.size - 1 step 2) {
val temp = charArray[i]
charArray[i] = charArray[i + 1]
charArray[i + 1] = temp
}
val modifiedSig = String(charArray).dropLast(5)
return url.replace(sig, modifiedSig)
}
private fun runJS2(hideMyHtmlContent: String): String {
Log.d("runJS", "start")
val rhino = Context.enter()
rhino.initSafeStandardObjects()
rhino.optimizationLevel = -1
val scope: Scriptable = rhino.initSafeStandardObjects()
scope.put("window", scope, scope)
var result = ""
try {
Log.d("runJS", "Executing JavaScript: $hideMyHtmlContent")
rhino.evaluateString(scope, hideMyHtmlContent, "JavaScript", 1, null)
val svgObject = scope.get("svg", scope)
result = if (svgObject is NativeObject) {
NativeJSON.stringify(Context.getCurrentContext(), scope, svgObject, null, null).toString()
} else {
Context.toString(svgObject)
}
Log.d("runJS", "Result: $result")
} catch (e: Exception) {
Log.e("runJS", "Error executing JavaScript", e)
} finally {
Context.exit()
}
return result
}
data class SvgObject(
val stream: String,
val hash: String
)
}

View File

@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.BiometricAuthenticator import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
@ -33,7 +34,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.BiometricAuthCallback { class AccountSelectActivity : AppCompatActivity(), BiometricCallback {
lateinit var viewModel: AccountViewModel lateinit var viewModel: AccountViewModel

View File

@ -9,9 +9,11 @@ import androidx.core.view.isVisible
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding
import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
@ -23,6 +25,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.* import java.util.*
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
@ -104,7 +108,7 @@ class EpisodeAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
val item = getItem(position) val item = getItem(position)
return if (item.poster.isNullOrBlank()) 0 else 1 return if (item.poster.isNullOrBlank() && item.description.isNullOrBlank()) 0 else 1
} }
@ -260,6 +264,33 @@ class EpisodeAdapter(
} }
} }
if (card.airDate != null) {
val isUpcoming = unixTimeMS < card.airDate
if (isUpcoming) {
episodePlayIcon.isVisible = false
episodeUpcomingIcon.isVisible = !episodePoster.isVisible
episodeDate.setText(
txt(
R.string.episode_upcoming_format,
secondsToReadable(card.airDate.minus(unixTimeMS).div(1000).toInt(), "")
)
)
} else {
episodeUpcomingIcon.isVisible = false
val formattedAirDate = SimpleDateFormat.getDateInstance(
DateFormat.LONG,
Locale.getDefault()
).apply {
}.format(Date(card.airDate))
episodeDate.setText(txt(formattedAirDate))
}
} else {
episodeDate.isVisible = false
}
if (isLayout(EMULATOR or PHONE)) { if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener { episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
@ -271,6 +302,7 @@ class EpisodeAdapter(
} }
} }
} }
itemView.setOnClickListener { itemView.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
} }

View File

@ -50,6 +50,7 @@ data class ResultEpisode(
val videoWatchState: VideoWatchState, val videoWatchState: VideoWatchState,
/** Sum of all previous season episode counts + episode */ /** Sum of all previous season episode counts + episode */
val totalEpisodeIndex: Int? = null, val totalEpisodeIndex: Int? = null,
val airDate: Long? = null,
) )
fun ResultEpisode.getRealPosition(): Long { fun ResultEpisode.getRealPosition(): Long {
@ -85,6 +86,7 @@ fun buildResultEpisode(
tvType: TvType, tvType: TvType,
parentId: Int, parentId: Int,
totalEpisodeIndex: Int? = null, totalEpisodeIndex: Int? = null,
airDate: Long? = null,
): ResultEpisode { ): ResultEpisode {
val posDur = getViewPos(id) val posDur = getViewPos(id)
val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
@ -107,7 +109,8 @@ fun buildResultEpisode(
tvType, tvType,
parentId, parentId,
videoWatchState, videoWatchState,
totalEpisodeIndex totalEpisodeIndex,
airDate,
) )
} }

View File

@ -2277,7 +2277,8 @@ class ResultViewModel2 : ViewModel() {
fillers.getOrDefault(episode, false), fillers.getOrDefault(episode, false),
loadResponse.type, loadResponse.type,
mainId, mainId,
totalIndex totalIndex,
airDate = i.date
) )
val season = eps.seasonIndex ?: 0 val season = eps.seasonIndex ?: 0
@ -2326,7 +2327,8 @@ class ResultViewModel2 : ViewModel() {
null, null,
loadResponse.type, loadResponse.type,
mainId, mainId,
totalIndex totalIndex,
airDate = episode.date
) )
val season = ep.seasonIndex ?: 0 val season = ep.seasonIndex ?: 0

View File

@ -40,7 +40,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setTool
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.BackupUtils import com.lagradost.cloudstream3.utils.BackupUtils
import com.lagradost.cloudstream3.utils.BiometricAuthenticator import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.authCallback import com.lagradost.cloudstream3.utils.BiometricAuthenticator.authCallback
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
@ -53,7 +53,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
class SettingsAccount : PreferenceFragmentCompat(), BiometricAuthenticator.BiometricAuthCallback { class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback {
companion object { companion object {
/** Used by nginx plugin too */ /** Used by nginx plugin too */
fun showLoginInfo( fun showLoginInfo(

View File

@ -33,7 +33,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog import com.lagradost.cloudstream3.utils.AppUtils.addRepositoryDialog
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
@ -273,10 +273,8 @@ class ExtensionsFragment : Fragment() {
if (plugins.isNullOrEmpty()) { if (plugins.isNullOrEmpty()) {
showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG)
} else { } else {
this@ExtensionsFragment.activity?.downloadAllPluginsDialog( this@ExtensionsFragment.activity?.addRepositoryDialog(
url, fixedName, true)
fixedName
)
} }
} }
} }

View File

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding
@ -70,6 +71,8 @@ class PluginsFragment : Fragment() {
val name = arguments?.getString(PLUGINS_BUNDLE_NAME) val name = arguments?.getString(PLUGINS_BUNDLE_NAME)
val url = arguments?.getString(PLUGINS_BUNDLE_URL) val url = arguments?.getString(PLUGINS_BUNDLE_URL)
val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true
// download all extensions button
val downloadAllButton = binding?.settingsToolbar?.menu?.findItem(R.id.download_all)
if (url == null || name == null) { if (url == null || name == null) {
activity?.onBackPressedDispatcher?.onBackPressed() activity?.onBackPressedDispatcher?.onBackPressed()
@ -171,7 +174,7 @@ class PluginsFragment : Fragment() {
if (isLocal) { if (isLocal) {
// No download button and no categories on local // No download button and no categories on local
binding?.settingsToolbar?.menu?.findItem(R.id.download_all)?.isVisible = false downloadAllButton?.isVisible = false
binding?.settingsToolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false binding?.settingsToolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false
pluginViewModel.updatePluginListLocal() pluginViewModel.updatePluginListLocal()
@ -179,6 +182,10 @@ class PluginsFragment : Fragment() {
} else { } else {
pluginViewModel.updatePluginList(context, url) pluginViewModel.updatePluginList(context, url)
binding?.tvtypesChipsScroll?.root?.isVisible = true binding?.tvtypesChipsScroll?.root?.isVisible = true
// not needed for users but may be useful for devs
downloadAllButton?.isVisible = BuildConfig.DEBUG
bindChips( bindChips(
binding?.tvtypesChipsScroll?.tvtypesChips, binding?.tvtypesChipsScroll?.tvtypesChips,

View File

@ -62,6 +62,7 @@ import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WebviewFragment import com.lagradost.cloudstream3.ui.WebviewFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.settings.Globals import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsFragment
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -386,7 +387,7 @@ object AppUtils {
) )
} }
afterRepositoryLoadedEvent.invoke(true) afterRepositoryLoadedEvent.invoke(true)
downloadAllPluginsDialog(url, repo.name) addRepositoryDialog(repo.name, false)
} }
} }
@ -429,25 +430,37 @@ object AppUtils {
} }
} }
fun Activity.addRepositoryDialog(repositoryName: String, isExtensionsFragment: Boolean) {
val message = String.format(resources.getString(
R.string.download_all_plugins_from_repo), repositoryName)
val repos = RepositoryManager.getRepositories()
fun Activity.downloadAllPluginsDialog(repositoryUrl: String, repositoryName: String) { // navigate to newly added repository on pressing OK
runOnUiThread { fun openAddedRepo() {
val context = this
val builder: AlertDialog.Builder = AlertDialog.Builder(this) // don't redirect if user is adding from add repo button
builder.setTitle( if (!isExtensionsFragment && repos.isNotEmpty()) {
repositoryName normalSafeApiCall { navigate(
) R.id.navigation_home_to_navigation_settings_plugins,
builder.setMessage( PluginsFragment.newInstance(
R.string.download_all_plugins_from_repo repositoryName,
) repos.last().url,
builder.apply { false))
setPositiveButton(R.string.download) { _, _ ->
downloadAll(context, repositoryUrl, null)
} }
setNegativeButton(R.string.no) { _, _ -> }
} }
builder.show().setDefaultFocus() }
runOnUiThread {
val builder : AlertDialog.Builder = AlertDialog.Builder(this)
builder.apply {
setTitle(repositoryName)
setMessage(message)
setPositiveButton(R.string.ok) { _, _ ->
openAddedRepo()
}
setCancelable(false)
show().setDefaultFocus()
}
} }
} }

View File

@ -26,7 +26,7 @@ object BiometricAuthenticator {
private var biometricManager: BiometricManager? = null private var biometricManager: BiometricManager? = null
var biometricPrompt: BiometricPrompt? = null var biometricPrompt: BiometricPrompt? = null
var promptInfo: BiometricPrompt.PromptInfo? = null var promptInfo: BiometricPrompt.PromptInfo? = null
var authCallback: BiometricAuthCallback? = null // listen to authentication success var authCallback: BiometricCallback? = null // listen to authentication success
private fun initializeBiometrics(activity: Activity) { private fun initializeBiometrics(activity: Activity) {
val executor = ContextCompat.getMainExecutor(activity) val executor = ContextCompat.getMainExecutor(activity)
@ -141,14 +141,14 @@ object BiometricAuthenticator {
// function to start authentication in any fragment or activity // function to start authentication in any fragment or activity
fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) { fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
initializeBiometrics(activity) initializeBiometrics(activity)
authCallback = activity as? BiometricAuthCallback authCallback = activity as? BiometricCallback
if (isBiometricHardWareAvailable()) { if (isBiometricHardWareAvailable()) {
authCallback = activity as? BiometricAuthCallback authCallback = activity as? BiometricCallback
authenticationDialog(activity, title, setDeviceCred) authenticationDialog(activity, title, setDeviceCred)
promptInfo?.let { biometricPrompt?.authenticate(it) } promptInfo?.let { biometricPrompt?.authenticate(it) }
} else { } else {
if (deviceHasPasswordPinLock(activity)) { if (deviceHasPasswordPinLock(activity)) {
authCallback = activity as? BiometricAuthCallback authCallback = activity as? BiometricCallback
authenticationDialog(activity, R.string.password_pin_authentication_title, true) authenticationDialog(activity, R.string.password_pin_authentication_title, true)
promptInfo?.let { biometricPrompt?.authenticate(it) } promptInfo?.let { biometricPrompt?.authenticate(it) }
@ -165,7 +165,7 @@ object BiometricAuthenticator {
} }
} }
interface BiometricAuthCallback { interface BiometricCallback {
fun onAuthenticationSuccess() fun onAuthenticationSuccess()
fun onAuthenticationError() fun onAuthenticationError()
} }

View File

@ -186,6 +186,7 @@ import com.lagradost.cloudstream3.extractors.VideoVard
import com.lagradost.cloudstream3.extractors.VideovardSX import com.lagradost.cloudstream3.extractors.VideovardSX
import com.lagradost.cloudstream3.extractors.Vidgomunime import com.lagradost.cloudstream3.extractors.Vidgomunime
import com.lagradost.cloudstream3.extractors.Vidgomunimesb import com.lagradost.cloudstream3.extractors.Vidgomunimesb
import com.lagradost.cloudstream3.extractors.Vidguardto
import com.lagradost.cloudstream3.extractors.VidhideExtractor import com.lagradost.cloudstream3.extractors.VidhideExtractor
import com.lagradost.cloudstream3.extractors.Vidmoly import com.lagradost.cloudstream3.extractors.Vidmoly
import com.lagradost.cloudstream3.extractors.Vidmolyme import com.lagradost.cloudstream3.extractors.Vidmolyme
@ -888,7 +889,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
StreamWishExtractor(), StreamWishExtractor(),
EmturbovidExtractor(), EmturbovidExtractor(),
Vtbe(), Vtbe(),
EPlayExtractor() EPlayExtractor(),
Vidguardto()
) )

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#9BA0A4"
android:pathData="M320,800h320v-120q0,-66 -47,-113t-113,-47q-66,0 -113,47t-47,113v120ZM480,440q66,0 113,-47t47,-113v-120L320,160v120q0,66 47,113t113,47ZM160,880v-80h80v-120q0,-61 28.5,-114.5T348,480q-51,-32 -79.5,-85.5T240,280v-120h-80v-80h640v80h-80v120q0,61 -28.5,114.5T612,480q51,32 79.5,85.5T720,680v120h80v80L160,880ZM480,800ZM480,160Z"/>
</vector>

View File

@ -43,14 +43,26 @@
android:foreground="?android:attr/selectableItemBackgroundBorderless" android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:nextFocusRight="@id/download_button" android:nextFocusRight="@id/download_button"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@drawable/example_poster" /> tools:src="@drawable/example_poster"
tools:visibility="invisible"/>
<ImageView <ImageView
android:id="@+id/episode_play_icon"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"
android:layout_gravity="center" android:layout_gravity="center"
android:contentDescription="@string/play_episode" android:contentDescription="@string/play_episode"
android:src="@drawable/play_button" /> android:src="@drawable/play_button"
tools:visibility="invisible"/>
<ImageView
android:id="@+id/episode_upcoming_icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:src="@drawable/hourglass_24"
android:visibility="gone"
tools:visibility="visible" />
<androidx.core.widget.ContentLoadingProgressBar <androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/episode_progress" android:id="@+id/episode_progress"
@ -100,6 +112,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor" android:textColor="?attr/grayTextColor"
tools:text="Rated: 8.8" /> tools:text="Rated: 8.8" />
<TextView
android:id="@+id/episode_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/grayTextColor"
tools:text="15 Apr 2024" />
</LinearLayout> </LinearLayout>
<com.lagradost.cloudstream3.ui.download.button.PieFetchButton <com.lagradost.cloudstream3.ui.download.button.PieFetchButton

View File

@ -21,5 +21,6 @@
android:id="@+id/download_all" android:id="@+id/download_all"
android:icon="@drawable/netflix_download" android:icon="@drawable/netflix_download"
android:title="@string/batch_download" android:title="@string/batch_download"
app:showAsAction="collapseActionView|ifRoom" /> app:showAsAction="collapseActionView|ifRoom"
android:visible="false"/>
</menu> </menu>

View File

@ -273,6 +273,27 @@
app:exitAnim="@anim/exit_anim" app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim" app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" /> app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/navigation_home_to_navigation_settings_plugins"
app:destination="@id/navigation_settings_plugins"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim">
<argument
android:name="name"
android:defaultValue="@null"
app:argType="string" />
<argument
android:name="url"
android:defaultValue="@null"
app:argType="string" />
<argument
android:name="isLocal"
android:defaultValue="false"
app:argType="boolean" />
</action>
</fragment> </fragment>
<fragment <fragment

View File

@ -292,6 +292,7 @@
<string name="episodes">Episodes</string> <string name="episodes">Episodes</string>
<string name="episodes_range">%1$d-%2$d</string> <string name="episodes_range">%1$d-%2$d</string>
<string name="episode_format" formatted="true">%1$d %2$s</string> <string name="episode_format" formatted="true">%1$d %2$s</string>
<string name="episode_upcoming_format" formatted="true">Upcoming in %s</string>
<string name="season_short">S</string> <string name="season_short">S</string>
<string name="episode_short">E</string> <string name="episode_short">E</string>
<string name="no_episodes_found">No Episodes found</string> <string name="no_episodes_found">No Episodes found</string>
@ -348,6 +349,9 @@
<string name="live_singular">Livestream</string> <string name="live_singular">Livestream</string>
<string name="nsfw_singular">NSFW</string> <string name="nsfw_singular">NSFW</string>
<string name="other_singular">Video</string> <string name="other_singular">Video</string>
<string name="music_singlar">Music</string>
<string name="audio_book_singular">Audio Book</string>
<string name="custom_media_singluar">Media</string>
<string name="source_error">Source error</string> <string name="source_error">Source error</string>
<string name="remote_error">Remote error</string> <string name="remote_error">Remote error</string>
<string name="render_error">Renderer error</string> <string name="render_error">Renderer error</string>
@ -606,7 +610,8 @@
<string name="view_public_repositories_button">View community repositories</string> <string name="view_public_repositories_button">View community repositories</string>
<string name="view_public_repositories_button_short">Public list</string> <string name="view_public_repositories_button_short">Public list</string>
<string name="uppercase_all_subtitles">Uppercase all subtitles</string> <string name="uppercase_all_subtitles">Uppercase all subtitles</string>
<string name="download_all_plugins_from_repo">Download all plugins from this repository?</string> <string name="download_all_plugins_from_repo">%1$s has been added. You can install your desired extensions from 𝗦𝗲𝘁𝘁𝗶𝗻𝗴𝘀 > 𝗘𝘅𝘁𝗲𝗻𝘀𝗶𝗼𝗻𝘀 > %1$s.
CloudStream 3 does not takes any responsibility for using third-party extensions neither provides support for them.</string>
<string name="single_plugin_disabled" formatted="true">%s (Disabled)</string> <string name="single_plugin_disabled" formatted="true">%s (Disabled)</string>
<string name="tracks">Tracks</string> <string name="tracks">Tracks</string>
<string name="audio_tracks">Audio tracks</string> <string name="audio_tracks">Audio tracks</string>
@ -762,10 +767,7 @@
<string name="password_pin_authentication_title">Password/PIN Authentication</string> <string name="password_pin_authentication_title">Password/PIN Authentication</string>
<string name="biometric_unsupported">Biometric authentication is not supported on this device</string> <string name="biometric_unsupported">Biometric authentication is not supported on this device</string>
<string name="biometric_setting_summary">Unlock the app with Fingerprint, Face ID, PIN, Pattern and Password.</string> <string name="biometric_setting_summary">Unlock the app with Fingerprint, Face ID, PIN, Pattern and Password.</string>
<string name="biometric_prompt_description">This screen was closed due to multiple failed attempts. Please restart the application.</string> <string name="biometric_prompt_description">After a few failed attempts, the prompt will close. Simply restart the app to try again.</string>
<string name="biometric_warning">Your CloudStream data has been backed up now. Although the possibility of this is very low, all devices can behave differently. In the rare case, that you get locked out from accessing the app, clear the app data completely and restore from a backup. We are very sorry for any inconvenience arising from this.</string> <string name="biometric_warning">Your CloudStream data has been backed up now. Although the possibility of this is very low, all devices can behave differently. In the rare case, that you get locked out from accessing the app, clear the app data completely and restore from a backup. We are very sorry for any inconvenience arising from this.</string>
<string name="music_singlar">Music</string>
<string name="audio_book_singular">Audio Book</string>
<string name="custom_media_singluar">Media</string>
<string name="reset_btn">Reset</string> <string name="reset_btn">Reset</string>
</resources> </resources>

View File

@ -1,6 +1,6 @@
#Fri Apr 30 17:11:15 CEST 2021 #Fri Apr 30 17:11:15 CEST 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME