mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'master' into lib
This commit is contained in:
commit
c16ba812e5
20 changed files with 369 additions and 235 deletions
.github/ISSUE_TEMPLATE
app
build.gradle.kts
src/main
java/com/lagradost/cloudstream3
DownloaderTestImpl.kt
ui
download
player
utils
res
library
4
.github/ISSUE_TEMPLATE/application-bug.yml
vendored
4
.github/ISSUE_TEMPLATE/application-bug.yml
vendored
|
@ -80,13 +80,13 @@ body:
|
|||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
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.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I have updated the app to pre-release version **[Latest](https://github.com/recloudstream/cloudstream/releases)**.
|
||||
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.
|
||||
required: true
|
||||
|
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -2,7 +2,7 @@ blank_issues_enabled: false
|
|||
contact_links:
|
||||
- name: Request a new provider or report bug with an existing provider
|
||||
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
|
||||
url: https://discord.gg/5Hus6fM
|
||||
about: Join our discord for faster support on smaller issues.
|
||||
|
|
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
|
@ -27,9 +27,7 @@ body:
|
|||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
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.
|
||||
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
|
||||
required: true
|
|
@ -14,7 +14,6 @@ plugins {
|
|||
|
||||
val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
|
||||
val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
|
||||
var isLibraryDebug = false
|
||||
|
||||
fun String.execute() = ByteArrayOutputStream().use { baot ->
|
||||
if (project.exec {
|
||||
|
@ -105,7 +104,6 @@ android {
|
|||
)
|
||||
}
|
||||
debug {
|
||||
isLibraryDebug = true
|
||||
isDebuggable = true
|
||||
applicationIdSuffix = ".debug"
|
||||
proguardFiles(
|
||||
|
@ -236,7 +234,14 @@ dependencies {
|
|||
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3
|
|||
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||
import org.schabi.newpipe.extractor.downloader.Request
|
||||
import org.schabi.newpipe.extractor.downloader.Response
|
||||
|
@ -18,7 +19,7 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
|
|||
val dataToSend: ByteArray? = request.dataToSend()
|
||||
var requestBody: RequestBody? = null
|
||||
if (dataToSend != null) {
|
||||
requestBody = RequestBody.create(null, dataToSend)
|
||||
requestBody = dataToSend.toRequestBody(null, 0, dataToSend.size)
|
||||
}
|
||||
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
|
||||
.method(httpMethod, requestBody).url(url)
|
||||
|
|
|
@ -15,7 +15,9 @@ import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull
|
||||
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.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
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_LONG_CLICK = 5
|
||||
|
||||
const val DOWNLOAD_ACTION_GO_TO_CHILD = 0
|
||||
const val DOWNLOAD_ACTION_LOAD_RESULT = 1
|
||||
|
||||
abstract class VisualDownloadCached(
|
||||
open val currentBytes: Long,
|
||||
open val totalBytes: Long,
|
||||
|
@ -93,110 +98,128 @@ class DownloadAdapter(
|
|||
private val mediaClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(card: VisualDownloadCached?) {
|
||||
when (binding) {
|
||||
is DownloadHeaderEpisodeBinding -> binding.apply {
|
||||
if (card == null || card !is VisualDownloadHeaderCached) return@apply
|
||||
val d = card.data
|
||||
is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadHeaderCached)
|
||||
is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadChildCached)
|
||||
}
|
||||
}
|
||||
|
||||
downloadHeaderPoster.apply {
|
||||
setImage(d.poster)
|
||||
setOnClickListener {
|
||||
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
|
||||
}
|
||||
}
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindHeader(card: VisualDownloadHeaderCached?) {
|
||||
if (binding !is DownloadHeaderEpisodeBinding) return
|
||||
card ?: return
|
||||
val d = card.data
|
||||
|
||||
downloadHeaderTitle.text = d.name
|
||||
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
|
||||
|
||||
if (card.child != null) {
|
||||
downloadHeaderGotoChild.isVisible = false
|
||||
|
||||
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, mediaClickCallback)
|
||||
downloadButton.isVisible = true
|
||||
|
||||
episodeHolder.setOnClickListener {
|
||||
mediaClickCallback.invoke(
|
||||
DownloadClickEvent(
|
||||
DOWNLOAD_ACTION_PLAY_FILE,
|
||||
card.child
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
downloadButton.isVisible = false
|
||||
downloadHeaderGotoChild.isVisible = true
|
||||
|
||||
try {
|
||||
downloadHeaderInfo.text =
|
||||
downloadHeaderInfo.context.getString(R.string.extra_info_format)
|
||||
.format(
|
||||
card.totalDownloads,
|
||||
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(
|
||||
R.string.episode
|
||||
) else downloadHeaderInfo.context.getString(
|
||||
R.string.episodes
|
||||
),
|
||||
mbString
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
// You probably formatted incorrectly
|
||||
downloadHeaderInfo.text = "Error"
|
||||
logError(t)
|
||||
}
|
||||
|
||||
episodeHolder.setOnClickListener {
|
||||
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
|
||||
}
|
||||
binding.apply {
|
||||
downloadHeaderPoster.apply {
|
||||
setImage(d.poster)
|
||||
setOnClickListener {
|
||||
clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_LOAD_RESULT, d))
|
||||
}
|
||||
}
|
||||
|
||||
is DownloadChildEpisodeBinding -> binding.apply {
|
||||
if (card == null || card !is VisualDownloadChildCached) return@apply
|
||||
val d = card.data
|
||||
downloadHeaderTitle.text = d.name
|
||||
val formattedSizeString = formatShortFileSize(itemView.context, card.totalBytes)
|
||||
|
||||
val posDur = DataStoreHelper.getViewPos(d.id)
|
||||
downloadChildEpisodeProgress.apply {
|
||||
if (posDur != null) {
|
||||
val visualPos = posDur.fixVisual()
|
||||
max = (visualPos.duration / 1000).toInt()
|
||||
progress = (visualPos.position / 1000).toInt()
|
||||
isVisible = true
|
||||
} else isVisible = false
|
||||
if (card.child != null) {
|
||||
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.isVisible = true
|
||||
|
||||
episodeHolder.setOnClickListener {
|
||||
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
||||
}
|
||||
} else {
|
||||
downloadButton.isVisible = false
|
||||
downloadHeaderGotoChild.isVisible = true
|
||||
|
||||
try {
|
||||
downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format)
|
||||
.format(
|
||||
card.totalDownloads,
|
||||
downloadHeaderInfo.context.resources.getQuantityString(
|
||||
R.plurals.episodes,
|
||||
card.totalDownloads
|
||||
),
|
||||
formattedSizeString
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// You probably formatted incorrectly
|
||||
downloadHeaderInfo.text = "Error"
|
||||
logError(e)
|
||||
}
|
||||
|
||||
downloadButton.setDefaultClickListener(card.data, downloadChildEpisodeTextExtra, mediaClickCallback)
|
||||
|
||||
downloadChildEpisodeText.apply {
|
||||
text = context.getNameFull(d.name, d.episode, d.season)
|
||||
isSelected = true // Needed for text repeating
|
||||
episodeHolder.setOnClickListener {
|
||||
clickCallback.invoke(DownloadHeaderClickEvent(DOWNLOAD_ACTION_GO_TO_CHILD, d))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadChildEpisodeHolder.setOnClickListener {
|
||||
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||
private fun bindChild(card: VisualDownloadChildCached?) {
|
||||
if (binding !is DownloadChildEpisodeBinding) return
|
||||
card ?: return
|
||||
val d = card.data
|
||||
|
||||
binding.apply {
|
||||
val posDur = getViewPos(d.id)
|
||||
downloadChildEpisodeProgress.apply {
|
||||
isVisible = posDur != null
|
||||
posDur?.let {
|
||||
val visualPos = it.fixVisual()
|
||||
max = (visualPos.duration / 1000).toInt()
|
||||
progress = (visualPos.position / 1000).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
text = context.getNameFull(d.name, d.episode, d.season)
|
||||
isSelected = true // Needed for text repeating
|
||||
}
|
||||
|
||||
downloadChildEpisodeHolder.setOnClickListener {
|
||||
mediaClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = when (viewType) {
|
||||
VIEW_TYPE_HEADER -> {
|
||||
DownloadHeaderEpisodeBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
}
|
||||
VIEW_TYPE_CHILD -> {
|
||||
DownloadChildEpisodeBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
}
|
||||
VIEW_TYPE_HEADER -> DownloadHeaderEpisodeBinding.inflate(inflater, parent, false)
|
||||
VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false)
|
||||
else -> throw IllegalArgumentException("Invalid view type")
|
||||
}
|
||||
return DownloadViewHolder(binding, clickCallback, mediaClickCallback)
|
||||
|
@ -207,8 +230,11 @@ class DownloadAdapter(
|
|||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
val card = getItem(position)
|
||||
return if (card is VisualDownloadChildCached) VIEW_TYPE_CHILD else VIEW_TYPE_HEADER
|
||||
return when (getItem(position)) {
|
||||
is VisualDownloadChildCached -> VIEW_TYPE_CHILD
|
||||
is VisualDownloadHeaderCached -> VIEW_TYPE_HEADER
|
||||
else -> throw IllegalArgumentException("Invalid data type at position $position")
|
||||
}
|
||||
}
|
||||
|
||||
class DiffCallback : DiffUtil.ItemCallback<VisualDownloadCached>() {
|
||||
|
|
|
@ -35,6 +35,7 @@ class DownloadChildFragment : Fragment() {
|
|||
|
||||
override fun onDestroyView() {
|
||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||
downloadDeleteEventListener = null
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package com.lagradost.cloudstream3.ui.download
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter.formatShortFileSize
|
||||
|
@ -12,6 +15,7 @@ import android.view.ViewGroup
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
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.GeneratorPlayer
|
||||
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.setLinearListLayout
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
|
@ -60,16 +65,8 @@ class DownloadFragment : Fragment() {
|
|||
this.layoutParams = param
|
||||
}
|
||||
|
||||
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
||||
main {
|
||||
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(list)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
downloadDeleteEventListener?.let {
|
||||
VideoDownloadManager.downloadDeleteEvent -= it
|
||||
}
|
||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||
downloadDeleteEventListener = null
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
|
@ -95,12 +92,10 @@ class DownloadFragment : Fragment() {
|
|||
hideKeyboard()
|
||||
binding?.downloadStorageAppbar?.setAppBarNoScrollFlagsOnTV()
|
||||
|
||||
observe(downloadsViewModel.noDownloadsText) {
|
||||
binding?.textNoDownloads?.text = it
|
||||
}
|
||||
observe(downloadsViewModel.headerCards) {
|
||||
setList(it)
|
||||
(binding?.downloadList?.adapter as? DownloadAdapter)?.submitList(it)
|
||||
binding?.downloadLoading?.isVisible = false
|
||||
binding?.textNoDownloads?.isVisible = it.isEmpty()
|
||||
}
|
||||
observe(downloadsViewModel.availableBytes) {
|
||||
updateStorageInfo(view.context, it, R.string.free_storage, binding?.downloadFreeTxt, binding?.downloadFree)
|
||||
|
@ -137,9 +132,15 @@ class DownloadFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
|
||||
binding?.downloadStreamButton?.apply {
|
||||
isGone = isLayout(TV)
|
||||
setOnClickListener { showStreamInputDialog(it.context) }
|
||||
binding?.apply {
|
||||
openLocalVideoButton.apply {
|
||||
isGone = isLayout(TV)
|
||||
setOnClickListener { openLocalVideo() }
|
||||
}
|
||||
downloadStreamButton.apply {
|
||||
isGone = isLayout(TV)
|
||||
setOnClickListener { showStreamInputDialog(it.context) }
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
|
@ -153,7 +154,7 @@ class DownloadFragment : Fragment() {
|
|||
|
||||
private fun handleItemClick(click: DownloadHeaderClickEvent) {
|
||||
when (click.action) {
|
||||
0 -> {
|
||||
DOWNLOAD_ACTION_GO_TO_CHILD -> {
|
||||
if (!click.data.type.isMovieType()) {
|
||||
val folder = DataStore.getFolderName(DOWNLOAD_EPISODE_CACHE, click.data.id.toString())
|
||||
activity?.navigate(
|
||||
|
@ -162,7 +163,7 @@ class DownloadFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
DOWNLOAD_ACTION_LOAD_RESULT -> {
|
||||
(activity as AppCompatActivity?)?.loadResult(click.data.url, click.data.apiName)
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +190,22 @@ class DownloadFragment : Fragment() {
|
|||
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) {
|
||||
val dialog = Dialog(context, R.style.AlertDialogCustom)
|
||||
val binding = StreamInputBinding.inflate(dialog.layoutInflater)
|
||||
|
@ -247,4 +264,13 @@ class DownloadFragment : Fragment() {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,17 +16,11 @@ import com.lagradost.cloudstream3.utils.DataStore.getFolderName
|
|||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||
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.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DownloadViewModel : ViewModel() {
|
||||
private val _noDownloadsText = MutableLiveData<String>().apply {
|
||||
value = ""
|
||||
}
|
||||
val noDownloadsText: LiveData<String> = _noDownloadsText
|
||||
|
||||
private val _headerCards =
|
||||
MutableLiveData<List<VisualDownloadHeaderCached>>().apply { listOf<VisualDownloadHeaderCached>() }
|
||||
val headerCards: LiveData<List<VisualDownloadHeaderCached>> = _headerCards
|
||||
|
@ -43,8 +37,8 @@ class DownloadViewModel : ViewModel() {
|
|||
|
||||
fun updateList(context: Context) = viewModelScope.launchSafe {
|
||||
val children = withContext(Dispatchers.IO) {
|
||||
val headers = context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
||||
headers.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
|
||||
context.getKeys(DOWNLOAD_EPISODE_CACHE)
|
||||
.mapNotNull { context.getKey<VideoDownloadHelper.DownloadEpisodeCached>(it) }
|
||||
.distinctBy { it.id } // Remove duplicates
|
||||
}
|
||||
|
||||
|
@ -57,10 +51,10 @@ class DownloadViewModel : ViewModel() {
|
|||
|
||||
// Gets all children downloads
|
||||
withContext(Dispatchers.IO) {
|
||||
for (c in children) {
|
||||
val childFile = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, c.id) ?: continue
|
||||
children.forEach { c ->
|
||||
val childFile = getDownloadFileInfoAndUpdateSettings(context, c.id) ?: return@forEach
|
||||
|
||||
if (childFile.fileLength <= 1) continue
|
||||
if (childFile.fileLength <= 1) return@forEach
|
||||
val len = childFile.totalBytes
|
||||
val flen = childFile.fileLength
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.download.button
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.Formatter
|
||||
import android.text.format.Formatter.formatShortFileSize
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
|
@ -9,6 +9,8 @@ import androidx.annotation.LayoutRes
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
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
|
||||
|
||||
typealias DownloadStatusTell = VideoDownloadManager.DownloadType
|
||||
|
@ -34,7 +36,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
lateinit var progressBar: ContentLoadingProgressBar
|
||||
var progressText: TextView? = null
|
||||
|
||||
/*val gid: String? get() = sessionIdToGid[persistentId]
|
||||
/* val gid: String? get() = sessionIdToGid[persistentId]
|
||||
|
||||
// used for resuming data
|
||||
var _lastRequestOverride: UriRequest? = null
|
||||
|
@ -44,7 +46,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
_lastRequestOverride = value
|
||||
}
|
||||
|
||||
var files: List<AbstractClient.JsonFile> = emptyList()*/
|
||||
var files: List<AbstractClient.JsonFile> = emptyList() */
|
||||
protected var isZeroBytes: Boolean = true
|
||||
|
||||
fun inflate(@LayoutRes layout: Int) {
|
||||
|
@ -55,9 +57,12 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
resetViewData()
|
||||
}
|
||||
|
||||
var doSetProgress = true
|
||||
|
||||
open fun resetViewData() {
|
||||
// lastRequest = null
|
||||
isZeroBytes = true
|
||||
doSetProgress = true
|
||||
persistentId = null
|
||||
}
|
||||
|
||||
|
@ -68,37 +73,45 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
persistentId = id
|
||||
currentMetaData.id = id
|
||||
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)?.let { savedData ->
|
||||
val downloadedBytes = savedData.fileLength
|
||||
val totalBytes = savedData.totalBytes
|
||||
if (!doSetProgress) return
|
||||
|
||||
/*lastRequest = savedData.uriRequest
|
||||
files = savedData.files
|
||||
ioSafe {
|
||||
val savedData = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)
|
||||
|
||||
var totalBytes: Long = 0
|
||||
var downloadedBytes: Long = 0
|
||||
for (file in savedData.files) {
|
||||
downloadedBytes += file.completedLength
|
||||
totalBytes += file.length
|
||||
}*/
|
||||
setProgress(downloadedBytes, totalBytes)
|
||||
// some extra padding for just in case
|
||||
val status = VideoDownloadManager.downloadStatus[id]
|
||||
?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) DownloadStatusTell.IsDone else DownloadStatusTell.IsPaused
|
||||
currentMetaData.apply {
|
||||
this.id = id
|
||||
this.downloadedLength = downloadedBytes
|
||||
this.totalLength = totalBytes
|
||||
this.status = status
|
||||
mainWork {
|
||||
if (savedData != null) {
|
||||
val downloadedBytes = savedData.fileLength
|
||||
val totalBytes = savedData.totalBytes
|
||||
|
||||
setProgress(downloadedBytes, totalBytes)
|
||||
applyMetaData(id, downloadedBytes, totalBytes)
|
||||
} else run { resetView() }
|
||||
}
|
||||
setStatus(status)
|
||||
} ?: run {
|
||||
resetView()
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun setStatus(status: VideoDownloadManager.DownloadType?)
|
||||
|
||||
fun getStatus(id:Int, downloadedBytes: Long, totalBytes: Long): DownloadStatusTell {
|
||||
// some extra padding for just in case
|
||||
return VideoDownloadManager.downloadStatus[id]
|
||||
?: 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 {
|
||||
this.id = id
|
||||
this.downloadedLength = downloadedBytes
|
||||
this.totalLength = totalBytes
|
||||
this.status = status
|
||||
}
|
||||
setStatus(status)
|
||||
}
|
||||
|
||||
open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
|
||||
isZeroBytes = downloadedBytes == 0L
|
||||
progressBar.post {
|
||||
|
@ -124,13 +137,15 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
if (isZeroBytes) {
|
||||
progressText?.isVisible = false
|
||||
} else {
|
||||
progressText?.apply {
|
||||
val currentMbString = Formatter.formatShortFileSize(context, downloadedBytes)
|
||||
val totalMbString = Formatter.formatShortFileSize(context, totalBytes)
|
||||
text =
|
||||
//if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
|
||||
context?.getString(R.string.download_size_format)
|
||||
?.format(currentMbString, totalMbString)
|
||||
if (doSetProgress) {
|
||||
progressText?.apply {
|
||||
val currentFormattedSizeString = formatShortFileSize(context, downloadedBytes)
|
||||
val totalFormattedSizeString = formatShortFileSize(context, totalBytes)
|
||||
text =
|
||||
// if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
|
||||
context?.getString(R.string.download_size_format)
|
||||
?.format(currentFormattedSizeString, totalFormattedSizeString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,8 +182,8 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
|
||||
override fun onAttachedToWindow() {
|
||||
VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent
|
||||
//VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent
|
||||
//VideoDownloadManager.downloadEvent += ::downloadEvent
|
||||
// VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent
|
||||
// VideoDownloadManager.downloadEvent += ::downloadEvent
|
||||
VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent
|
||||
|
||||
val pid = persistentId
|
||||
|
@ -182,8 +197,8 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
|
||||
override fun onDetachedFromWindow() {
|
||||
VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent
|
||||
//VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent
|
||||
//VideoDownloadManager.downloadEvent -= ::downloadEvent
|
||||
// VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent
|
||||
// VideoDownloadManager.downloadEvent -= ::downloadEvent
|
||||
VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent
|
||||
|
||||
super.onDetachedFromWindow()
|
||||
|
@ -198,5 +213,4 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
* Get a clean slate again, might be useful in recyclerview?
|
||||
* */
|
||||
abstract fun resetView()
|
||||
|
||||
}
|
|
@ -13,7 +13,6 @@ import androidx.annotation.MainThread
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
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.KEY_RESUME_PACKAGES
|
||||
|
||||
|
||||
open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||
BaseFetchButton(context, attributeSet) {
|
||||
|
||||
|
@ -303,6 +301,7 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
setStatus(null)
|
||||
currentMetaData = DownloadMetadata(0, 0, 0, null)
|
||||
isZeroBytes = true
|
||||
doSetProgress = true
|
||||
progressBar.progress = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.lagradost.cloudstream3.ui.player
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
|
@ -12,10 +10,15 @@ import com.lagradost.cloudstream3.CommonActivity
|
|||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
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"
|
||||
|
||||
class DownloadedPlayerActivity : AppCompatActivity() {
|
||||
private val dTAG = "DownloadedPlayerAct"
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
CommonActivity.dispatchKeyEvent(this, event)?.let {
|
||||
return it
|
||||
|
@ -34,53 +37,18 @@ class DownloadedPlayerActivity : AppCompatActivity() {
|
|||
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?) {
|
||||
Log.i(DTAG, "onCreate")
|
||||
|
||||
CommonActivity.loadThemes(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
CommonActivity.loadThemes(this)
|
||||
CommonActivity.init(this)
|
||||
|
||||
setContentView(R.layout.empty_layout)
|
||||
Log.i(dTAG, "onCreate")
|
||||
|
||||
val data = intent.data
|
||||
|
||||
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)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
val cd = intent.clipData
|
||||
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
|
||||
if (item?.uri != null)
|
||||
playUri(item.uri)
|
||||
playUri(this, item.uri)
|
||||
else if (url != null)
|
||||
playLink(url)
|
||||
playLink(this, url)
|
||||
else if (data != null)
|
||||
playUri(data)
|
||||
playUri(this, data)
|
||||
else if (extraText != null)
|
||||
playLink(extraText)
|
||||
playLink(this, extraText)
|
||||
else {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
} else if (data?.scheme == "content") {
|
||||
playUri(data)
|
||||
playUri(this, data)
|
||||
} else {
|
||||
finish()
|
||||
return
|
||||
|
|
|
@ -3,9 +3,12 @@ package com.lagradost.cloudstream3.ui.player
|
|||
import android.net.Uri
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.amap
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import java.net.URI
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||
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(
|
||||
val uri: Uri,
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ import android.graphics.Color
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.TransactionTooLargeException
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
|
@ -475,7 +477,23 @@ object UIHelper {
|
|||
}
|
||||
|
||||
fun FragmentActivity.popCurrentPage() {
|
||||
this.onBackPressedDispatcher.onBackPressed()
|
||||
// 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()
|
||||
} 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 {
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
|
@ -29,7 +30,6 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.services.VideoDownloadService
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -225,10 +225,10 @@ object VideoDownloadManager {
|
|||
return cachedBitmaps[url]
|
||||
}
|
||||
|
||||
val bitmap = com.bumptech.glide.Glide.with(this)
|
||||
val bitmap = Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(GlideUrl(url) { headers ?: emptyMap() })
|
||||
.into(720, 720)
|
||||
.submit(720, 720)
|
||||
.get()
|
||||
|
||||
if (bitmap != null) {
|
||||
|
@ -1857,4 +1857,4 @@ object VideoDownloadManager {
|
|||
@JsonProperty("ep") val ep: DownloadEpisodeMetadata,
|
||||
@JsonProperty("links") val links: List<ExtractorLink>
|
||||
)
|
||||
}
|
||||
}
|
11
app/src/main/res/drawable/ic_network_stream.xml
Normal file
11
app/src/main/res/drawable/ic_network_stream.xml
Normal 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>
|
|
@ -143,17 +143,14 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text_no_downloads"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="30dp"
|
||||
android:text="@string/downloads_empty"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!--
|
||||
<ProgressBar
|
||||
|
@ -198,11 +195,30 @@
|
|||
</LinearLayout>
|
||||
</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
|
||||
android:id="@+id/download_stream_button"
|
||||
style="@style/ExtendedFloatingActionButton"
|
||||
android:text="@string/stream"
|
||||
android:textColor="?attr/textColor"
|
||||
app:icon="@drawable/netflix_play"
|
||||
tools:ignore="ContentDescription" />
|
||||
app:icon="@drawable/ic_network_stream"
|
||||
android:contentDescription="@string/stream" />
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -149,8 +149,10 @@
|
|||
<string name="download_canceled">Download Canceled</string>
|
||||
<string name="download_done">Download Done</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="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="links_reloaded_toast">Links Reloaded</string>
|
||||
<string name="download_storage_text">Internal Storage</string>
|
||||
|
@ -339,6 +341,10 @@
|
|||
<string name="livestreams">Livestreams</string>
|
||||
<string name="nsfw">NSFW</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-->
|
||||
<string name="movies_singular">Movie</string>
|
||||
<string name="tv_series_singular">Series</string>
|
||||
|
|
|
@ -47,6 +47,11 @@ buildkonfig {
|
|||
|
||||
defaultConfigs {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -74,4 +79,4 @@ publishing {
|
|||
groupId = "com.lagradost.api"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue