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)
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>()
@ -246,18 +251,19 @@ object UIHelper {
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
// or View.SYSTEM_UI_FLAG_LOW_PROFILE
)
// 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)

View file

@ -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 =
"${currentMbString}MB / ${totalMbString}MB"
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))
}
}
}
}

View file

@ -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,32 +82,59 @@ class DownloadChildFragment : Fragment() {
DownloadChildAdapter(
ArrayList(),
) { click ->
if (click.action == 0) { // TODO PLAY
val info =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(requireContext(), click.data.id)
?: return@DownloadChildAdapter
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
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
.add(
R.id.homeRoot,
PlayerFragment.newInstance(
UriData(
info.path.toString(),
click.data.id,
name ?: "null",
click.data.episode,
click.data.season
),
context?.getViewPos(click.data.id)?.position ?: 0
(requireActivity() as AppCompatActivity).supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
)
.commit()
.add(
R.id.homeRoot,
PlayerFragment.newInstance(
UriData(
info.path.toString(),
click.data.id,
name ?: "null",
click.data.episode,
click.data.season
),
context?.getViewPos(click.data.id)?.position ?: 0
)
)
.commit()
}
}
}
download_child_list.adapter = adapter

View file

@ -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()
}
}
}

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 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)
)
}
}

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>
<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>
<ImageView
android:visibility="gone"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:layout_height="match_parent"
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:contentDescription="@string/download_descript"/>
</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="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:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/download_descript"/>
</FrameLayout>
</GridLayout>
</androidx.cardview.widget.CardView>

View file

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

View file

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