Merge branch 'master' into lib

This commit is contained in:
CranberrySoup 2024-07-04 17:45:40 +00:00 committed by GitHub
commit c16ba812e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 369 additions and 235 deletions

View file

@ -80,13 +80,13 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your issue will be closed if you haven't done these steps. description: Your issue will be closed if you haven't done these steps.
options: options:
- label: I am sure my issue is related to the app and **NOT some extension**.
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have updated the app to pre-release version **[Latest](https://github.com/recloudstream/cloudstream/releases)**. - label: I have updated the app to pre-release version **[Latest](https://github.com/recloudstream/cloudstream/releases)**.
required: true required: true
- label: If related to a provider, I have checked the site and it works, but not the app.
required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View file

@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links: contact_links:
- name: Request a new provider or report bug with an existing provider - name: Request a new provider or report bug with an existing provider
url: https://github.com/recloudstream url: https://github.com/recloudstream
about: Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord. about: EXTREMELY IMPORTANT - Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
- name: Discord - name: Discord
url: https://discord.gg/5Hus6fM url: https://discord.gg/5Hus6fM
about: Join our discord for faster support on smaller issues. about: Join our discord for faster support on smaller issues.

View file

@ -27,9 +27,7 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your issue will be closed if you haven't done these steps. description: Your issue will be closed if you haven't done these steps.
options: options:
- label: My suggestion is **NOT** about adding a new provider
required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true required: true
- label: I have written a short but informative title.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View file

@ -14,7 +14,6 @@ plugins {
val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/" val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first() val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
var isLibraryDebug = false
fun String.execute() = ByteArrayOutputStream().use { baot -> fun String.execute() = ByteArrayOutputStream().use { baot ->
if (project.exec { if (project.exec {
@ -105,7 +104,6 @@ android {
) )
} }
debug { debug {
isLibraryDebug = true
isDebuggable = true isDebuggable = true
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
proguardFiles( proguardFiles(
@ -236,7 +234,14 @@ dependencies {
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
implementation(project(":library") { implementation(project(":library") {
this.extra.set("isDebug", isLibraryDebug) // There does not seem to be a good way of getting the android flavor.
val isDebug = gradle.startParameter.taskRequests.any { task ->
task.args.any { arg ->
arg.contains("debug", true)
}
}
this.extra.set("isDebug", isDebug)
}) })
} }

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.schabi.newpipe.extractor.downloader.Downloader import org.schabi.newpipe.extractor.downloader.Downloader
import org.schabi.newpipe.extractor.downloader.Request import org.schabi.newpipe.extractor.downloader.Request
import org.schabi.newpipe.extractor.downloader.Response import org.schabi.newpipe.extractor.downloader.Response
@ -18,7 +19,7 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
val dataToSend: ByteArray? = request.dataToSend() val dataToSend: ByteArray? = request.dataToSend()
var requestBody: RequestBody? = null var requestBody: RequestBody? = null
if (dataToSend != null) { if (dataToSend != null) {
requestBody = RequestBody.create(null, dataToSend) requestBody = dataToSend.toRequestBody(null, 0, dataToSend.size)
} }
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
.method(httpMethod, requestBody).url(url) .method(httpMethod, requestBody).url(url)

View file

@ -15,7 +15,9 @@ import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
@ -26,6 +28,9 @@ const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
const val DOWNLOAD_ACTION_DOWNLOAD = 4 const val DOWNLOAD_ACTION_DOWNLOAD = 4
const val DOWNLOAD_ACTION_LONG_CLICK = 5 const val DOWNLOAD_ACTION_LONG_CLICK = 5
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
const val DOWNLOAD_ACTION_LOAD_RESULT = 1
abstract class VisualDownloadCached( abstract class VisualDownloadCached(
open val currentBytes: Long, open val currentBytes: Long,
open val totalBytes: Long, open val totalBytes: Long,
@ -93,80 +98,110 @@ class DownloadAdapter(
private val mediaClickCallback: (DownloadClickEvent) -> Unit, private val mediaClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(card: VisualDownloadCached?) { fun bind(card: VisualDownloadCached?) {
when (binding) { when (binding) {
is DownloadHeaderEpisodeBinding -> binding.apply { is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadHeaderCached)
if (card == null || card !is VisualDownloadHeaderCached) return@apply is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadChildCached)
}
}
@SuppressLint("SetTextI18n")
private fun bindHeader(card: VisualDownloadHeaderCached?) {
if (binding !is DownloadHeaderEpisodeBinding) return
card ?: return
val d = card.data val d = card.data
binding.apply {
downloadHeaderPoster.apply { downloadHeaderPoster.apply {
setImage(d.poster) setImage(d.poster)
setOnClickListener { setOnClickListener {
clickCallback.invoke(DownloadHeaderClickEvent(1, d)) clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_LOAD_RESULT, d))
} }
} }
downloadHeaderTitle.text = d.name downloadHeaderTitle.text = d.name
val mbString = formatShortFileSize(itemView.context, card.totalBytes) val formattedSizeString = formatShortFileSize(itemView.context, card.totalBytes)
if (card.child != null) { if (card.child != null) {
downloadHeaderGotoChild.isVisible = false downloadHeaderGotoChild.isVisible = false
val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes)
if (status == DownloadStatusTell.IsDone) {
// We do this here instead if we are finished downloading
// so that we can use the value from the view model
// rather than extra unneeded disk operations and to prevent a
// delay in updating download icon state.
downloadButton.setProgress(card.currentBytes, card.totalBytes)
downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes)
// We will let the view model handle this
downloadButton.doSetProgress = false
downloadHeaderInfo.text = formattedSizeString
} else downloadButton.doSetProgress = true
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback) downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
downloadButton.isVisible = true downloadButton.isVisible = true
episodeHolder.setOnClickListener { episodeHolder.setOnClickListener {
mediaClickCallback.invoke( mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
DownloadClickEvent(
DOWNLOAD_ACTION_PLAY_FILE,
card.child
)
)
} }
} else { } else {
downloadButton.isVisible = false downloadButton.isVisible = false
downloadHeaderGotoChild.isVisible = true downloadHeaderGotoChild.isVisible = true
try { try {
downloadHeaderInfo.text = downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format)
downloadHeaderInfo.context.getString(R.string.extra_info_format)
.format( .format(
card.totalDownloads, card.totalDownloads,
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString( downloadHeaderInfo.context.resources.getQuantityString(
R.string.episode R.plurals.episodes,
) else downloadHeaderInfo.context.getString( card.totalDownloads
R.string.episodes
), ),
mbString formattedSizeString
) )
} catch (t: Throwable) { } catch (e: Exception) {
// You probably formatted incorrectly // You probably formatted incorrectly
downloadHeaderInfo.text = "Error" downloadHeaderInfo.text = "Error"
logError(t) logError(e)
} }
episodeHolder.setOnClickListener { episodeHolder.setOnClickListener {
clickCallback.invoke(DownloadHeaderClickEvent(0, d)) clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_GO_TO_CHILD, d))
}
} }
} }
} }
is DownloadChildEpisodeBinding -> binding.apply { private fun bindChild(card: VisualDownloadChildCached?) {
if (card == null || card !is VisualDownloadChildCached) return@apply if (binding !is DownloadChildEpisodeBinding) return
card ?: return
val d = card.data val d = card.data
val posDur = DataStoreHelper.getViewPos(d.id) binding.apply {
val posDur = getViewPos(d.id)
downloadChildEpisodeProgress.apply { downloadChildEpisodeProgress.apply {
if (posDur != null) { isVisible = posDur != null
val visualPos = posDur.fixVisual() posDur?.let {
val visualPos = it.fixVisual()
max = (visualPos.duration / 1000).toInt() max = (visualPos.duration / 1000).toInt()
progress = (visualPos.position / 1000).toInt() progress = (visualPos.position / 1000).toInt()
isVisible = true }
} else isVisible = false
} }
downloadButton.setDefaultClickListener(card.data, downloadChildEpisodeTextExtra, mediaClickCallback) val status = downloadButton.getStatus(d.id, card.currentBytes, card.totalBytes)
if (status == DownloadStatusTell.IsDone) {
// We do this here instead if we are finished downloading
// so that we can use the value from the view model
// rather than extra unneeded disk operations and to prevent a
// delay in updating download icon state.
downloadButton.setProgress(card.currentBytes, card.totalBytes)
downloadButton.applyMetaData(d.id, card.currentBytes, card.totalBytes)
// We will let the view model handle this
downloadButton.doSetProgress = false
downloadChildEpisodeTextExtra.text = formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes)
} else downloadButton.doSetProgress = true
downloadButton.setDefaultClickListener(d, downloadChildEpisodeTextExtra, mediaClickCallback)
downloadButton.isVisible = true
downloadChildEpisodeText.apply { downloadChildEpisodeText.apply {
text = context.getNameFull(d.name, d.episode, d.season) text = context.getNameFull(d.name, d.episode, d.season)
@ -179,24 +214,12 @@ class DownloadAdapter(
} }
} }
} }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = when (viewType) { val binding = when (viewType) {
VIEW_TYPE_HEADER -> { VIEW_TYPE_HEADER -> DownloadHeaderEpisodeBinding.inflate(inflater, parent, false)
DownloadHeaderEpisodeBinding.inflate( VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
LayoutInflater.from(parent.context),
parent,
false
)
}
VIEW_TYPE_CHILD -> {
DownloadChildEpisodeBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
}
else -> throw IllegalArgumentException("Invalid view type") else -> throw IllegalArgumentException("Invalid view type")
} }
return DownloadViewHolder(binding, clickCallback, mediaClickCallback) return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
@ -207,8 +230,11 @@ class DownloadAdapter(
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
val card = getItem(position) return when (getItem(position)) {
return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER is VisualDownloadChildCached -> VIEW_TYPE_CHILD
is VisualDownloadHeaderCached -> VIEW_TYPE_HEADER
else -> throw IllegalArgumentException("Invalid data type at position $position")
}
} }
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() { class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {

View file

@ -35,6 +35,7 @@ class DownloadChildFragment : Fragment() {
override fun onDestroyView() { override fun onDestroyView() {
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it } downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
downloadDeleteEventListener = null
binding = null binding = null
super.onDestroyView() super.onDestroyView()
} }

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.download package com.lagradost.cloudstream3.ui.download
import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.format.Formatter.formatShortFileSize import android.text.format.Formatter.formatShortFileSize
@ -12,6 +15,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone import androidx.core.view.isGone
@ -30,6 +34,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownload
import com.lagradost.cloudstream3.ui.player.BasicLink import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator import com.lagradost.cloudstream3.ui.player.LinkGenerator
import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playUri
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
@ -60,16 +65,8 @@ class DownloadFragment : Fragment() {
this.layoutParams = param this.layoutParams = param
} }
private fun setList(list: List<VisualDownloadHeaderCached>) {
main {
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list)
}
}
override fun onDestroyView() { override fun onDestroyView() {
downloadDeleteEventListener?.let { downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
VideoDownloadManager.downloadDeleteEvent -= it
}
downloadDeleteEventListener = null downloadDeleteEventListener = null
binding = null binding = null
super.onDestroyView() super.onDestroyView()
@ -95,12 +92,10 @@ class DownloadFragment : Fragment() {
hideKeyboard() hideKeyboard()
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV() binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
observe(downloadsViewModel.noDownloadsText) {
binding?.textNoDownloads?.text = it
}
observe(downloadsViewModel.headerCards) { observe(downloadsViewModel.headerCards) {
setList(it) (binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(it)
binding?.downloadLoading?.isVisible = false binding?.downloadLoading?.isVisible = false
binding?.textNoDownloads?.isVisible = it.isEmpty()
} }
observe(downloadsViewModel.availableBytes) { observe(downloadsViewModel.availableBytes) {
updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree) updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree)
@ -137,10 +132,16 @@ class DownloadFragment : Fragment() {
) )
} }
binding?.downloadStreamButton?.apply { binding?.apply {
openLocalVideoButton.apply {
isGone = isLayout(TV)
setOnClickListener { openLocalVideo() }
}
downloadStreamButton.apply {
isGone = isLayout(TV) isGone = isLayout(TV)
setOnClickListener { showStreamInputDialog(it.context) } setOnClickListener { showStreamInputDialog(it.context) }
} }
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
@ -153,7 +154,7 @@ class DownloadFragment : Fragment() {
private fun handleItemClick(click: DownloadHeaderClickEvent) { private fun handleItemClick(click: DownloadHeaderClickEvent) {
when (click.action) { when (click.action) {
0 -> { DOWNLOAD_ACTION_GO_TO_CHILD -> {
if (!click.data.type.isMovieType()) { if (!click.data.type.isMovieType()) {
val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString()) val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString())
activity?.navigate( activity?.navigate(
@ -162,7 +163,7 @@ class DownloadFragment : Fragment() {
) )
} }
} }
1 -> { DOWNLOAD_ACTION_LOAD_RESULT -> {
(activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName) (activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName)
} }
} }
@ -189,6 +190,22 @@ class DownloadFragment : Fragment() {
view?.setLayoutWidth(bytes) view?.setLayoutWidth(bytes)
} }
private fun openLocalVideo() {
val intent = Intent()
.setAction(Intent.ACTION_GET_CONTENT)
.setType("video/*")
.addCategory(Intent.CATEGORY_OPENABLE)
.addFlags(FLAG_GRANT_READ_URI_PERMISSION) // Request temporary access
normalSafeApiCall {
videoResultLauncher.launch(
Intent.createChooser(
intent,
getString(R.string.open_local_video)
)
)
}
}
private fun showStreamInputDialog(context: Context) { private fun showStreamInputDialog(context: Context) {
val dialog = Dialog(context, R.style.AlertDialogCustom) val dialog = Dialog(context, R.style.AlertDialogCustom)
val binding = StreamInputBinding.inflate(dialog.layoutInflater) val binding = StreamInputBinding.inflate(dialog.layoutInflater)
@ -247,4 +264,13 @@ class DownloadFragment : Fragment() {
binding?.downloadStreamButton?.extend() binding?.downloadStreamButton?.extend()
} }
} }
// Open local video from files using content provider x safeFile
private val videoResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val selectedVideoUri = result?.data?.data ?: return@registerForActivityResult
playUri(activity ?: return@registerForActivityResult, selectedVideoUri)
}
} }

View file

@ -16,17 +16,11 @@ import com.lagradost.cloudstream3.utils.DataStore.getFolderName
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.getKeys
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadFileInfoAndUpdateSettings
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DownloadViewModel : ViewModel() { class DownloadViewModel : ViewModel() {
private val _noDownloadsText = MutableLiveData<String>().apply {
value = ""
}
val noDownloadsText: LiveData<String> = _noDownloadsText
private val _headerCards = private val _headerCards =
MutableLiveData<List<VisualDownloadHeaderCached>>().apply { listOf<VisualDownloadHeaderCached>() } MutableLiveData<List<VisualDownloadHeaderCached>>().apply { listOf<VisualDownloadHeaderCached>() }
val headerCards: LiveData<List<VisualDownloadHeaderCached>> = _headerCards val headerCards: LiveData<List<VisualDownloadHeaderCached>> = _headerCards
@ -43,8 +37,8 @@ class DownloadViewModel : ViewModel() {
fun updateList(context: Context) = viewModelScope.launchSafe { fun updateList(context: Context) = viewModelScope.launchSafe {
val children = withContext(Dispatchers.IO) { val children = withContext(Dispatchers.IO) {
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE) context.getKeys(DOWNLOAD_EPISODE_CACHE)
headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) } .mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
.distinctBy { it.id } // Remove duplicates .distinctBy { it.id } // Remove duplicates
} }
@ -57,10 +51,10 @@ class DownloadViewModel : ViewModel() {
// Gets all children downloads // Gets all children downloads
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
for (c in children) { children.forEach { c ->
val childFile = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, c.id) ?: continue val childFile = getDownloadFileInfoAndUpdateSettings(context, c.id) ?: return@forEach
if (childFile.fileLength <= 1) continue if (childFile.fileLength <= 1) return@forEach
val len = childFile.totalBytes val len = childFile.totalBytes
val flen = childFile.fileLength val flen = childFile.fileLength

View file

@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.ui.download.button package com.lagradost.cloudstream3.ui.download.button
import android.content.Context import android.content.Context
import android.text.format.Formatter import android.text.format.Formatter.formatShortFileSize
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
@ -9,6 +9,8 @@ import androidx.annotation.LayoutRes
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.ContentLoadingProgressBar
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
typealias DownloadStatusTell = VideoDownloadManager.DownloadType typealias DownloadStatusTell = VideoDownloadManager.DownloadType
@ -34,7 +36,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
lateinit var progressBar: ContentLoadingProgressBar lateinit var progressBar: ContentLoadingProgressBar
var progressText: TextView? = null var progressText: TextView? = null
/*val gid: String? get() = sessionIdToGid[persistentId] /* val gid: String? get() = sessionIdToGid[persistentId]
// used for resuming data // used for resuming data
var _lastRequestOverride: UriRequest? = null var _lastRequestOverride: UriRequest? = null
@ -44,7 +46,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
_lastRequestOverride = value _lastRequestOverride = value
} }
var files: List<AbstractClient.JsonFile> = emptyList()*/ var files: List<AbstractClient.JsonFile> = emptyList() */
protected var isZeroBytes: Boolean = true protected var isZeroBytes: Boolean = true
fun inflate(@LayoutRes layout: Int) { fun inflate(@LayoutRes layout: Int) {
@ -55,9 +57,12 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
resetViewData() resetViewData()
} }
var doSetProgress = true
open fun resetViewData() { open fun resetViewData() {
// lastRequest = null // lastRequest = null
isZeroBytes = true isZeroBytes = true
doSetProgress = true
persistentId = null persistentId = null
} }
@ -68,23 +73,36 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
persistentId = id persistentId = id
currentMetaData.id = id currentMetaData.id = id
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)?.let { savedData -> if (!doSetProgress) return
ioSafe {
val savedData = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)
mainWork {
if (savedData != null) {
val downloadedBytes = savedData.fileLength val downloadedBytes = savedData.fileLength
val totalBytes = savedData.totalBytes val totalBytes = savedData.totalBytes
/*lastRequest = savedData.uriRequest
files = savedData.files
var totalBytes: Long = 0
var downloadedBytes: Long = 0
for (file in savedData.files) {
downloadedBytes += file.completedLength
totalBytes += file.length
}*/
setProgress(downloadedBytes, totalBytes) setProgress(downloadedBytes, totalBytes)
applyMetaData(id, downloadedBytes, totalBytes)
} else run { resetView() }
}
}
}
abstract fun setStatus(status: VideoDownloadManager.DownloadType?)
fun getStatus(id:Int, downloadedBytes: Long, totalBytes: Long): DownloadStatusTell {
// some extra padding for just in case // some extra padding for just in case
val status = VideoDownloadManager.downloadStatus[id] return VideoDownloadManager.downloadStatus[id]
?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) DownloadStatusTell.IsDone else DownloadStatusTell.IsPaused ?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) {
DownloadStatusTell.IsDone
} else DownloadStatusTell.IsPaused
}
fun applyMetaData(id:Int, downloadedBytes: Long, totalBytes: Long) {
val status = getStatus(id, downloadedBytes, totalBytes)
currentMetaData.apply { currentMetaData.apply {
this.id = id this.id = id
this.downloadedLength = downloadedBytes this.downloadedLength = downloadedBytes
@ -92,12 +110,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
this.status = status this.status = status
} }
setStatus(status) setStatus(status)
} ?: run {
resetView()
} }
}
abstract fun setStatus(status: VideoDownloadManager.DownloadType?)
open fun setProgress(downloadedBytes: Long, totalBytes: Long) { open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
isZeroBytes = downloadedBytes == 0L isZeroBytes = downloadedBytes == 0L
@ -124,13 +137,15 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
if (isZeroBytes) { if (isZeroBytes) {
progressText?.isVisible = false progressText?.isVisible = false
} else { } else {
if (doSetProgress) {
progressText?.apply { progressText?.apply {
val currentMbString = Formatter.formatShortFileSize(context, downloadedBytes) val currentFormattedSizeString = formatShortFileSize(context, downloadedBytes)
val totalMbString = Formatter.formatShortFileSize(context, totalBytes) val totalFormattedSizeString = formatShortFileSize(context, totalBytes)
text = text =
//if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else // if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
context?.getString(R.string.download_size_format) context?.getString(R.string.download_size_format)
?.format(currentMbString, totalMbString) ?.format(currentFormattedSizeString, totalFormattedSizeString)
}
} }
} }
@ -167,8 +182,8 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent
//VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent // VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent
//VideoDownloadManager.downloadEvent += ::downloadEvent // VideoDownloadManager.downloadEvent += ::downloadEvent
VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent
val pid = persistentId val pid = persistentId
@ -182,8 +197,8 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent
//VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent // VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent
//VideoDownloadManager.downloadEvent -= ::downloadEvent // VideoDownloadManager.downloadEvent -= ::downloadEvent
VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent
super.onDetachedFromWindow() super.onDetachedFromWindow()
@ -198,5 +213,4 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
* Get a clean slate again, might be useful in recyclerview? * Get a clean slate again, might be useful in recyclerview?
* */ * */
abstract fun resetView() abstract fun resetView()
} }

View file

@ -13,7 +13,6 @@ import androidx.annotation.MainThread
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
@ -29,7 +28,6 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import com.lagradost.cloudstream3.utils.VideoDownloadManager.KEY_RESUME_PACKAGES import com.lagradost.cloudstream3.utils.VideoDownloadManager.KEY_RESUME_PACKAGES
open class PieFetchButton(context: Context, attributeSet: AttributeSet) : open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
BaseFetchButton(context, attributeSet) { BaseFetchButton(context, attributeSet) {
@ -303,6 +301,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
setStatus(null) setStatus(null)
currentMetaData = DownloadMetadata(0, 0, 0, null) currentMetaData = DownloadMetadata(0, 0, 0, null)
isZeroBytes = true isZeroBytes = true
doSetProgress = true
progressBar.progress = 0 progressBar.progress = 0
} }

View file

@ -1,8 +1,6 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import android.content.ContentUris
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
@ -12,10 +10,15 @@ import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.safefile.SafeFile import com.lagradost.safefile.SafeFile
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playLink
import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playUri
const val DTAG = "PlayerActivity" const val DTAG = "PlayerActivity"
class DownloadedPlayerActivity : AppCompatActivity() { class DownloadedPlayerActivity : AppCompatActivity() {
private val dTAG = "DownloadedPlayerAct"
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let { CommonActivity.dispatchKeyEvent(this, event)?.let {
return it return it
@ -34,53 +37,18 @@ class DownloadedPlayerActivity : AppCompatActivity() {
CommonActivity.onUserLeaveHint(this) CommonActivity.onUserLeaveHint(this)
} }
private fun playLink(url: String) {
this.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
LinkGenerator(
listOf(
BasicLink(url)
)
)
)
)
}
private fun playUri(uri: Uri) {
val name = SafeFile.fromUri(this, uri)?.name()
this.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
DownloadFileGenerator(
listOf(
ExtractorUri(
uri = uri,
name = name ?: getString(R.string.downloaded_file),
// well not the same as a normal id, but we take it as users may want to
// play downloaded files and save the location
id = kotlin.runCatching { ContentUris.parseId(uri) }.getOrNull()?.hashCode()
)
)
)
)
)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.i(DTAG, "onCreate")
CommonActivity.loadThemes(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
CommonActivity.loadThemes(this)
CommonActivity.init(this) CommonActivity.init(this)
setContentView(R.layout.empty_layout) setContentView(R.layout.empty_layout)
Log.i(dTAG, "onCreate")
val data = intent.data val data = intent.data
if (intent?.action == Intent.ACTION_SEND) { if (intent?.action == Intent.ACTION_SEND) {
val extraText = try { // I dont trust android val extraText = normalSafeApiCall { // I dont trust android
intent.getStringExtra(Intent.EXTRA_TEXT) intent.getStringExtra(Intent.EXTRA_TEXT)
} catch (e: Exception) {
null
} }
val cd = intent.clipData val cd = intent.clipData
val item = if (cd != null && cd.itemCount > 0) cd.getItemAt(0) else null val item = if (cd != null && cd.itemCount > 0) cd.getItemAt(0) else null
@ -88,19 +56,19 @@ class DownloadedPlayerActivity : AppCompatActivity() {
// idk what I am doing, just hope any of these work // idk what I am doing, just hope any of these work
if (item?.uri != null) if (item?.uri != null)
playUri(item.uri) playUri(this, item.uri)
else if (url != null) else if (url != null)
playLink(url) playLink(this, url)
else if (data != null) else if (data != null)
playUri(data) playUri(this, data)
else if (extraText != null) else if (extraText != null)
playLink(extraText) playLink(this, extraText)
else { else {
finish() finish()
return return
} }
} else if (data?.scheme == "content") { } else if (data?.scheme == "content") {
playUri(data) playUri(this, data)
} else { } else {
finish() finish()
return return

View file

@ -3,9 +3,12 @@ package com.lagradost.cloudstream3.ui.player
import android.net.Uri import android.net.Uri
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.ExtractorUri
import java.net.URI import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.loadExtractor
import com.lagradost.cloudstream3.utils.unshortenLinkSafe
data class ExtractorUri( data class ExtractorUri(
val uri: Uri, val uri: Uri,

View file

@ -0,0 +1,43 @@
package com.lagradost.cloudstream3.ui.player
import android.app.Activity
import android.content.ContentUris
import android.net.Uri
import androidx.core.content.ContextCompat.getString
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.safefile.SafeFile
object OfflinePlaybackHelper {
fun playLink(activity: Activity, url: String) {
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
LinkGenerator(
listOf(
BasicLink(url)
)
)
)
)
}
fun playUri(activity: Activity, uri: Uri) {
val name = SafeFile.fromUri(activity, uri)?.name()
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
DownloadFileGenerator(
listOf(
ExtractorUri(
uri = uri,
name = name ?: getString(activity, R.string.downloaded_file),
// well not the same as a normal id, but we take it as users may want to
// play downloaded files and save the location
id = kotlin.runCatching { ContentUris.parseId(uri) }.getOrNull()?.hashCode()
)
)
)
)
)
}
}

View file

@ -16,6 +16,8 @@ import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.TransactionTooLargeException import android.os.TransactionTooLargeException
import android.util.Log import android.util.Log
import android.view.* import android.view.*
@ -475,7 +477,23 @@ object UIHelper {
} }
fun FragmentActivity.popCurrentPage() { fun FragmentActivity.popCurrentPage() {
// Post the back press action to the main thread handler to ensure it executes
// after any currently pending UI updates or fragment transactions.
Handler(Looper.getMainLooper()).post {
// Check if the FragmentManager state is saved. If it is, we cannot perform
// fragment transactions safely because the state may be inconsistent.
if (!supportFragmentManager.isStateSaved) {
// If the state is not saved, it's safe to perform the back press action.
this.onBackPressedDispatcher.onBackPressed() this.onBackPressedDispatcher.onBackPressed()
} else {
// If the state is saved, retry the back press action after a slight delay.
// This gives the FragmentManager time to complete any ongoing state-saving
// operations or transactions, ensuring that we do not encounter an IllegalStateException.
Handler(Looper.getMainLooper()).postDelayed({
this.onBackPressedDispatcher.onBackPressed()
}, 100)
}
}
} }
fun Context.getStatusBarHeight(): Int { fun Context.getStatusBarHeight(): Int {

View file

@ -17,6 +17,7 @@ import androidx.work.Data
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
@ -29,7 +30,6 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.services.VideoDownloadService import com.lagradost.cloudstream3.services.VideoDownloadService
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
@ -225,10 +225,10 @@ object VideoDownloadManager {
return cachedBitmaps[url] return cachedBitmaps[url]
} }
val bitmap = com.bumptech.glide.Glide.with(this) val bitmap = Glide.with(this)
.asBitmap() .asBitmap()
.load(GlideUrl(url) { headers ?: emptyMap() }) .load(GlideUrl(url) { headers ?: emptyMap() })
.into(720, 720) .submit(720, 720)
.get() .get()
if (bitmap != null) { if (bitmap != null) {

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/white"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z" />
</vector>

View file

@ -143,17 +143,14 @@
<TextView <TextView
android:id="@+id/text_no_downloads" android:id="@+id/text_no_downloads"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_gravity="center"
android:layout_marginTop="8dp" android:layout_margin="30dp"
android:layout_marginEnd="8dp" android:text="@string/downloads_empty"
android:textAlignment="center" android:gravity="center"
android:textSize="20sp" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" tools:visibility="visible" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- <!--
<ProgressBar <ProgressBar
@ -198,11 +195,30 @@
</LinearLayout> </LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout> </com.facebook.shimmer.ShimmerFrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/open_local_video_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/floatingActionButtonSmallStyle"
android:backgroundTint="?attr/primaryGrayBackground"
android:src="@drawable/netflix_play"
android:layout_marginEnd="16dp"
android:tooltipText="@string/open_local_video"
android:layout_gravity="bottom|end"
android:contentDescription="@string/open_local_video" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_stream_button" android:id="@+id/download_stream_button"
style="@style/ExtendedFloatingActionButton" style="@style/ExtendedFloatingActionButton"
android:text="@string/stream" android:text="@string/stream"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
app:icon="@drawable/netflix_play" app:icon="@drawable/ic_network_stream"
tools:ignore="ContentDescription" /> android:contentDescription="@string/stream" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -149,8 +149,10 @@
<string name="download_canceled">Download Canceled</string> <string name="download_canceled">Download Canceled</string>
<string name="download_done">Download Done</string> <string name="download_done">Download Done</string>
<string name="download_format" translatable="false">%s - %s</string> <string name="download_format" translatable="false">%s - %s</string>
<string name="downloads_empty">There are currently no downloads.</string>
<string name="update_started">Update Started</string> <string name="update_started">Update Started</string>
<string name="stream">Network stream</string> <string name="stream">Network stream</string>
<string name="open_local_video">Open local video</string>
<string name="error_loading_links_toast">Error Loading Links</string> <string name="error_loading_links_toast">Error Loading Links</string>
<string name="links_reloaded_toast">Links Reloaded</string> <string name="links_reloaded_toast">Links Reloaded</string>
<string name="download_storage_text">Internal Storage</string> <string name="download_storage_text">Internal Storage</string>
@ -339,6 +341,10 @@
<string name="livestreams">Livestreams</string> <string name="livestreams">Livestreams</string>
<string name="nsfw">NSFW</string> <string name="nsfw">NSFW</string>
<string name="others">Others</string> <string name="others">Others</string>
<plurals name="episodes" translatable="false">
<item quantity="one">@string/episode</item>
<item quantity="other">@string/episodes</item>
</plurals>
<!--singular--> <!--singular-->
<string name="movies_singular">Movie</string> <string name="movies_singular">Movie</string>
<string name="tv_series_singular">Series</string> <string name="tv_series_singular">Series</string>

View file

@ -47,6 +47,11 @@ buildkonfig {
defaultConfigs { defaultConfigs {
val isDebug = kotlin.runCatching { extra.get("isDebug") }.getOrNull() == true val isDebug = kotlin.runCatching { extra.get("isDebug") }.getOrNull() == true
if (isDebug) {
logger.quiet("Compiling library with debug flag")
} else {
logger.quiet("Compiling library with release flag")
}
buildConfigField(FieldSpec.Type.BOOLEAN, "DEBUG", isDebug.toString()) buildConfigField(FieldSpec.Type.BOOLEAN, "DEBUG", isDebug.toString())
} }
} }