forked from recloudstream/cloudstream
download stuff
This commit is contained in:
parent
062d7feff9
commit
57828527aa
11 changed files with 281 additions and 65 deletions
|
@ -50,18 +50,22 @@ object UIHelper {
|
|||
val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density)
|
||||
|
||||
fun Activity.checkWrite(): Boolean {
|
||||
return (ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
return (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
== PackageManager.PERMISSION_GRANTED)
|
||||
}
|
||||
|
||||
fun Activity.requestRW() {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
),
|
||||
1337)
|
||||
1337
|
||||
)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
|
@ -141,6 +145,7 @@ object UIHelper {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var currentAudioFocusRequest: AudioFocusRequest? = null
|
||||
private var currentAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
|
||||
var onAudioFocusEvent = Event<Boolean>()
|
||||
|
@ -258,6 +263,7 @@ object UIHelper {
|
|||
)
|
||||
// window.addFlags(View.KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
fun FragmentActivity.popCurrentPage() {
|
||||
val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
||||
it.isVisible
|
||||
|
@ -363,10 +369,11 @@ object UIHelper {
|
|||
}
|
||||
|
||||
|
||||
/**id, icon, stringRes */
|
||||
@SuppressLint("RestrictedApi")
|
||||
inline fun View.popupMenu(
|
||||
fun View.popupMenu(
|
||||
items: List<Triple<Int, Int, Int>>,
|
||||
noinline onMenuItemClick: MenuItem.() -> Unit,
|
||||
onMenuItemClick: MenuItem.() -> Unit,
|
||||
): PopupMenu {
|
||||
val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
|
||||
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
||||
|
@ -386,9 +393,11 @@ object UIHelper {
|
|||
return popup
|
||||
}
|
||||
|
||||
inline fun View.popupMenuNoIcons(
|
||||
/**id, stringRes */
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun View.popupMenuNoIcons(
|
||||
items: List<Pair<Int, Int>>,
|
||||
noinline onMenuItemClick: MenuItem.() -> Unit,
|
||||
onMenuItemClick: MenuItem.() -> Unit,
|
||||
): PopupMenu {
|
||||
val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
|
||||
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
||||
|
@ -408,15 +417,17 @@ object UIHelper {
|
|||
return popup
|
||||
}
|
||||
|
||||
inline fun View.popupMenuNoIconsAndNoStringres(
|
||||
/**id, string */
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun View.popupMenuNoIconsAndNoStringres(
|
||||
items: List<Pair<Int, String>>,
|
||||
noinline onMenuItemClick: MenuItem.() -> Unit,
|
||||
onMenuItemClick: MenuItem.() -> Unit,
|
||||
): PopupMenu {
|
||||
val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
|
||||
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
||||
|
||||
items.forEach { (id, stringRes) ->
|
||||
popup.menu.add(0, id, 0, stringRes)
|
||||
items.forEach { (id, string) ->
|
||||
popup.menu.add(0, id, 0, string)
|
||||
}
|
||||
|
||||
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
|
||||
|
|
|
@ -1,19 +1,34 @@
|
|||
package com.lagradost.cloudstream3.ui.download
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.widget.ContentLoadingProgressBar
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadState
|
||||
import kotlinx.android.synthetic.main.download_child_episode.view.*
|
||||
|
||||
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
||||
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
||||
const val DOWNLOAD_ACTION_RESUME_DOWNLOAD = 2
|
||||
const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3
|
||||
|
||||
data class VisualDownloadChildCached(
|
||||
val currentBytes: Long,
|
||||
val totalBytes: Long,
|
||||
|
@ -56,6 +71,8 @@ class DownloadChildAdapter(
|
|||
private val extraInfo: TextView = itemView.download_child_episode_text_extra
|
||||
private val holder: CardView = itemView.download_child_episode_holder
|
||||
private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress
|
||||
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
|
||||
private val downloadImage: ImageView = itemView.download_child_episode_download
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(card: VisualDownloadChildCached) {
|
||||
|
@ -73,13 +90,91 @@ class DownloadChildAdapter(
|
|||
|
||||
title.text = d.name ?: "Episode ${d.episode}" //TODO FIX
|
||||
val totalMbString = "%.1f".format(card.totalBytes / 1000000f)
|
||||
val currentMbString = "%.1f".format(card.currentBytes / 1000000f)
|
||||
|
||||
extraInfo.text =
|
||||
var lastState: VideoDownloadManager.DownloadType? = null
|
||||
var currentBytes: Long = card.currentBytes
|
||||
|
||||
fun changeDownloadImage(state: VideoDownloadManager.DownloadType) {
|
||||
runOnMainThread {
|
||||
val img = when (state) {
|
||||
VideoDownloadManager.DownloadType.IsPaused -> R.drawable.ic_baseline_play_arrow_24
|
||||
VideoDownloadManager.DownloadType.IsDownloading -> R.drawable.netflix_pause
|
||||
else -> R.drawable.ic_baseline_delete_outline_24
|
||||
}
|
||||
downloadImage?.setImageResource(img)
|
||||
}
|
||||
}
|
||||
|
||||
fun fixDownloadedBytes(setCurrentBytes: Long, animate : Boolean) {
|
||||
currentBytes = setCurrentBytes
|
||||
runOnMainThread {
|
||||
val currentMbString = "%.1f".format(currentBytes / 1000000f)
|
||||
|
||||
extraInfo?.text =
|
||||
"${currentMbString}MB / ${totalMbString}MB"
|
||||
|
||||
progressBarDownload?.let { bar ->
|
||||
bar.max = (card.totalBytes / 1000).toInt()
|
||||
|
||||
if(animate) {
|
||||
val animation: ObjectAnimator = ObjectAnimator.ofInt(
|
||||
bar,
|
||||
"progress",
|
||||
bar.progress,
|
||||
(currentBytes / 1000).toInt()
|
||||
)
|
||||
animation.duration = 500
|
||||
animation.setAutoCancel(true)
|
||||
animation.interpolator = DecelerateInterpolator()
|
||||
animation.start()
|
||||
} else {
|
||||
bar.progress = (currentBytes / 1000).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fixDownloadedBytes(card.currentBytes, false)
|
||||
changeDownloadImage(getDownloadState(card.data.id))
|
||||
|
||||
VideoDownloadManager.downloadProgressEvent += { downloadData ->
|
||||
if (card.data.id == downloadData.first) {
|
||||
fixDownloadedBytes(downloadData.second, true)
|
||||
}
|
||||
}
|
||||
|
||||
VideoDownloadManager.downloadStatusEvent += { downloadData ->
|
||||
if (card.data.id == downloadData.first) {
|
||||
if (lastState != downloadData.second) { // TO PREVENT WASTING UI TIME
|
||||
lastState = downloadData.second
|
||||
changeDownloadImage(downloadData.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.setOnClickListener {
|
||||
clickCallback.invoke(DownloadClickEvent(0, d))
|
||||
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||
}
|
||||
|
||||
progressBarDownload.setOnClickListener {
|
||||
val list = arrayListOf(
|
||||
Pair(DOWNLOAD_ACTION_DELETE_FILE, R.string.popup_delete_file),
|
||||
)
|
||||
|
||||
// DON'T RESUME A DOWNLOADED FILE
|
||||
if (lastState != VideoDownloadManager.DownloadType.IsDone && (currentBytes * 100 / card.totalBytes < 98)) {
|
||||
list.add(
|
||||
if (lastState == VideoDownloadManager.DownloadType.IsDownloading)
|
||||
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
||||
else
|
||||
Pair(DOWNLOAD_ACTION_RESUME_DOWNLOAD, R.string.popup_resume_download)
|
||||
)
|
||||
}
|
||||
|
||||
it.popupMenuNoIcons(
|
||||
list
|
||||
) {
|
||||
clickCallback.invoke(DownloadClickEvent(itemId, d))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,8 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerData
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||
import com.lagradost.cloudstream3.ui.player.UriData
|
||||
import com.lagradost.cloudstream3.ui.result.getRealPosition
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||
|
@ -84,7 +82,33 @@ class DownloadChildFragment : Fragment() {
|
|||
DownloadChildAdapter(
|
||||
ArrayList(),
|
||||
) { click ->
|
||||
if (click.action == 0) { // TODO PLAY
|
||||
val id = click.data.id
|
||||
when (click.action) {
|
||||
DOWNLOAD_ACTION_DELETE_FILE -> {
|
||||
context?.let { ctx ->
|
||||
VideoDownloadManager.deleteFileAndUpdateSettings(ctx, id)
|
||||
}
|
||||
updateList(folder)
|
||||
}
|
||||
DOWNLOAD_ACTION_PAUSE_DOWNLOAD -> {
|
||||
VideoDownloadManager.downloadEvent.invoke(
|
||||
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Pause)
|
||||
)
|
||||
updateList(folder)
|
||||
}
|
||||
DOWNLOAD_ACTION_RESUME_DOWNLOAD -> {
|
||||
context?.let { ctx ->
|
||||
val pkg = VideoDownloadManager.getDownloadResumePackage(ctx, id)
|
||||
if(pkg != null) {
|
||||
VideoDownloadManager.downloadFromResume(ctx, pkg)
|
||||
} else {
|
||||
VideoDownloadManager.downloadEvent.invoke(
|
||||
Pair(click.data.id, VideoDownloadManager.DownloadActionType.Resume)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DOWNLOAD_ACTION_PLAY_FILE -> {
|
||||
val info =
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), click.data.id)
|
||||
?: return@DownloadChildAdapter
|
||||
|
@ -112,6 +136,7 @@ class DownloadChildFragment : Fragment() {
|
|||
.commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
download_child_list.adapter = adapter
|
||||
download_child_list.layoutManager = GridLayoutManager(context, 1)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -11,4 +13,10 @@ object Coroutines {
|
|||
work()
|
||||
}
|
||||
}
|
||||
fun runOnMainThread(work: (() -> Unit)) {
|
||||
val mainHandler = Handler(Looper.getMainLooper())
|
||||
mainHandler.post {
|
||||
work()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -166,6 +166,16 @@ object VideoDownloadManager {
|
|||
}
|
||||
}
|
||||
|
||||
/** Will return IsDone if not found or error */
|
||||
fun getDownloadState(id : Int) : DownloadType {
|
||||
return try {
|
||||
downloadStatus[id] ?: DownloadType.IsDone
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
DownloadType.IsDone
|
||||
}
|
||||
}
|
||||
|
||||
private val cachedBitmaps = hashMapOf<String, Bitmap>()
|
||||
private fun Context.getImageBitmapFromUrl(url: String): Bitmap? {
|
||||
if (cachedBitmaps.containsKey(url)) {
|
||||
|
@ -532,6 +542,7 @@ object VideoDownloadManager {
|
|||
try {
|
||||
downloadStatus[ep.id] = type
|
||||
downloadStatusEvent.invoke(Pair(ep.id, type))
|
||||
downloadProgressEvent.invoke(Pair(ep.id, bytesDownloaded))
|
||||
} catch (e: Exception) {
|
||||
// IDK MIGHT ERROR
|
||||
}
|
||||
|
@ -584,7 +595,7 @@ object VideoDownloadManager {
|
|||
count = connectionInputStream.read(buffer)
|
||||
if (count < 0) break
|
||||
bytesDownloaded += count
|
||||
downloadProgressEvent.invoke(Pair(id, bytesDownloaded))
|
||||
// downloadProgressEvent.invoke(Pair(id, bytesDownloaded)) // Updates too much for any UI to keep up with
|
||||
while (isPaused) {
|
||||
sleep(100)
|
||||
if (isStopped) {
|
||||
|
@ -621,6 +632,7 @@ object VideoDownloadManager {
|
|||
deleteFile()
|
||||
}
|
||||
else -> {
|
||||
downloadProgressEvent.invoke(Pair(id, bytesDownloaded))
|
||||
isDone = true
|
||||
updateNotification()
|
||||
SUCCESS_DOWNLOAD_DONE
|
||||
|
@ -741,6 +753,10 @@ object VideoDownloadManager {
|
|||
downloadQueue.addLast(pkg)
|
||||
downloadCheck(context)
|
||||
if (setKey) saveQueue(context)
|
||||
} else {
|
||||
downloadEvent.invoke(
|
||||
Pair(pkg.item.ep.id, DownloadActionType.Resume)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
11
app/src/main/res/drawable/circle_shape.xml
Normal file
11
app/src/main/res/drawable/circle_shape.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="ring"
|
||||
android:innerRadiusRatio="2.5"
|
||||
android:thickness="2dp"
|
||||
android:useLevel="false">
|
||||
|
||||
<solid android:color="#CCC" />
|
||||
|
||||
</shape>
|
18
app/src/main/res/drawable/circular_progress_bar.xml
Normal file
18
app/src/main/res/drawable/circular_progress_bar.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromDegrees="270"
|
||||
android:toDegrees="270">
|
||||
<shape
|
||||
android:innerRadiusRatio="2.5"
|
||||
android:shape="ring"
|
||||
android:thickness="2dp"
|
||||
android:useLevel="true"><!-- this line fixes the issue for lollipop api 21 -->
|
||||
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="?attr/colorPrimary"
|
||||
android:startColor="?attr/colorPrimary"
|
||||
android:type="sweep"
|
||||
android:useLevel="false" />
|
||||
</shape>
|
||||
</rotate>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8,9h8v10L8,19L8,9zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
|
||||
</vector>
|
|
@ -25,6 +25,7 @@
|
|||
</androidx.core.widget.ContentLoadingProgressBar>
|
||||
<GridLayout android:layout_width="match_parent" android:layout_height="match_parent">
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
|
@ -56,18 +57,39 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
</TextView>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<FrameLayout
|
||||
android:layout_gravity="end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:id="@+id/download_child_episode_progress_downloaded"
|
||||
android:indeterminate="false"
|
||||
android:progressDrawable="@drawable/circular_progress_bar"
|
||||
android:background="@drawable/circle_shape"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:max="100"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:progress="0"
|
||||
android:visibility="visible"
|
||||
/>
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:visibility="visible"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="2dp"
|
||||
android:layout_width="30dp"
|
||||
android:id="@+id/download_child_episode_download"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:src="@drawable/netflix_download"
|
||||
android:src="@drawable/ic_baseline_play_arrow_24"
|
||||
android:contentDescription="@string/download_descript"/>
|
||||
</FrameLayout>
|
||||
</GridLayout>
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -101,7 +101,7 @@
|
|||
|
||||
</ImageView>-->
|
||||
<TextView
|
||||
android:text="Dub"
|
||||
android:text="@string/app_dubbed_text"
|
||||
android:id="@+id/text_is_dub"
|
||||
android:textColor="@color/textColor"
|
||||
android:paddingRight="10dp"
|
||||
|
@ -117,7 +117,7 @@
|
|||
</TextView>
|
||||
<TextView
|
||||
android:id="@+id/text_is_sub"
|
||||
android:text="Sub"
|
||||
android:text="@string/app_subbed_text"
|
||||
android:layout_gravity="end"
|
||||
android:textColor="@color/textColor"
|
||||
android:paddingRight="10dp"
|
||||
|
|
|
@ -42,4 +42,9 @@
|
|||
<string name="error_loading_links">Error Loading Links</string>
|
||||
<string name="download_storage_text">Internal Storage</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="app_dubbed_text">Dub</string>
|
||||
<string name="app_subbed_text">Sub</string>
|
||||
<string name="popup_delete_file">Delete File</string>
|
||||
<string name="popup_resume_download">Resume Download</string>
|
||||
<string name="popup_pause_download">Pause Download</string>
|
||||
</resources>
|
Loading…
Reference in a new issue