download stuff

This commit is contained in:
LagradOst 2021-07-24 17:13:21 +02:00
parent 062d7feff9
commit 57828527aa
11 changed files with 281 additions and 65 deletions

View file

@ -50,18 +50,22 @@ object UIHelper {
val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density) val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density)
fun Activity.checkWrite(): Boolean { fun Activity.checkWrite(): Boolean {
return (ContextCompat.checkSelfPermission(this, return (ContextCompat.checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED) == PackageManager.PERMISSION_GRANTED)
} }
fun Activity.requestRW() { fun Activity.requestRW() {
ActivityCompat.requestPermissions(this, ActivityCompat.requestPermissions(
this,
arrayOf( arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
), ),
1337) 1337
)
} }
@ColorInt @ColorInt
@ -141,6 +145,7 @@ object UIHelper {
) )
} }
} }
private var currentAudioFocusRequest: AudioFocusRequest? = null private var currentAudioFocusRequest: AudioFocusRequest? = null
private var currentAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null private var currentAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
var onAudioFocusEvent = Event<Boolean>() var onAudioFocusEvent = Event<Boolean>()
@ -258,6 +263,7 @@ object UIHelper {
) )
// window.addFlags(View.KEEP_SCREEN_ON) // window.addFlags(View.KEEP_SCREEN_ON)
} }
fun FragmentActivity.popCurrentPage() { fun FragmentActivity.popCurrentPage() {
val currentFragment = supportFragmentManager.fragments.lastOrNull { val currentFragment = supportFragmentManager.fragments.lastOrNull {
it.isVisible it.isVisible
@ -363,10 +369,11 @@ object UIHelper {
} }
/**id, icon, stringRes */
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
inline fun View.popupMenu( fun View.popupMenu(
items: List<Triple<Int, Int, Int>>, items: List<Triple<Int, Int, Int>>,
noinline onMenuItemClick: MenuItem.() -> Unit, onMenuItemClick: MenuItem.() -> Unit,
): PopupMenu { ): PopupMenu {
val ctw = ContextThemeWrapper(context, R.style.PopupMenu) val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
@ -386,9 +393,11 @@ object UIHelper {
return popup return popup
} }
inline fun View.popupMenuNoIcons( /**id, stringRes */
@SuppressLint("RestrictedApi")
fun View.popupMenuNoIcons(
items: List<Pair<Int, Int>>, items: List<Pair<Int, Int>>,
noinline onMenuItemClick: MenuItem.() -> Unit, onMenuItemClick: MenuItem.() -> Unit,
): PopupMenu { ): PopupMenu {
val ctw = ContextThemeWrapper(context, R.style.PopupMenu) val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
@ -408,15 +417,17 @@ object UIHelper {
return popup return popup
} }
inline fun View.popupMenuNoIconsAndNoStringres( /**id, string */
@SuppressLint("RestrictedApi")
fun View.popupMenuNoIconsAndNoStringres(
items: List<Pair<Int, String>>, items: List<Pair<Int, String>>,
noinline onMenuItemClick: MenuItem.() -> Unit, onMenuItemClick: MenuItem.() -> Unit,
): PopupMenu { ): PopupMenu {
val ctw = ContextThemeWrapper(context, R.style.PopupMenu) val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
items.forEach { (id, stringRes) -> items.forEach { (id, string) ->
popup.menu.add(0, id, 0, stringRes) popup.menu.add(0, id, 0, string)
} }
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true) (popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)

View file

@ -1,19 +1,34 @@
package com.lagradost.cloudstream3.ui.download package com.lagradost.cloudstream3.ui.download
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R 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.fixVisual
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.VideoDownloadHelper 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.* 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( data class VisualDownloadChildCached(
val currentBytes: Long, val currentBytes: Long,
val totalBytes: Long, val totalBytes: Long,
@ -56,6 +71,8 @@ class DownloadChildAdapter(
private val extraInfo: TextView = itemView.download_child_episode_text_extra private val extraInfo: TextView = itemView.download_child_episode_text_extra
private val holder: CardView = itemView.download_child_episode_holder private val holder: CardView = itemView.download_child_episode_holder
private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress 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") @SuppressLint("SetTextI18n")
fun bind(card: VisualDownloadChildCached) { fun bind(card: VisualDownloadChildCached) {
@ -73,13 +90,91 @@ class DownloadChildAdapter(
title.text = d.name ?: "Episode ${d.episode}" //TODO FIX title.text = d.name ?: "Episode ${d.episode}" //TODO FIX
val totalMbString = "%.1f".format(card.totalBytes / 1000000f) 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" "${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 { 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))
}
} }
} }
} }

View file

@ -10,10 +10,8 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar 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.PlayerFragment
import com.lagradost.cloudstream3.ui.player.UriData 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.Coroutines.main
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
@ -84,7 +82,33 @@ class DownloadChildFragment : Fragment() {
DownloadChildAdapter( DownloadChildAdapter(
ArrayList(), ArrayList(),
) { click -> ) { 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 = val info =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), click.data.id) VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), click.data.id)
?: return@DownloadChildAdapter ?: return@DownloadChildAdapter
@ -112,6 +136,7 @@ class DownloadChildFragment : Fragment() {
.commit() .commit()
} }
} }
}
download_child_list.adapter = adapter download_child_list.adapter = adapter
download_child_list.layoutManager = GridLayoutManager(context, 1) download_child_list.layoutManager = GridLayoutManager(context, 1)

View file

@ -1,5 +1,7 @@
package com.lagradost.cloudstream3.utils package com.lagradost.cloudstream3.utils
import android.os.Handler
import android.os.Looper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -11,4 +13,10 @@ object Coroutines {
work() work()
} }
} }
fun runOnMainThread(work: (() -> Unit)) {
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post {
work()
}
}
} }

View file

@ -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 val cachedBitmaps = hashMapOf<String, Bitmap>()
private fun Context.getImageBitmapFromUrl(url: String): Bitmap? { private fun Context.getImageBitmapFromUrl(url: String): Bitmap? {
if (cachedBitmaps.containsKey(url)) { if (cachedBitmaps.containsKey(url)) {
@ -532,6 +542,7 @@ object VideoDownloadManager {
try { try {
downloadStatus[ep.id] = type downloadStatus[ep.id] = type
downloadStatusEvent.invoke(Pair(ep.id, type)) downloadStatusEvent.invoke(Pair(ep.id, type))
downloadProgressEvent.invoke(Pair(ep.id, bytesDownloaded))
} catch (e: Exception) { } catch (e: Exception) {
// IDK MIGHT ERROR // IDK MIGHT ERROR
} }
@ -584,7 +595,7 @@ object VideoDownloadManager {
count = connectionInputStream.read(buffer) count = connectionInputStream.read(buffer)
if (count < 0) break if (count < 0) break
bytesDownloaded += count bytesDownloaded += count
downloadProgressEvent.invoke(Pair(id, bytesDownloaded)) // downloadProgressEvent.invoke(Pair(id, bytesDownloaded)) // Updates too much for any UI to keep up with
while (isPaused) { while (isPaused) {
sleep(100) sleep(100)
if (isStopped) { if (isStopped) {
@ -621,6 +632,7 @@ object VideoDownloadManager {
deleteFile() deleteFile()
} }
else -> { else -> {
downloadProgressEvent.invoke(Pair(id, bytesDownloaded))
isDone = true isDone = true
updateNotification() updateNotification()
SUCCESS_DOWNLOAD_DONE SUCCESS_DOWNLOAD_DONE
@ -741,6 +753,10 @@ object VideoDownloadManager {
downloadQueue.addLast(pkg) downloadQueue.addLast(pkg)
downloadCheck(context) downloadCheck(context)
if (setKey) saveQueue(context) if (setKey) saveQueue(context)
} else {
downloadEvent.invoke(
Pair(pkg.item.ep.id, DownloadActionType.Resume)
)
} }
} }

View 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>

View 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>

View file

@ -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>

View file

@ -25,6 +25,7 @@
</androidx.core.widget.ContentLoadingProgressBar> </androidx.core.widget.ContentLoadingProgressBar>
<GridLayout android:layout_width="match_parent" android:layout_height="match_parent"> <GridLayout android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView <ImageView
android:visibility="gone"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
@ -56,18 +57,39 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"> android:layout_height="match_parent">
</TextView> </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 <ImageView
android:visibility="gone" android:visibility="visible"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="2dp"
android:layout_width="30dp" android:layout_width="30dp"
android:id="@+id/download_child_episode_download" android:id="@+id/download_child_episode_download"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical|end" android:src="@drawable/ic_baseline_play_arrow_24"
android:src="@drawable/netflix_download"
android:contentDescription="@string/download_descript"/> android:contentDescription="@string/download_descript"/>
</FrameLayout>
</GridLayout> </GridLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View file

@ -101,7 +101,7 @@
</ImageView>--> </ImageView>-->
<TextView <TextView
android:text="Dub" android:text="@string/app_dubbed_text"
android:id="@+id/text_is_dub" android:id="@+id/text_is_dub"
android:textColor="@color/textColor" android:textColor="@color/textColor"
android:paddingRight="10dp" android:paddingRight="10dp"
@ -117,7 +117,7 @@
</TextView> </TextView>
<TextView <TextView
android:id="@+id/text_is_sub" android:id="@+id/text_is_sub"
android:text="Sub" android:text="@string/app_subbed_text"
android:layout_gravity="end" android:layout_gravity="end"
android:textColor="@color/textColor" android:textColor="@color/textColor"
android:paddingRight="10dp" android:paddingRight="10dp"

View file

@ -42,4 +42,9 @@
<string name="error_loading_links">Error Loading Links</string> <string name="error_loading_links">Error Loading Links</string>
<string name="download_storage_text">Internal Storage</string> <string name="download_storage_text">Internal Storage</string>
<string name="options">Options</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> </resources>