diff --git a/app/build.gradle b/app/build.gradle index b80c820f..0fa0e7f1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -199,6 +199,9 @@ dependencies { // Library/extensions searching with Levenshtein distance implementation 'me.xdrop:fuzzywuzzy:1.4.0' + + // aria2c downloader + implementation 'com.github.LagradOst:Aria2cButton:v0.0.6' } task androidSourcesJar(type: Jar) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 49143498..aa0ad969 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -17,6 +17,8 @@ import androidx.appcompat.widget.SearchView import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.download.Aria2cHelper.removeMetadata +import com.lagradost.cloudstream3.ui.download.Aria2cHelper.saveMetadata import com.lagradost.cloudstream3.ui.player.PlayerEventType import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv @@ -25,8 +27,13 @@ import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode import com.lagradost.cloudstream3.utils.UIHelper.toPx +import com.lagradost.fetchbutton.aria2c.Aria2Settings +import com.lagradost.fetchbutton.aria2c.Aria2Starter +import com.lagradost.fetchbutton.aria2c.DownloadListener +import com.lagradost.fetchbutton.aria2c.DownloadStatusTell import org.schabi.newpipe.extractor.NewPipe import java.util.* +import kotlin.concurrent.thread object CommonActivity { @MainThread @@ -129,6 +136,43 @@ object CommonActivity { act.updateLocale() act.updateTv() NewPipe.init(DownloaderTestImpl.getInstance()) + + DownloadListener.mainListener = { (data, metadata) -> + //TODO FIX + DownloadListener.sessionGidToId[data.gid]?.let { id -> + if (metadata.status == DownloadStatusTell.Removed + || metadata.status == DownloadStatusTell.Error + || metadata.status == DownloadStatusTell.Waiting + || metadata.status == null) { + removeMetadata(id) + } else { + saveMetadata(id, metadata) + } + /*val mainpath = metadata.items[0].files[0].path + AcraApplication.setKey( + VideoDownloadManager.KEY_DOWNLOAD_INFO, + id.toString(), + VideoDownloadManager.DownloadedFileInfo( + metadata.totalLength, + relativePath ?: "", + , + basePath = basePath.second + ) + )*/ + } + } + + thread { + Aria2Starter.start( + act, + Aria2Settings( + "1337", //UUID.randomUUID().toString() + 4337, + act.filesDir.path + "/download", //"/storage/emulated/0/Download",// + null//"${act.filesDir.path}/session" + ) + ) + } } private fun Activity.enterPIPMode() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 7686a84c..6dbdd594 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -10,10 +10,10 @@ import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.view.WindowManager -import android.widget.Toast import androidx.annotation.IdRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.navigation.NavController @@ -107,6 +107,8 @@ const val VLC_EXTRA_POSITION_OUT = "extra_position" const val VLC_EXTRA_DURATION_OUT = "extra_duration" const val VLC_LAST_ID_KEY = "vlc_last_open_id" +const val DOWNLOAD_COUNT_KEY = "download_badge_count" + // Short name for requests client to make it nicer to use var app = Requests(responseParser = object : ResponseParser { @@ -150,7 +152,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { * @return true if the str has launched an app task (be it successful or not) * @param isWebview does not handle providers and opening download page if true. Can still add repos and login. * */ - fun handleAppIntentUrl(activity: FragmentActivity?, str: String?, isWebview: Boolean): Boolean = + fun handleAppIntentUrl( + activity: FragmentActivity?, + str: String?, + isWebview: Boolean + ): Boolean = with(activity) { if (str != null && this != null) { if (str.startsWith("https://cs.repo")) { @@ -191,7 +197,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val url = str.replaceFirst(appStringRepo, "https") loadRepository(url) return true - } else if (!isWebview){ + } else if (!isWebview) { if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) { this.navigate(R.id.navigation_downloads) return true @@ -267,6 +273,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } + if (destination.id == R.id.navigation_download_child || destination.id == R.id.navigation_downloads) { + setKey(DOWNLOAD_COUNT_KEY, 0) + } + + nav_view?.getOrCreateBadge(R.id.navigation_downloads)?.apply { + val count = getKey(DOWNLOAD_COUNT_KEY) ?: 0 + if (count <= 0) { + clearNumber() + isVisible = false + } else { + this.backgroundColor = + getResourceColor(R.attr.colorPrimary) + this.badgeTextColor = + getResourceColor(R.attr.colorOnPrimary) + isVisible = true + number = count + } + } + nav_view?.isVisible = isNavVisible && !landscape nav_rail_view?.isVisible = isNavVisible && landscape } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/DownloadButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/DownloadButton.kt new file mode 100644 index 00000000..ce9969c5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/DownloadButton.kt @@ -0,0 +1,55 @@ +package com.lagradost.cloudstream3.ui + +import android.content.Context +import android.util.AttributeSet +import android.widget.TextView +import androidx.core.view.isVisible +import com.google.android.material.button.MaterialButton +import com.lagradost.cloudstream3.R +import com.lagradost.fetchbutton.aria2c.DownloadStatusTell +import com.lagradost.fetchbutton.aria2c.Metadata +import com.lagradost.fetchbutton.ui.PieFetchButton + +class DownloadButton(context: Context, attributeSet: AttributeSet) : + PieFetchButton(context, attributeSet) { + + var progressText: TextView? = null + var mainText: TextView? = null + var bigButton: MaterialButton? = null + + override fun onInflate() { + overrideLayout = R.layout.download_button_layout + super.onInflate() + progressText = findViewById(R.id.result_movie_download_text_precentage) + mainText = findViewById(R.id.result_movie_download_text) + bigButton = findViewById(R.id.download_big_button) + } + + override fun setOnClickListener(l: OnClickListener?) { + bigButton?.setOnClickListener(l) + } + + override fun setOnLongClickListener(l: OnLongClickListener?) { + bigButton?.setOnLongClickListener(l) + } + + override fun setStatus(status: DownloadStatusTell?) { + super.setStatus(status) + val txt = when (status) { + DownloadStatusTell.Paused -> R.string.download_paused + DownloadStatusTell.Active -> R.string.downloading + DownloadStatusTell.Complete -> R.string.downloaded + else -> R.string.download + } + mainText?.setText(txt) + } + + override fun updateViewOnDownload(metadata: Metadata) { + super.updateViewOnDownload(metadata) + + val isVis = metadata.progressPercentage > 0 + progressText?.isVisible = isVis + if (isVis) + progressText?.text = "${metadata.progressPercentage}%" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/Aria2cHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/Aria2cHelper.kt new file mode 100644 index 00000000..94e52b8a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/Aria2cHelper.kt @@ -0,0 +1,68 @@ +package com.lagradost.cloudstream3.ui.download + +import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.utils.VideoDownloadManager +import com.lagradost.cloudstream3.utils.VideoDownloadManager.KEY_DOWNLOAD_INFO +import com.lagradost.fetchbutton.aria2c.Aria2Starter +import com.lagradost.fetchbutton.aria2c.DownloadListener +import com.lagradost.fetchbutton.aria2c.Metadata +import java.io.File + +const val KEY_DOWNLOAD_INFO_METADATA = "download_info_metadata" + +object Aria2cHelper { + fun deleteId(id: Long) { + // backward compatibility + VideoDownloadManager.downloadDeleteEvent.invoke(id.toInt()) + + getMetadata(id)?.let { data -> + Aria2Starter.delete( + DownloadListener.sessionIdToGid[id], + id, + data.items.flatMap { it.files }) + } + removeMetadata(id) + AcraApplication.removeKey(KEY_DOWNLOAD_INFO, id.toString()) + } + + fun saveMetadata(id: Long, meta: Metadata) { + AcraApplication.setKey(KEY_DOWNLOAD_INFO_METADATA, id.toString(), meta) + } + + fun removeMetadata(id: Long) { + AcraApplication.removeKey(KEY_DOWNLOAD_INFO_METADATA, id.toString()) + } + + fun downloadExist(data: Metadata): Boolean { + return data.items.any { + it.files.any { file -> + try { + //println("TESTING PATH: ${file.path}") + File(file.path).exists() + } catch (e: Exception) { + false + } + } + } + } + + fun downloadExist(id: Long): Boolean { + return downloadExist(getMetadata(id) ?: return false) + } + + fun getMetadata(id: Long): Metadata? { + return AcraApplication.getKey(KEY_DOWNLOAD_INFO_METADATA, id.toString()) + } + + fun pause(id: Long) { + DownloadListener.sessionIdToGid[id]?.let { gid -> + Aria2Starter.pause(gid, all = true) + } + } + + fun unpause(id: Long) { + DownloadListener.sessionIdToGid[id]?.let { gid -> + Aria2Starter.unpause(gid, all = true) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt index 0069be3a..5f007de6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt @@ -2,22 +2,160 @@ package com.lagradost.cloudstream3.ui.download import android.app.Activity import android.content.DialogInterface +import android.text.format.Formatter +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.view.doOnAttach +import androidx.lifecycle.findViewTreeLifecycleOwner import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.DownloadFileGenerator import com.lagradost.cloudstream3.ui.player.GeneratorPlayer +import com.lagradost.cloudstream3.ui.result.DownloadHelper.play import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.UIHelper.navigate +import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager +import com.lagradost.fetchbutton.aria2c.DownloadListener +import com.lagradost.fetchbutton.aria2c.DownloadStatusTell +import com.lagradost.fetchbutton.ui.PieFetchButton object DownloadButtonSetup { + fun bind( + card: IVisualDownloadChildCached, + downloadButton: PieFetchButton, + extraInfo: TextView, + clickCallback: (DownloadClickEvent) -> Unit + ) { + val d = card.data ?: return + + fun updateText(downloadBytes: Long, totalBytes: Long) { + extraInfo?.apply { + text = + context.getString(R.string.download_size_format).format( + Formatter.formatShortFileSize(context, downloadBytes), + Formatter.formatShortFileSize(context, totalBytes) + ) + } + } + updateText(card.currentBytes, card.totalBytes) + downloadButton.apply { + val play = + if (card is VisualDownloadChildCached) R.string.play_episode else R.string.play_movie_button//if (card.episode <= 0) R.string.play_movie_button else R.string.play_episode + + setPersistentId(d.id.toLong()) + doOnAttach { view -> + view.findViewTreeLifecycleOwner()?.let { life -> + DownloadListener.observe(life) { + gid?.let { realGId -> + val meta = DownloadListener.getInfo(realGId) + updateText(meta.downloadedLength, meta.totalLength) + } + } + } + } + + val isValid = when (downloadButton.currentStatus) { + null, DownloadStatusTell.Removed -> false + else -> true + } + + + downloadButton.setOnClickListener { + val view = downloadButton + + fun delete() { + // view.deleteAllFiles() + clickCallback.invoke( + DownloadClickEvent( + DOWNLOAD_ACTION_DELETE_FILE, + d + ) + ) + } + + //if (view !is PieFetchButton) return@setOnClickListener + when (view.currentStatus) { + /*null, DownloadStatusTell.Removed -> { + view.setStatus(DownloadStatusTell.Waiting) + downloadClickCallback.invoke( + DownloadEpisodeClickEvent( + DOWNLOAD_ACTION_DOWNLOAD, + card + ) + ) + }*/ + DownloadStatusTell.Paused -> { + view.popupMenuNoIcons( + if (isValid) listOf( + 1 to R.string.resume, + 2 to play, + 3 to R.string.delete + ) else listOf(2 to play, 3 to R.string.delete) + ) { + when (itemId) { + 1 -> if (!view.resumeDownload()) { + /*downloadClickCallback.invoke( + DownloadEpisodeClickEvent( + DOWNLOAD_ACTION_DOWNLOAD, + card + ) + )*/ + } + 2 -> play(d) + 3 -> delete() + } + } + } + DownloadStatusTell.Complete -> { + view.popupMenuNoIcons( + listOf( + 2 to play, + 3 to R.string.delete + ) + ) { + when (itemId) { + 2 -> play(d) + 3 -> delete() + } + } + } + DownloadStatusTell.Active -> { + view.popupMenuNoIcons( + if (isValid) listOf( + 4 to R.string.pause, + 2 to play, + 3 to R.string.delete + ) else listOf( + 2 to play, + 3 to R.string.delete + ) + ) { + when (itemId) { + 4 -> view.pauseDownload() + 2 -> play(d) + 3 -> delete() + } + } + } + DownloadStatusTell.Error -> { + view.redownload() + } + DownloadStatusTell.Waiting -> { + + } + else -> {} + } + } + } + } + fun handleDownloadClick(activity: Activity?, click: DownloadClickEvent) { val id = click.data.id if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return @@ -29,6 +167,7 @@ object DownloadButtonSetup { DialogInterface.OnClickListener { _, which -> when (which) { DialogInterface.BUTTON_POSITIVE -> { + Aria2cHelper.deleteId(id.toLong()) VideoDownloadManager.deleteFileAndUpdateSettings(ctx, id) } DialogInterface.BUTTON_NEGATIVE -> { @@ -57,11 +196,14 @@ object DownloadButtonSetup { } } DOWNLOAD_ACTION_PAUSE_DOWNLOAD -> { + Aria2cHelper.pause(click.data.id.toLong()) VideoDownloadManager.downloadEvent.invoke( Pair(click.data.id, VideoDownloadManager.DownloadActionType.Pause) ) } DOWNLOAD_ACTION_RESUME_DOWNLOAD -> { + Aria2cHelper.unpause(click.data.id.toLong()) + activity?.let { ctx -> if (VideoDownloadManager.downloadStatus.containsKey(id) && VideoDownloadManager.downloadStatus[id] == VideoDownloadManager.DownloadType.IsPaused) { VideoDownloadManager.downloadEvent.invoke( @@ -86,6 +228,7 @@ object DownloadButtonSetup { act, click.data.id )?.fileLength + ?: Aria2cHelper.getMetadata(click.data.id.toLong())?.downloadedLength ?: 0 if (length > 0) { showToast(act, R.string.delete, Toast.LENGTH_LONG) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt index a541171b..2f8a42e5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildAdapter.kt @@ -3,18 +3,20 @@ package com.lagradost.cloudstream3.ui.download import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView import android.widget.TextView import androidx.cardview.widget.CardView +import androidx.core.view.isVisible import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.DownloadHelper.play +import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.fetchbutton.ui.PieFetchButton import kotlinx.android.synthetic.main.download_child_episode.view.* -import java.util.* const val DOWNLOAD_ACTION_PLAY_FILE = 0 const val DOWNLOAD_ACTION_DELETE_FILE = 1 @@ -23,52 +25,31 @@ const val DOWNLOAD_ACTION_PAUSE_DOWNLOAD = 3 const val DOWNLOAD_ACTION_DOWNLOAD = 4 const val DOWNLOAD_ACTION_LONG_CLICK = 5 +interface IVisualDownloadChildCached{ + val currentBytes: Long + val totalBytes: Long + val data: VideoDownloadHelper.DownloadEpisodeCached? +} + data class VisualDownloadChildCached( - val currentBytes: Long, - val totalBytes: Long, - val data: VideoDownloadHelper.DownloadEpisodeCached, -) + override val currentBytes: Long, + override val totalBytes: Long, + override val data: VideoDownloadHelper.DownloadEpisodeCached, +) : IVisualDownloadChildCached data class DownloadClickEvent(val action: Int, val data: EasyDownloadButton.IMinimumData) +data class DownloadEpisodeClickEvent(val action: Int, val data: ResultEpisode) class DownloadChildAdapter( var cardList: List, private val clickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.Adapter() { - private val mBoundViewHolders: HashSet = HashSet() - private fun getAllBoundViewHolders(): Set? { - return Collections.unmodifiableSet(mBoundViewHolders) - } - - fun killAdapter() { - getAllBoundViewHolders()?.forEach { view -> - view?.downloadButton?.dispose() - } - } - - override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.downloadButton.dispose() - } - } - - override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.downloadButton.dispose() - mBoundViewHolders.remove(holder) - } - } - - override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.reattachDownloadButton() - } - } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return DownloadChildViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.download_child_episode, parent, false), + LayoutInflater.from(parent.context) + .inflate(R.layout.download_child_episode, parent, false), clickCallback ) } @@ -77,7 +58,6 @@ class DownloadChildAdapter( when (holder) { is DownloadChildViewHolder -> { holder.bind(cardList[position]) - mBoundViewHolders.add(holder) } } } @@ -90,15 +70,13 @@ class DownloadChildAdapter( constructor( itemView: View, private val clickCallback: (DownloadClickEvent) -> Unit, - ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { - override var downloadButton = EasyDownloadButton() - + ) : RecyclerView.ViewHolder(itemView) { private val title: TextView = itemView.download_child_episode_text 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 + private val progressBar: ContentLoadingProgressBar = + itemView.download_child_episode_progress + private val downloadButton: PieFetchButton = itemView.download_child_episode_download private var localCard: VisualDownloadChildCached? = null @@ -118,35 +96,17 @@ class DownloadChildAdapter( title.text = title.context.getNameFull(d.name, d.episode, d.season) title.isSelected = true // is needed for text repeating - - downloadButton.setUpButton( - card.currentBytes, - card.totalBytes, - progressBarDownload, - downloadImage, - extraInfo, - card.data, - clickCallback - ) + //extraInfo.text = card.currentBytes + DownloadButtonSetup.bind(card, downloadButton, extraInfo, clickCallback) holder.setOnClickListener { - clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d)) - } - } - - override fun reattachDownloadButton() { - downloadButton.dispose() - val card = localCard - if (card != null) { - downloadButton.setUpButton( - card.currentBytes, - card.totalBytes, - progressBarDownload, - downloadImage, - extraInfo, - card.data, - clickCallback - ) + if (downloadButton.isVisible) { + downloadButton.play(d) + } else { + holder.setOnClickListener { + clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d)) + } + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 477a18e0..7d402d8b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar +import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager import kotlinx.android.synthetic.main.fragment_child_downloads.* @@ -21,7 +22,7 @@ import kotlinx.coroutines.withContext class DownloadChildFragment : Fragment() { companion object { - fun newInstance(headerName: String, folder: String) : Bundle { + fun newInstance(headerName: String, folder: String): Bundle { return Bundle().apply { putString("folder", folder) putString("name", headerName) @@ -30,15 +31,18 @@ class DownloadChildFragment : Fragment() { } override fun onDestroyView() { - (download_child_list?.adapter as DownloadChildAdapter?)?.killAdapter() downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it } super.onDestroyView() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { return inflater.inflate(R.layout.fragment_child_downloads, container, false) } - + private var hasPopped = false // fix stupid bug private fun updateList(folder: String) = main { context?.let { ctx -> val data = withContext(Dispatchers.IO) { ctx.getKeys(folder) } @@ -50,9 +54,11 @@ class DownloadChildFragment : Fragment() { ?: return@mapNotNull null VisualDownloadChildCached(info.fileLength, info.totalBytes, it) } - }.sortedBy { it.data.episode + (it.data.season?: 0)*100000 } - if (eps.isEmpty()) { - activity?.onBackPressed() + }.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 } + + if (eps.isEmpty() && !hasPopped) { + hasPopped = true + activity?.popCurrentPage() return@main } @@ -85,6 +91,13 @@ class DownloadChildFragment : Fragment() { ArrayList(), ) { click -> handleDownloadClick(activity, click) + /* println("HANDLE ACTION :${click.action}") + if (click.action == DOWNLOAD_ACTION_DELETE_FILE) { + val list = (download_child_list?.adapter as DownloadChildAdapter?)?.cardList + if (list != null) { + updateList(folder) + } + }*/ } downloadDeleteEventListener = { id: Int -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index b2286c99..cc47771f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -69,7 +69,6 @@ class DownloadFragment : Fragment() { VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!! downloadDeleteEventListener = null } - (download_list?.adapter as DownloadHeaderAdapter?)?.killAdapter() super.onDestroyView() } @@ -165,7 +164,7 @@ class DownloadFragment : Fragment() { downloadDeleteEventListener = { id -> val list = (download_list?.adapter as DownloadHeaderAdapter?)?.cardList if (list != null) { - if (list.any { it.data.id == id }) { + if (list.any { it.data?.id == id }) { context?.let { ctx -> setList(ArrayList()) downloadsViewModel.updateList(ctx) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt index 29bb303a..c1cb7e1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt @@ -8,23 +8,25 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.cardview.widget.CardView +import androidx.core.view.isVisible import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.fetchbutton.ui.PieFetchButton import kotlinx.android.synthetic.main.download_header_episode.view.* import java.util.* data class VisualDownloadHeaderCached( val currentOngoingDownloads: Int, val totalDownloads: Int, - val totalBytes: Long, - val currentBytes: Long, - val data: VideoDownloadHelper.DownloadHeaderCached, - val child: VideoDownloadHelper.DownloadEpisodeCached?, -) + override val totalBytes: Long, + override val currentBytes: Long, + val header: VideoDownloadHelper.DownloadHeaderCached, + override val data: VideoDownloadHelper.DownloadEpisodeCached?, +) : IVisualDownloadChildCached data class DownloadHeaderClickEvent(val action: Int, val data: VideoDownloadHelper.DownloadHeaderCached) @@ -34,35 +36,6 @@ class DownloadHeaderAdapter( private val movieClickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.Adapter() { - private val mBoundViewHolders: HashSet = HashSet() - private fun getAllBoundViewHolders(): Set? { - return Collections.unmodifiableSet(mBoundViewHolders) - } - - fun killAdapter() { - getAllBoundViewHolders()?.forEach { view -> - view?.downloadButton?.dispose() - } - } - - override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.downloadButton.dispose() - } - } - - override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.downloadButton.dispose() - mBoundViewHolders.remove(holder) - } - } - - override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.reattachDownloadButton() - } - } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return DownloadHeaderViewHolder( @@ -76,7 +49,6 @@ class DownloadHeaderAdapter( when (holder) { is DownloadHeaderViewHolder -> { holder.bind(cardList[position]) - mBoundViewHolders.add(holder) } } } @@ -90,23 +62,17 @@ class DownloadHeaderAdapter( itemView: View, private val clickCallback: (DownloadHeaderClickEvent) -> Unit, private val movieClickCallback: (DownloadClickEvent) -> Unit, - ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { - override var downloadButton = EasyDownloadButton() - + ) : RecyclerView.ViewHolder(itemView) { private val poster: ImageView? = itemView.download_header_poster private val title: TextView = itemView.download_header_title private val extraInfo: TextView = itemView.download_header_info private val holder: CardView = itemView.episode_holder - private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded - private val downloadImage: ImageView = itemView.download_header_episode_download - private val normalImage: ImageView = itemView.download_header_goto_child - private var localCard: VisualDownloadHeaderCached? = null + private val downloadButton: PieFetchButton = itemView.download_header_download @SuppressLint("SetTextI18n") fun bind(card: VisualDownloadHeaderCached) { - localCard = card - val d = card.data + val d = card.header poster?.setImage(d.poster) poster?.setOnClickListener { @@ -117,10 +83,8 @@ class DownloadHeaderAdapter( val mbString = formatShortFileSize(itemView.context, card.totalBytes) //val isMovie = d.type.isMovieType() - if (card.child != null) { - downloadBar.visibility = View.VISIBLE - downloadImage.visibility = View.VISIBLE - normalImage.visibility = View.GONE + if (card.data != null) { + downloadButton.isVisible = true /*setUpButton( card.currentBytes, card.totalBytes, @@ -130,14 +94,15 @@ class DownloadHeaderAdapter( card.child, movieClickCallback )*/ + DownloadButtonSetup.bind(card, downloadButton, extraInfo) { click -> + movieClickCallback.invoke(DownloadClickEvent(click.action, card.data)) + } holder.setOnClickListener { - movieClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child)) + movieClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.data)) } } else { - downloadBar.visibility = View.GONE - downloadImage.visibility = View.GONE - normalImage.visibility = View.VISIBLE + downloadButton.isVisible = false try { extraInfo.text = @@ -160,21 +125,5 @@ class DownloadHeaderAdapter( } } } - - override fun reattachDownloadButton() { - downloadButton.dispose() - val card = localCard - if (card?.child != null) { - downloadButton.setUpButton( - card.currentBytes, - card.totalBytes, - downloadBar, - downloadImage, - extraInfo, - card.child, - movieClickCallback - ) - } - } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index 3a74a715..70458a62 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -1,12 +1,15 @@ package com.lagradost.cloudstream3.ui.download import android.content.Context +import android.net.Uri import android.os.Environment import android.os.StatFs import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError @@ -46,6 +49,8 @@ class DownloadViewModel : ViewModel() { .distinctBy { it.id } // Remove duplicates } + println("CHILDRE:$children") + // parentId : bytes val totalBytesUsedByChild = HashMap() // parentId : bytes @@ -53,12 +58,10 @@ class DownloadViewModel : ViewModel() { // parentId : downloadsCount val totalDownloads = HashMap() - // Gets all children downloads withContext(Dispatchers.IO) { for (c in children) { val childFile = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, c.id) ?: continue - if (childFile.fileLength <= 1) continue val len = childFile.totalBytes val flen = childFile.fileLength @@ -68,7 +71,8 @@ class DownloadViewModel : ViewModel() { totalDownloads[c.parentId] = totalDownloads[c.parentId]?.plus(1) ?: 1 } } - + println("TotalDownloads:$totalDownloads") + //println("FIXED: $totalDownloads") val cached = withContext(Dispatchers.IO) { // wont fetch useless keys totalDownloads.entries.filter { it.value > 0 }.mapNotNull { context.getKey( @@ -77,6 +81,7 @@ class DownloadViewModel : ViewModel() { ) } } + println("Cached:$cached") val visual = withContext(Dispatchers.IO) { cached.mapNotNull { // TODO FIX @@ -99,7 +104,7 @@ class DownloadViewModel : ViewModel() { movieEpisode ) }.sortedBy { - (it.child?.episode ?: 0) + (it.child?.season?.times(10000) ?: 0) + (it.data?.episode ?: 0) + (it.data?.season?.times(10000) ?: 0) } // episode sorting by episode, lowest to highest } try { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/DownloadHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/DownloadHelper.kt new file mode 100644 index 00000000..9cc1489c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/DownloadHelper.kt @@ -0,0 +1,166 @@ +package com.lagradost.cloudstream3.ui.result + +import androidx.core.net.toUri +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD +import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK +import com.lagradost.cloudstream3.ui.download.DownloadEpisodeClickEvent +import com.lagradost.cloudstream3.ui.player.DownloadFileGenerator +import com.lagradost.cloudstream3.ui.player.GeneratorPlayer +import com.lagradost.cloudstream3.utils.ExtractorUri +import com.lagradost.cloudstream3.utils.UIHelper.navigate +import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons +import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.fetchbutton.aria2c.Aria2Starter +import com.lagradost.fetchbutton.aria2c.DownloadStatusTell +import com.lagradost.fetchbutton.ui.PieFetchButton +import java.io.File + +object DownloadHelper { + fun PieFetchButton.play(card: VideoDownloadHelper.DownloadEpisodeCached) { + val files = this.getVideos() + // fucked af, but who cares + Aria2Starter.saveActivity.get()?.navigate( + R.id.global_to_navigation_player, GeneratorPlayer.newInstance(DownloadFileGenerator( + files.map { path -> + val file = File(path) + + ExtractorUri( + uri = file.toUri(), + + id = card.id, + parentId = card.parentId, + name = context.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName + season = card.season, + episode = card.episode, + + basePath = file.path, + displayName = file.name + //displayName = keyInfo.displayName, + //relativePath = keyInfo.relativePath, + ) + } + ))) + } + + fun PieFetchButton.play(card: ResultEpisode) { + val files = this.getVideos() + // fucked af, but who cares + Aria2Starter.saveActivity.get()?.navigate( + + R.id.global_to_navigation_player, GeneratorPlayer.newInstance(DownloadFileGenerator( + files.map { path -> + val file = File(path) + + ExtractorUri( + uri = file.toUri(), + + id = card.id, + parentId = card.parentId, + name = context.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName + season = card.season, + episode = card.episode, + headerName = card.headerName, + tvType = card.tvType, + + basePath = file.path, + displayName = file.name, + //relativePath = keyInfo.relativePath, + ) + } + ))) + } + + fun PieFetchButton.setUp( + card: ResultEpisode, + downloadClickCallback: (DownloadEpisodeClickEvent) -> Unit + ) { + setPersistentId(card.id.toLong()) + val play = if (card.episode <= 0) R.string.play_movie_button else R.string.play_episode + + setOnLongClickListener { //Aria2Starter.saveActivity.get() + downloadClickCallback.invoke( + DownloadEpisodeClickEvent( + DOWNLOAD_ACTION_LONG_CLICK, + card + ) + ) + //showToast(it.context as? Activity, R.string.download, Toast.LENGTH_SHORT) + return@setOnLongClickListener true + } + + setOnClickListener { + val view = this + //if (view !is PieFetchButton) return@setOnClickListener + when (view.currentStatus) { + null, DownloadStatusTell.Removed -> { + view.setStatus(DownloadStatusTell.Waiting) + downloadClickCallback.invoke( + DownloadEpisodeClickEvent( + DOWNLOAD_ACTION_DOWNLOAD, + card + ) + ) + } + DownloadStatusTell.Paused -> { + view.popupMenuNoIcons( + listOf( + 1 to R.string.resume, + 2 to play, + 3 to R.string.delete + ) + ) { + when (itemId) { + 1 -> if (!view.resumeDownload()) { + downloadClickCallback.invoke( + DownloadEpisodeClickEvent( + DOWNLOAD_ACTION_DOWNLOAD, + card + ) + ) + } + 2 -> play(card) + 3 -> view.deleteAllFiles() + } + } + } + DownloadStatusTell.Complete -> { + view.popupMenuNoIcons( + listOf( + 2 to play, + 3 to R.string.delete + ) + ) { + when (itemId) { + 2 -> play(card) + 3 -> view.deleteAllFiles() + } + } + } + DownloadStatusTell.Active -> { + view.popupMenuNoIcons( + listOf( + 4 to R.string.pause, + 2 to play, + 3 to R.string.delete + ) + ) { + when (itemId) { + 4 -> view.pauseDownload() + 2 -> play(card) + 3 -> view.deleteAllFiles() + } + } + } + DownloadStatusTell.Error -> { + view.redownload() + } + DownloadStatusTell.Waiting -> { + + } + else -> {} + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 3abd827e..e5e886b0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -13,23 +13,17 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder -import com.lagradost.cloudstream3.ui.download.DownloadClickEvent -import com.lagradost.cloudstream3.ui.download.EasyDownloadButton +import com.lagradost.cloudstream3.ui.download.DownloadEpisodeClickEvent +import com.lagradost.cloudstream3.ui.result.DownloadHelper.setUp import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.UIHelper.setImage -import com.lagradost.cloudstream3.utils.VideoDownloadHelper -import com.lagradost.cloudstream3.utils.VideoDownloadManager import kotlinx.android.synthetic.main.result_episode.view.* import kotlinx.android.synthetic.main.result_episode.view.episode_text -import kotlinx.android.synthetic.main.result_episode_large.view.* import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler import kotlinx.android.synthetic.main.result_episode_large.view.episode_progress -import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_download -import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_progress_downloaded -import java.util.* +import kotlinx.android.synthetic.main.result_episode_large_tv.view.* const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2 @@ -58,21 +52,10 @@ data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) class EpisodeAdapter( private val hasDownloadSupport: Boolean, private val clickCallback: (EpisodeClickEvent) -> Unit, - private val downloadClickCallback: (DownloadClickEvent) -> Unit, + private val downloadClickCallback: (DownloadEpisodeClickEvent) -> Unit, ) : RecyclerView.Adapter() { private var cardList: MutableList = mutableListOf() - private val mBoundViewHolders: HashSet = HashSet() - private fun getAllBoundViewHolders(): Set? { - return Collections.unmodifiableSet(mBoundViewHolders) - } - - fun killAdapter() { - getAllBoundViewHolders()?.forEach { view -> - view?.downloadButton?.dispose() - } - } - override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { if (holder.itemView.hasFocus()) { holder.itemView.clearFocus() @@ -85,15 +68,6 @@ class EpisodeAdapter( } } - override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - if (holder is DownloadButtonViewHolder) { - holder.downloadButton.dispose() - mBoundViewHolders.remove(holder) - //(holder.itemView as? FrameLayout?)?.descendantFocusability = - // ViewGroup.FOCUS_BLOCK_DESCENDANTS - } - } - override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { if (holder is DownloadButtonViewHolder) { //println("onViewAttachedToWindow = ${holder.absoluteAdapterPosition}") @@ -138,7 +112,6 @@ class EpisodeAdapter( when (holder) { is EpisodeCardViewHolder -> { holder.bind(cardList[position]) - mBoundViewHolders.add(holder) } } } @@ -152,18 +125,12 @@ class EpisodeAdapter( itemView: View, private val hasDownloadSupport: Boolean, private val clickCallback: (EpisodeClickEvent) -> Unit, - private val downloadClickCallback: (DownloadClickEvent) -> Unit, - ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { - override var downloadButton = EasyDownloadButton() - - var episodeDownloadBar: ContentLoadingProgressBar? = null - var episodeDownloadImage: ImageView? = null - var localCard: ResultEpisode? = null + private val downloadClickCallback: (DownloadEpisodeClickEvent) -> Unit, + ) : RecyclerView.ViewHolder(itemView) { + //override var downloadButton = EasyDownloadButton() @SuppressLint("SetTextI18n") fun bind(card: ResultEpisode) { - localCard = card - val isTrueTv = isTrueTvSettings() val (parentView, otherView) = if (card.poster == null) { @@ -181,9 +148,10 @@ class EpisodeAdapter( val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress val episodePoster: ImageView? = parentView.episode_poster - episodeDownloadBar = - parentView.result_episode_progress_downloaded - episodeDownloadImage = parentView.result_episode_download + val downloadButton = parentView.result_episode_download + + downloadButton.isVisible = hasDownloadSupport + downloadButton.setUp(card, downloadClickCallback) val name = if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}" @@ -242,49 +210,6 @@ class EpisodeAdapter( return@setOnLongClickListener true } - - episodeDownloadImage?.isVisible = hasDownloadSupport - episodeDownloadBar?.isVisible = hasDownloadSupport - reattachDownloadButton() - } - - override fun reattachDownloadButton() { - downloadButton.dispose() - val card = localCard - if (hasDownloadSupport && card != null) { - if (episodeDownloadBar == null || - episodeDownloadImage == null - ) return - val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings( - itemView.context, - card.id - ) - - downloadButton.setUpButton( - downloadInfo?.fileLength, - downloadInfo?.totalBytes, - episodeDownloadBar ?: return, - episodeDownloadImage ?: return, - null, - VideoDownloadHelper.DownloadEpisodeCached( - card.name, - card.poster, - card.episode, - card.season, - card.id, - card.parentId, - card.rating, - card.description, - System.currentTimeMillis(), - ) - ) { - if (it.action == DOWNLOAD_ACTION_DOWNLOAD) { - clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card)) - } else { - downloadClickCallback.invoke(it) - } - } - } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 5fc61146..552b3762 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -35,20 +35,20 @@ import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD -import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick -import com.lagradost.cloudstream3.ui.download.EasyDownloadButton +import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment +import com.lagradost.cloudstream3.ui.result.DownloadHelper.setUp import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.openBrowser -import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe -import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog +import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar @@ -72,11 +72,6 @@ import kotlinx.android.synthetic.main.fragment_result.result_meta_rating import kotlinx.android.synthetic.main.fragment_result.result_meta_site import kotlinx.android.synthetic.main.fragment_result.result_meta_type import kotlinx.android.synthetic.main.fragment_result.result_meta_year -import kotlinx.android.synthetic.main.fragment_result.result_movie_download_icon -import kotlinx.android.synthetic.main.fragment_result.result_movie_download_text -import kotlinx.android.synthetic.main.fragment_result.result_movie_download_text_precentage -import kotlinx.android.synthetic.main.fragment_result.result_movie_progress_downloaded -import kotlinx.android.synthetic.main.fragment_result.result_movie_progress_downloaded_holder import kotlinx.android.synthetic.main.fragment_result.result_next_airing import kotlinx.android.synthetic.main.fragment_result.result_next_airing_time import kotlinx.android.synthetic.main.fragment_result.result_no_episodes @@ -253,11 +248,8 @@ open class ResultFragment : ResultTrailerPlayer() { return inflater.inflate(resultLayout, container, false) } - private var downloadButton: EasyDownloadButton? = null override fun onDestroyView() { updateUIListener = null - (result_episodes?.adapter as EpisodeAdapter?)?.killAdapter() - downloadButton?.dispose() super.onDestroyView() } @@ -344,7 +336,18 @@ open class ResultFragment : ResultTrailerPlayer() { return@setOnLongClickListener true } - main { + + result_download_movie?.setUp(ep) { + when (it.action) { + DOWNLOAD_ACTION_DOWNLOAD -> viewModel.download(activity, it.data) + DOWNLOAD_ACTION_LONG_CLICK -> viewModel.handleAction( + activity, + EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, ep) + ) + } + } + result_download_movie?.isVisible = true + /*main { val file = ioWorkSafe { context?.let { @@ -389,11 +392,12 @@ open class ResultFragment : ResultTrailerPlayer() { } } result_movie_progress_downloaded_holder?.isVisible = true - } + }*/ } } else -> { - result_movie_progress_downloaded_holder?.isVisible = false + //result_movie_progress_downloaded_holder?.isVisible = false + result_download_movie?.isVisible = false result_play_movie?.isVisible = false } } @@ -458,7 +462,14 @@ open class ResultFragment : ResultTrailerPlayer() { val storedData = getStoredData(activity ?: context ?: return) ?: return //viewModel.clear() - viewModel.load(activity, storedData.url ?: return, storedData.apiName, storedData.showFillers, storedData.dubStatus, storedData.start) + viewModel.load( + activity, + storedData.url ?: return, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) } } @@ -507,8 +518,15 @@ open class ResultFragment : ResultTrailerPlayer() { { episodeClick -> viewModel.handleAction(activity, episodeClick) }, - { downloadClickEvent -> - handleDownloadClick(activity, downloadClickEvent) + { clickEvent -> + when (clickEvent.action) { + DOWNLOAD_ACTION_DOWNLOAD -> viewModel.download(activity, clickEvent.data) + DOWNLOAD_ACTION_LONG_CLICK -> viewModel.handleAction( + activity, + EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, clickEvent.data) + ) + } + //handleDownloadClick(activity, downloadClickEvent) } ) @@ -916,7 +934,14 @@ open class ResultFragment : ResultTrailerPlayer() { if (storedData?.url != null) { result_reload_connectionerror.setOnClickListener { - viewModel.load(activity, storedData.url, storedData.apiName, storedData.showFillers, storedData.dubStatus, storedData.start) + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) } result_reload_connection_open_in_browser?.setOnClickListener { @@ -952,7 +977,14 @@ open class ResultFragment : ResultTrailerPlayer() { if (restart || !viewModel.hasLoaded()) { //viewModel.clear() - viewModel.load(activity, storedData.url, storedData.apiName, storedData.showFillers, storedData.dubStatus, storedData.start) + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 48919308..a47c653c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -16,6 +16,8 @@ import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.unixTime +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer @@ -28,7 +30,6 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.IGenerator import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator @@ -53,8 +54,14 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.UIHelper.checkWrite +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW +import com.lagradost.fetchbutton.NotificationMetaData +import com.lagradost.fetchbutton.aria2c.Aria2Starter +import com.lagradost.fetchbutton.aria2c.UriRequest +import com.lagradost.fetchbutton.aria2c.newUriRequest +import com.lagradost.fetchbutton.utils.Coroutines.mainThread import kotlinx.coroutines.* import java.io.File import java.lang.Math.abs @@ -529,24 +536,16 @@ class ResultViewModel2 : ViewModel() { } private fun downloadSubtitle( - context: Context?, link: ExtractorSubtitleLink, fileName: String, folder: String ) { - ioSafe { - VideoDownloadManager.downloadThing( - context ?: return@ioSafe, - link, - "$fileName ${link.name}", - folder, - if (link.url.contains(".srt")) ".srt" else "vtt", - false, - null - ) { - // no notification - } - } + Aria2Starter.download( + newUriRequest( + null, link.url, fileName, folder, link.headers, + USER_AGENT + ) + ) } private fun getFolder(currentType: TvType, titleName: String): String { @@ -576,7 +575,6 @@ class ResultViewModel2 : ViewModel() { val fileName = VideoDownloadManager.getFileName(ctx, meta) val folder = getFolder(meta.type ?: return, meta.mainName) downloadSubtitle( - ctx, ExtractorSubtitleLink(link.name, link.url, ""), fileName, folder @@ -584,8 +582,7 @@ class ResultViewModel2 : ViewModel() { } } - fun startDownload( - context: Context?, + fun getDownloadRequest( episode: ResultEpisode, currentIsMovie: Boolean, currentHeaderName: String, @@ -596,93 +593,182 @@ class ResultViewModel2 : ViewModel() { url: String, links: List, subs: List? - ) { - try { - if (context == null) return - - val meta = - getMeta( - episode, - currentHeaderName, - apiName, - currentPoster, - currentIsMovie, - currentType - ) - - val folder = getFolder(currentType, currentHeaderName) - - val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let - - // SET VISUAL KEYS - AcraApplication.setKey( - DOWNLOAD_HEADER_CACHE, - parentId.toString(), - VideoDownloadHelper.DownloadHeaderCached( - apiName, - url, - currentType, - currentHeaderName, - currentPoster, - parentId, - System.currentTimeMillis(), - ) + ): DownloadRequest? { + val meta = + getMeta( + episode, + currentHeaderName, + apiName, + currentPoster, + currentIsMovie, + currentType ) - AcraApplication.setKey( - DataStore.getFolderName( - DOWNLOAD_EPISODE_CACHE, - parentId.toString() - ), // 3 deep folder for faster acess - episode.id.toString(), - VideoDownloadHelper.DownloadEpisodeCached( - episode.name, - episode.poster, - episode.episode, - episode.season, - episode.id, - parentId, - episode.rating, - episode.description, - System.currentTimeMillis(), - ) - ) + val topFolder = VideoDownloadManager.getDownloadDir()?.filePath + ?: throw RuntimeException("FUCK YOU")//AcraApplication.context?.getBasePath()?.first //?.second?.also { println("URIIIIII: $it") } ?: throw RuntimeException("FUCK YOU") + //?: VideoDownloadManager.getDownloadDir()?.filePath ?: return null - // DOWNLOAD VIDEO - VideoDownloadManager.downloadEpisodeUsingWorker( - context, - src,//url ?: return, - folder, - meta, - links - ) + val folder = + topFolder//topFolder?.gotoDir(getFolder(currentType, currentHeaderName).replace(".", ""), true)?.uri?.toString() ?: throw RuntimeException("FUCK YOU") + //val folder = + // topFolder + "/" + getFolder(currentType, currentHeaderName).replace(".", "") + //val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let - // 1. Checks if the lang should be downloaded - // 2. Makes it into the download format - // 3. Downloads it as a .vtt file - val downloadList = SubtitlesFragment.getDownloadSubsLanguageISO639_1() - subs?.let { subsList -> - subsList.filter { - downloadList.contains( - SubtitleHelper.fromLanguageToTwoLetters( - it.name, - true - ) - ) - } - .map { ExtractorSubtitleLink(it.name, it.url, "") } - .forEach { link -> - val fileName = VideoDownloadManager.getFileName(context, meta) - downloadSubtitle(context, link, fileName, folder) - } + // SET VISUAL KEYS + AcraApplication.setKey( + DOWNLOAD_HEADER_CACHE, + parentId.toString(), + VideoDownloadHelper.DownloadHeaderCached( + apiName, + url, + currentType, + currentHeaderName, + currentPoster, + parentId, + System.currentTimeMillis(), + ) + ) + + AcraApplication.setKey( + DataStore.getFolderName( + DOWNLOAD_EPISODE_CACHE, + parentId.toString() + ), // 3 deep folder for faster acess + episode.id.toString(), + VideoDownloadHelper.DownloadEpisodeCached( + episode.name, + episode.poster, + episode.episode, + episode.season, + episode.id, + parentId, + episode.rating, + episode.description, + System.currentTimeMillis(), + ) + ) + + val notification = AcraApplication.context?.let { ctx -> + val rowTwoExtra = if (episode.name != null) " - ${episode.name}\n" else "" + val rowTwo = if (episode.season != null && episode.episode > 0) { + "${ctx.getString(R.string.season_short)}${episode.season}:${ctx.getString(R.string.episode_short)}${episode.episode}" + rowTwoExtra + } else if (episode.episode > 0) { + "${ctx.getString(R.string.episode)} ${episode.episode}" + rowTwoExtra + } else { + (episode.name ?: "") + "" } - } catch (e: Exception) { - logError(e) + NotificationMetaData( + episode.id, + iconColor = ctx.colorFromAttribute(R.attr.colorPrimary), + posterUrl = currentPoster, + contentTitle = currentHeaderName, + secondRow = rowTwo, + subText = null, + linkName = null, + ) } + + + val linkRequests = links.filter { link -> !link.isM3u8 }.map { link -> + newUriRequest( + episode.id.toLong(), link.url, + getFolder(currentType, currentHeaderName) + File.pathSeparator + + VideoDownloadManager.getDisplayName( + VideoDownloadManager.getFileName( + AcraApplication.context ?: return null, + meta + ), "mp4" + ), null, // we use the dir set at start + link.headers, USER_AGENT, + notificationMetaData = notification?.copy( + linkName = "${link.name} ${ + Qualities.getStringByInt( + link.quality + ) + }" + ) + ) + } + + val downloadList = SubtitlesFragment.getDownloadSubsLanguageISO639_1() + val downloadSubsList = (subs ?: emptyList()).filter { + downloadList.contains( + SubtitleHelper.fromLanguageToTwoLetters( + it.name, + true + ) + ) + }.distinctBy { it.url }.groupBy { link -> + SubtitleHelper.fromLanguageToTwoLetters( + link.name, + true + ) + }.map { + it.value.map { link -> + val fileName = VideoDownloadManager.getDisplayName( + VideoDownloadManager.getFileName( + AcraApplication.context ?: return null, + meta + ), "vtt" + ) + + newUriRequest(null, link.url, fileName, folder, link.headers, USER_AGENT) + //downloadSubtitle(context, link, fileName, folder) + } + } + + return DownloadRequest(linkRequests, downloadSubsList) + + // DOWNLOAD VIDEO + //VideoDownloadManager.downloadEpisodeUsingWorker( + // context, + // src,//url ?: return, + // folder, + // meta, + // links + //) + + // 1. Checks if the lang should be downloaded + // 2. Makes it into the download format + // 3. Downloads it as a .vtt file + //val downloadList = SubtitlesFragment.getDownloadSubsLanguageISO639_1() + //subs?.let { subsList -> + // subsList.filter { + // downloadList.contains( + // SubtitleHelper.fromLanguageToTwoLetters( + // it.name, + // true + // ) + // ) + // } + // .map { ExtractorSubtitleLink(it.name, it.url, "") } + // .forEach { link -> + // val fileName = VideoDownloadManager.getFileName(context, meta) + // downloadSubtitle(context, link, fileName, folder) + // } + //} } + data class DownloadRequest( + val links: List, + val subs: List>, + ) + + + /*suspend fun download(episode: ResultEpisode): DownloadRequest { + val generator = RepoLinkGenerator(listOf(episode)) + val currentLinks = mutableSetOf() + val currentSubs = mutableSetOf() + generator.generateLinks(clearCache = false, isCasting = false, callback = { + it.first?.let { link -> + currentLinks.add(link) + } + }, subtitleCallback = { sub -> + currentSubs.add(sub) + }) + }*/ + suspend fun downloadEpisode( - activity: Activity?, episode: ResultEpisode, currentIsMovie: Boolean, currentHeaderName: String, @@ -691,52 +777,49 @@ class ResultViewModel2 : ViewModel() { apiName: String, parentId: Int, url: String, - ) { - ioSafe { - val generator = RepoLinkGenerator(listOf(episode)) - val currentLinks = mutableSetOf() - val currentSubs = mutableSetOf() - generator.generateLinks(clearCache = false, isCasting = false, callback = { - it.first?.let { link -> - currentLinks.add(link) - } - }, subtitleCallback = { sub -> - currentSubs.add(sub) - }) - - if (currentLinks.isEmpty()) { - main { - showToast( - activity, - R.string.no_links_found_toast, - Toast.LENGTH_SHORT - ) - } - return@ioSafe - } else { - main { - showToast( - activity, - R.string.download_started, - Toast.LENGTH_SHORT - ) - } + ): DownloadRequest? { + val generator = RepoLinkGenerator(listOf(episode)) + val currentLinks = mutableSetOf() + val currentSubs = mutableSetOf() + generator.generateLinks(clearCache = false, isCasting = false, callback = { + it.first?.let { link -> + currentLinks.add(link) } + }, subtitleCallback = { sub -> + currentSubs.add(sub) + }) - startDownload( - activity, - episode, - currentIsMovie, - currentHeaderName, - currentType, - currentPoster, - apiName, - parentId, - url, - sortUrls(currentLinks), - sortSubs(currentSubs), - ) - } + //if (currentLinks.isEmpty()) { + // main { + // showToast( + // activity, + // R.string.no_links_found_toast, + // Toast.LENGTH_SHORT + // ) + // } + // return@ioSafe + //} else { + // main { + // showToast( + // activity, + // R.string.download_started, + // Toast.LENGTH_SHORT + // ) + // } + //} + + return getDownloadRequest( + episode, + currentIsMovie, + currentHeaderName, + currentType, + currentPoster, + apiName, + parentId, + url, + sortUrls(currentLinks), + sortSubs(currentSubs), + ) } private fun getMeta( @@ -1021,6 +1104,42 @@ class ResultViewModel2 : ViewModel() { handleEpisodeClickEvent(activity, click) } + private fun downloadFromRequest(activity: Activity?, req: DownloadRequest) { + Aria2Starter.download(req.links) + for (sub in req.subs.take(4)) { // download max 4 langs to not block real download + Aria2Starter.download(sub) + } + val linksFound = req.links.isNotEmpty() + setKey(DOWNLOAD_COUNT_KEY, (getKey(DOWNLOAD_COUNT_KEY) ?: 0) + 1) + mainThread { + showToast( + activity, + if (linksFound) R.string.download_started else R.string.no_links_found_toast, + Toast.LENGTH_SHORT + ) + } + } + + fun download(activity: Activity?, card: ResultEpisode) = ioSafe { + getRequest(card)?.let { req -> + downloadFromRequest(activity, req) + } + } + + suspend fun getRequest(card: ResultEpisode): DownloadRequest? { + val response = currentResponse ?: return null + return downloadEpisode( + card, + response.isMovie(), + response.name, + response.type, + response.posterUrl, + response.apiName, + response.getId(), + response.url + ) + } + private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) { when (click.action) { ACTION_SHOW_OPTIONS -> { @@ -1117,18 +1236,11 @@ class ResultViewModel2 : ViewModel() { showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT) } ACTION_DOWNLOAD_EPISODE -> { - val response = currentResponse ?: return - downloadEpisode( - activity, - click.data, - response.isMovie(), - response.name, - response.type, - response.posterUrl, - response.apiName, - response.getId(), - response.url - ) + ioSafe { + val response = currentResponse ?: return@ioSafe + val req = getRequest(click.data) ?: return@ioSafe + Aria2Starter.client?.downloadFailQueue(req.links) { _, _ -> } + } } ACTION_DOWNLOAD_MIRROR -> { val response = currentResponse ?: return @@ -1138,8 +1250,7 @@ class ResultViewModel2 : ViewModel() { txt(R.string.episode_action_download_mirror) ) { (result, index) -> ioSafe { - startDownload( - activity, + val req = getDownloadRequest( click.data, response.isMovie(), response.name, @@ -1150,13 +1261,9 @@ class ResultViewModel2 : ViewModel() { response.url, listOf(result.links[index]), result.subs, - ) + ) ?: return@ioSafe + downloadFromRequest(activity, req) } - showToast( - activity, - R.string.download_started, - Toast.LENGTH_SHORT - ) } } ACTION_RELOAD_EPISODE -> { @@ -1592,7 +1699,8 @@ class ResultViewModel2 : ViewModel() { val idIndex = ep.key.id for ((index, i) in ep.value.withIndex()) { val episode = i.episode ?: (index + 1) - val id = mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000) ?: 0) + val id = + mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000) ?: 0) if (!existingEpisodes.contains(id)) { existingEpisodes.add(id) val seasonData = loadResponse.seasonNames.getSeason(i.season) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index a629dad9..eec7ab17 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -22,15 +22,14 @@ import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.fasterxml.jackson.annotation.JsonProperty import com.hippo.unifile.UniFile +import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.MainActivity -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.services.VideoDownloadService +import com.lagradost.cloudstream3.ui.download.Aria2cHelper import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey @@ -426,7 +425,7 @@ object VideoDownloadManager { } private const val reservedChars = "|\\?*<\":>+[]/\'" - fun sanitizeFilename(name: String, removeSpaces: Boolean= false): String { + fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String { var tempName = name for (c in reservedChars) { tempName = tempName.replace(c, ' ') @@ -531,6 +530,7 @@ object VideoDownloadManager { MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), projection, selection, null, null ) + println("result:$result ${result?.count}") result.use { c -> if (c != null && c.count >= 1) { @@ -941,7 +941,7 @@ object VideoDownloadManager { * @param directoryName if null will use the current path. * @return UniFile / null if createMissingDirectories = false and folder is not found. * */ - private fun UniFile.gotoDir( + fun UniFile.gotoDir( directoryName: String?, createMissingDirectories: Boolean = true ): UniFile? { @@ -995,7 +995,7 @@ object VideoDownloadManager { } } - private fun getDisplayName(name: String, extension: String): String { + fun getDisplayName(name: String, extension: String): String { return "$name.$extension" } @@ -1487,27 +1487,50 @@ object VideoDownloadManager { fun getDownloadFileInfoAndUpdateSettings(context: Context, id: Int): DownloadedFileInfoResult? { val res = getDownloadFileInfo(context, id) - if (res == null) context.removeKey(KEY_DOWNLOAD_INFO, id.toString()) + if (res == null) { + Aria2cHelper.getMetadata(id.toLong())?.let { data -> + if (Aria2cHelper.downloadExist(data)) { + return DownloadedFileInfoResult( + data.downloadedLength, + data.totalLength, + Uri.EMPTY + ) + } + } + + Aria2cHelper.deleteId(id.toLong()) + context.removeKey(KEY_DOWNLOAD_INFO, id.toString()) + } return res } private fun getDownloadFileInfo(context: Context, id: Int): DownloadedFileInfoResult? { try { + println("getDownloadFileInfo:$id") + val info = context.getKey(KEY_DOWNLOAD_INFO, id.toString()) ?: return null val base = basePathToFile(context, info.basePath) - + println("BASE:$info") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) { val cr = context.contentResolver ?: return null + println("CR:$cr") + val fileUri = cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName) ?: return null + println("FILEURI:$fileUri") + val fileLength = cr.getFileLength(fileUri) ?: return null + println("fileLength:$fileLength") + if (fileLength == 0L) return null return DownloadedFileInfoResult(fileLength, info.totalBytes, fileUri) } else { + println("STUFF:$base") val file = base?.gotoDir(info.relativePath, false)?.findFile(info.displayName) + println("file:$file") // val normalPath = context.getNormalPath(getFile(info.relativePath), info.displayName) // val dFile = File(normalPath) @@ -1612,20 +1635,20 @@ object VideoDownloadManager { .mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } .toTypedArray() setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) - } catch (t : Throwable) { + } catch (t: Throwable) { logError(t) } } - /*fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean { - val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? - for (service in manager!!.getRunningServices(Int.MAX_VALUE)) { - if (serviceClass.name == service.service.className) { - return true - } +/*fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean { + val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? + for (service in manager!!.getRunningServices(Int.MAX_VALUE)) { + if (serviceClass.name == service.service.className) { + return true } - return false - }*/ + } + return false +}*/ fun downloadEpisode( context: Context?, diff --git a/app/src/main/res/drawable/circle_shape.xml b/app/src/main/res/drawable/circle_shape_cs3.xml similarity index 100% rename from app/src/main/res/drawable/circle_shape.xml rename to app/src/main/res/drawable/circle_shape_cs3.xml diff --git a/app/src/main/res/drawable/circular_progress_bar.xml b/app/src/main/res/drawable/circular_progress_bar_cs3.xml similarity index 100% rename from app/src/main/res/drawable/circular_progress_bar.xml rename to app/src/main/res/drawable/circular_progress_bar_cs3.xml diff --git a/app/src/main/res/drawable/circular_progress_bar_filled.xml b/app/src/main/res/drawable/circular_progress_bar_filled_cs3.xml similarity index 100% rename from app/src/main/res/drawable/circular_progress_bar_filled.xml rename to app/src/main/res/drawable/circular_progress_bar_filled_cs3.xml diff --git a/app/src/main/res/layout/download_button_layout.xml b/app/src/main/res/layout/download_button_layout.xml new file mode 100644 index 00000000..f1ba7fb9 --- /dev/null +++ b/app/src/main/res/layout/download_button_layout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/download_child_episode.xml b/app/src/main/res/layout/download_child_episode.xml index f2633dd6..c856b1ee 100644 --- a/app/src/main/res/layout/download_child_episode.xml +++ b/app/src/main/res/layout/download_child_episode.xml @@ -1,118 +1,95 @@ + android:layout_height="50dp" + android:layout_marginBottom="5dp" + android:foreground="@drawable/outline_drawable" + android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusRight="@id/download_child_episode_download" + app:cardBackgroundColor="@color/transparent" + app:cardCornerRadius="@dimen/rounded_image_radius" + app:cardElevation="0dp"> + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="5dp" + android:layout_gravity="bottom" + android:layout_marginBottom="-1.5dp" + android:progressBackgroundTint="?attr/colorPrimary" + android:progressTint="?attr/colorPrimary" + tools:progress="50" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:foreground="?android:attr/selectableItemBackgroundBorderless"> + android:id="@+id/download_child_episode_play" + android:layout_gravity="center_vertical" + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:contentDescription="@string/episode_play_img_des" + android:src="@drawable/ic_baseline_play_arrow_24" + android:visibility="gone" /> + + + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + + android:ellipsize="marquee" + android:gravity="center_vertical" + android:marqueeRepeatLimit="marquee_forever" + android:scrollHorizontally="true" + + android:singleLine="true" + android:textColor="?attr/textColor" + tools:text="Episode 1 Episode 1 Episode 1 Episode 1 Episode 1 Episode 1 Episode 1" /> - - + android:id="@+id/download_child_episode_text_extra" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_vertical" + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:gravity="center_vertical" + android:textColor="?attr/grayTextColor" + tools:text="128MB / 237MB" /> - - - - - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/download_header_episode.xml b/app/src/main/res/layout/download_header_episode.xml index da4b3617..b422e8d4 100644 --- a/app/src/main/res/layout/download_header_episode.xml +++ b/app/src/main/res/layout/download_header_episode.xml @@ -1,103 +1,66 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/episode_holder" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="10dp" + android:layout_marginTop="10dp" + android:layout_marginEnd="10dp" + android:foreground="@drawable/outline_drawable" + app:cardBackgroundColor="?attr/boxItemBackground" + app:cardCornerRadius="@dimen/rounded_image_radius"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:orientation="horizontal"> + android:layout_width="70dp" + android:layout_height="104dp"> + android:id="@+id/download_header_poster" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/episode_poster_img_des" + android:scaleType="centerCrop" + tools:src="@drawable/example_poster" /> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginStart="15dp" + android:layout_marginEnd="50dp" + android:orientation="vertical"> - - - - - - - + android:layout_height="wrap_content" + android:textColor="?attr/textColor" + android:textStyle="bold" + tools:text="Perfect Run" /> - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index d26c1b5a..c9b03cd1 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -569,90 +569,12 @@ android:clickable="true" android:focusable="true" android:layout_width="match_parent" />--> - - - - - - - - - - - - - - - - - + - + + \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_large.xml b/app/src/main/res/layout/result_episode_large.xml index 0aeedeea..6bdd7410 100644 --- a/app/src/main/res/layout/result_episode_large.xml +++ b/app/src/main/res/layout/result_episode_large.xml @@ -1,151 +1,130 @@ + app:cardCornerRadius="@dimen/rounded_image_radius"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:orientation="vertical" + android:padding="10dp"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + android:layout_width="126dp" + android:layout_height="72dp" + android:foreground="@drawable/outline_drawable"> + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:nextFocusRight="@id/result_episode_download" + android:scaleType="centerCrop" + tools:src="@drawable/example_poster" /> + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_gravity="center" + android:contentDescription="@string/play_episode" + android:src="@drawable/play_button" /> + android:id="@+id/episode_progress" + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="5dp" + android:layout_gravity="bottom" + android:layout_marginBottom="-1.5dp" + android:progressBackgroundTint="?attr/colorPrimary" + android:progressTint="?attr/colorPrimary" + tools:progress="50" /> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginStart="15dp" + android:layout_marginEnd="50dp" + android:orientation="vertical"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + android:id="@+id/episode_filler" + style="@style/SmallBlackButton" + android:layout_gravity="start" + android:layout_marginEnd="10dp" + android:text="@string/filler" /> + android:id="@+id/episode_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:textColor="?attr/textColor" + android:textStyle="bold" + tools:text="1. Jobless" /> + android:id="@+id/episode_rating" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?attr/grayTextColor" + tools:text="Rated: 8.8" /> + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="end" + android:layout_marginStart="-50dp"> - - - + + + android:id="@+id/episode_descript" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="4" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:textColor="?attr/grayTextColor" + tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart." /> \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d745c7a1..c9a61b37 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -22,6 +22,10 @@ #FFF #000 + #FFF + #000 + + #3d50fa #121213 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2fb9b5b4..6ff90a6b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -49,11 +49,14 @@ true true + @color/colorPrimary @color/colorPrimaryDark + @color/whiteText + @color/colorAccent @color/textColor @color/grayTextColor @@ -65,6 +68,10 @@ #FFF @style/CustomPreferenceThemeOverlay + ?attr/white + ?attr/white + ?attr/white + 0.7 @@ -110,7 +117,7 @@ @color/colorPrimaryBlue #4855A2 #5A6BCB - #5A6BCB + @color/whiteText @color/colorPrimaryBlue @@ -120,7 +127,7 @@ @color/colorPrimaryPurple #4704A3 #7125DB - #7125DB + @color/whiteText @color/colorPrimaryPurple @@ -129,7 +136,7 @@ @color/colorPrimaryGreen #007363 #39C1AE - #39C1AE + @color/blackText @color/colorPrimaryGreen @@ -138,7 +145,7 @@ @color/colorPrimaryGreenApple #319B5A #51C57E - #51C57E + @color/blackText @color/colorPrimaryGreenApple @@ -147,7 +154,7 @@ @color/colorPrimaryRed #B62B2B @color/colorPrimaryRed - @color/colorPrimaryRed + @color/whiteText @color/colorPrimaryRed @@ -157,7 +164,7 @@ @color/colorPrimaryBanana #9B7D31 #C5B251 - #C5A851 + @color/blackText @color/colorPrimaryBanana @@ -166,7 +173,7 @@ @color/colorPrimaryParty #C1495B #FD798C - #BF5968 + @color/blackText @color/colorPrimaryParty @@ -175,7 +182,7 @@ @color/colorPrimaryPink #DD1280 #FF4DAE - #DD1280 + @color/blackText @color/colorPrimaryPink @@ -184,7 +191,7 @@ @color/colorPrimaryCarnationPink #83366f #BD5DA5 - #BD5DA5 + @color/blackText @color/colorPrimaryCarnationPink @@ -194,7 +201,7 @@ @color/colorPrimaryMaroon #370C0C #451010 - #451010 + @color/whiteText @color/colorPrimaryMaroon @@ -204,7 +211,7 @@ @color/colorPrimaryDarkGreen #003d00 #004500 - #004500 + @color/whiteText @color/colorPrimaryDarkGreen @@ -214,7 +221,7 @@ @color/colorPrimaryNavyBlue #000073 #000080 - #000080 + @color/whiteText @color/colorPrimaryNavyBlue @@ -224,7 +231,7 @@ @color/colorPrimaryGrey #484848 #515151 - #515151 + @color/whiteText @color/colorPrimaryGrey @@ -234,7 +241,7 @@ @color/colorPrimaryWhite #CCCCCC #FFFFFF - #FFFFFF + @color/blackText @color/colorPrimaryWhite @@ -244,7 +251,7 @@ @color/colorPrimaryBrown #582700 #622C00 - #622C00 + @color/whiteText @color/colorPrimaryBrown