diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index 41a249c2..60148260 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -9,6 +9,7 @@ import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.Bitmap import android.net.Uri +import android.os.Build import android.os.Environment import android.os.IBinder import android.widget.Toast @@ -35,6 +36,7 @@ import java.net.URL import androidx.core.content.ContextCompat import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED +import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROGRESS import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER import ani.dantotsu.snackString @@ -160,23 +162,20 @@ class MangaDownloaderService : Service() { suspend fun download(task: DownloadTask) { withContext(Dispatchers.Main) { - if (ContextCompat.checkSelfPermission( + val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( this@MangaDownloaderService, Manifest.permission.POST_NOTIFICATIONS - ) != PackageManager.PERMISSION_GRANTED - ) { - Toast.makeText( - this@MangaDownloaderService, - "Please grant notification permission", - Toast.LENGTH_SHORT - ).show() - broadcastDownloadFailed(task.chapter) - return@withContext + ) == PackageManager.PERMISSION_GRANTED + } else { + true } val deferredList = mutableListOf>() builder.setContentText("Downloading ${task.title} - ${task.chapter}") - notificationManager.notify(NOTIFICATION_ID, builder.build()) + if (notifi) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } // Loop through each ImageData object from the task var farthest = 0 @@ -208,7 +207,10 @@ class MangaDownloaderService : Service() { } farthest++ builder.setProgress(task.imageData.size, farthest, false) - notificationManager.notify(NOTIFICATION_ID, builder.build()) + broadcastDownloadProgress(task.chapter, farthest * 100 / task.imageData.size) + if (notifi) { + notificationManager.notify(NOTIFICATION_ID, builder.build()) + } bitmap } @@ -225,7 +227,6 @@ class MangaDownloaderService : Service() { saveMediaInfo(task) downloadsManager.addDownload(Download(task.title, task.chapter, Download.Type.MANGA)) - //downloadsManager.exportDownloads(Download(task.title, task.chapter, Download.Type.MANGA)) broadcastDownloadFinished(task.chapter) snackString("${task.title} - ${task.chapter} Download finished") } @@ -260,7 +261,7 @@ class MangaDownloaderService : Service() { } } - fun saveMediaInfo(task: DownloadTask) { + private fun saveMediaInfo(task: DownloadTask) { GlobalScope.launch(Dispatchers.IO) { val directory = File( getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), @@ -289,7 +290,7 @@ class MangaDownloaderService : Service() { } - suspend fun downloadImage(url: String, directory: File, name: String): String? = withContext(Dispatchers.IO) { + private suspend fun downloadImage(url: String, directory: File, name: String): String? = withContext(Dispatchers.IO) { var connection: HttpURLConnection? = null println("Downloading url $url") try { @@ -338,6 +339,14 @@ class MangaDownloaderService : Service() { sendBroadcast(intent) } + private fun broadcastDownloadProgress(chapterNumber: String, progress: Int) { + val intent = Intent(ACTION_DOWNLOAD_PROGRESS).apply { + putExtra(EXTRA_CHAPTER_NUMBER, chapterNumber) + putExtra("progress", progress) + } + sendBroadcast(intent) + } + private val cancelReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == ACTION_CANCEL_DOWNLOAD) { diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index a7c0477b..35865f2c 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -57,6 +57,7 @@ class OfflineMangaFragment: Fragment() { startActivity( Intent(requireContext(), MediaDetailsActivity::class.java) .putExtra("media", getMedia(media)) + .putExtra("download", true) ) } diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 4d295cfc..874e0af5 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -120,7 +120,8 @@ ThemeManager(this).applyTheme() viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings)) var media: Media = intent.getSerialized("media") ?: return - media.selected = model.loadSelected(media) + val isDownload = intent.getBooleanExtra("download", false) + media.selected = model.loadSelected(media, isDownload) binding.mediaCoverImage.loadImage(media.cover) binding.mediaCoverImage.setOnLongClickListener { @@ -326,7 +327,7 @@ ThemeManager(this).applyTheme() tabLayout.setOnItemSelectedListener { item -> selectFromID(item.itemId) viewPager.setCurrentItem(selected, false) - val sel = model.loadSelected(media) + val sel = model.loadSelected(media, isDownload) sel.window = selected model.saveSelected(media.id, sel, this) true diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index b730c87c..70083be2 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -58,7 +58,7 @@ class MediaDetailsViewModel : ViewModel() { } - fun loadSelected(media: Media): Selected { + fun loadSelected(media: Media, isDownload: Boolean = false): Selected { val sharedPreferences = Injekt.get() val data = loadData("${media.id}-select") ?: Selected().let { it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) { @@ -69,6 +69,12 @@ class MediaDetailsViewModel : ViewModel() { saveSelected(media.id, it) it } + if (isDownload) { + data.sourceIndex = when (media.anime != null) { + true -> AnimeSources.list.size - 1 + else -> MangaSources.list.size - 1 + } + } return data } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt index d0637c89..d30155d3 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapter.kt @@ -12,6 +12,7 @@ data class MangaChapter( var title: String? = null, var description: String? = null, var sChapter: SChapter, + var progress: String? = "" ) : Serializable { constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt index 1703336f..78b9e69f 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt @@ -29,7 +29,15 @@ class MangaChapterAdapter( false ) ) - 0 -> ChapterListViewHolder(ItemChapterListBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + + 0 -> ChapterListViewHolder( + ItemChapterListBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + else -> throw IllegalArgumentException() } } @@ -40,7 +48,8 @@ class MangaChapterAdapter( override fun getItemCount(): Int = arr.size - inner class ChapterCompactViewHolder(val binding: ItemEpisodeCompactBinding) : RecyclerView.ViewHolder(binding.root) { + inner class ChapterCompactViewHolder(val binding: ItemEpisodeCompactBinding) : + RecyclerView.ViewHolder(binding.root) { init { itemView.setOnClickListener { if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) @@ -90,19 +99,39 @@ class MangaChapterAdapter( } } - inner class ChapterListViewHolder(val binding: ItemChapterListBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(chapterNumber: String) { + fun updateDownloadProgress(chapterNumber: String, progress: Int) { + // Find the position of the chapter and notify only that item + val position = arr.indexOfFirst { it.number == chapterNumber } + if (position != -1) { + arr[position].progress = "Downloading: ${progress}%" + + notifyItemChanged(position) + } + } + + inner class ChapterListViewHolder(val binding: ItemChapterListBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(chapterNumber: String, progress: String?) { + if (progress != null) { + binding.itemChapterTitle.visibility = View.VISIBLE + binding.itemChapterTitle.text = "$progress" + }else{ + binding.itemChapterTitle.visibility = View.GONE + binding.itemChapterTitle.text = "" + } if (activeDownloads.contains(chapterNumber)) { // Show spinner binding.itemDownload.setImageResource(R.drawable.ic_round_refresh_24) - } else if(downloadedChapters.contains(chapterNumber)) { + } else if (downloadedChapters.contains(chapterNumber)) { // Show checkmark binding.itemDownload.setImageResource(R.drawable.ic_check) } else { // Show download icon binding.itemDownload.setImageResource(R.drawable.ic_round_download_24) } + } + init { itemView.setOnClickListener { if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) @@ -111,13 +140,13 @@ class MangaChapterAdapter( binding.itemDownload.setOnClickListener { if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) { val chapterNumber = arr[bindingAdapterPosition].number - if(activeDownloads.contains(chapterNumber)) { + if (activeDownloads.contains(chapterNumber)) { fragment.onMangaChapterStopDownloadClick(chapterNumber) return@setOnClickListener - }else if(downloadedChapters.contains(chapterNumber)) { + } else if (downloadedChapters.contains(chapterNumber)) { fragment.onMangaChapterRemoveDownloadClick(chapterNumber) return@setOnClickListener - }else { + } else { fragment.onMangaChapterDownloadClick(chapterNumber) startDownload(chapterNumber) } @@ -136,25 +165,31 @@ class MangaChapterAdapter( val parsedNumber = MangaNameAdapter.findChapterNumber(ep.number)?.toInt() binding.itemEpisodeNumber.text = parsedNumber?.toString() ?: ep.number if (media.userProgress != null) { - if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat()) + if ((MangaNameAdapter.findChapterNumber(ep.number) + ?: 9999f) <= media.userProgress!!.toFloat() + ) binding.itemEpisodeViewedCover.visibility = View.VISIBLE else { binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeCont.setOnLongClickListener { - updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString()) + updateProgress( + media, + MangaNameAdapter.findChapterNumber(ep.number).toString() + ) true } } } } - is ChapterListViewHolder -> { + + is ChapterListViewHolder -> { val binding = holder.binding val ep = arr[position] - holder.bind(ep.number) + holder.bind(ep.number, ep.progress) setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) binding.itemChapterNumber.text = ep.number - if (!ep.title.isNullOrEmpty()) { - binding.itemChapterTitle.text = ep.title + /*if (!ep.progress.isNullOrEmpty()) { + binding.itemChapterTitle.text = ep.progress binding.itemChapterTitle.setOnLongClickListener { binding.itemChapterTitle.maxLines.apply { binding.itemChapterTitle.maxLines = if (this == 1) 3 else 1 @@ -162,17 +197,22 @@ class MangaChapterAdapter( true } binding.itemChapterTitle.visibility = View.VISIBLE - } else binding.itemChapterTitle.visibility = View.GONE + } else*/ binding.itemChapterTitle.visibility = View.VISIBLE if (media.userProgress != null) { - if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat()) { + if ((MangaNameAdapter.findChapterNumber(ep.number) + ?: 9999f) <= media.userProgress!!.toFloat() + ) { binding.itemEpisodeViewedCover.visibility = View.VISIBLE binding.itemEpisodeViewed.visibility = View.VISIBLE } else { binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeViewed.visibility = View.GONE binding.root.setOnLongClickListener { - updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString()) + updateProgress( + media, + MangaNameAdapter.findChapterNumber(ep.number).toString() + ) true } } @@ -189,4 +229,4 @@ class MangaChapterAdapter( } -} +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 1169d682..a622a6b2 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -7,6 +7,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.os.Parcelable @@ -63,6 +64,8 @@ import uy.kohesive.injekt.api.get import kotlin.math.ceil import kotlin.math.max import kotlin.math.roundToInt +import android.Manifest +import androidx.core.app.ActivityCompat open class MangaReadFragment : Fragment() { private var _binding: FragmentAnimeWatchBinding? = null @@ -103,10 +106,12 @@ open class MangaReadFragment : Fragment() { val intentFilter = IntentFilter().apply { addAction(ACTION_DOWNLOAD_STARTED) addAction(ACTION_DOWNLOAD_FINISHED) + addAction(ACTION_DOWNLOAD_FAILED) + addAction(ACTION_DOWNLOAD_PROGRESS) } ContextCompat.registerReceiver(requireContext(), downloadStatusReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED) - + binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) screenWidth = resources.displayMetrics.widthPixels.dp @@ -355,6 +360,16 @@ open class MangaReadFragment : Fragment() { } fun onMangaChapterDownloadClick(i: String) { + if (!isNotificationPermissionGranted()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityCompat.requestPermissions( + requireActivity(), + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + 1 + ) + } + } + model.continueMedia = false media.manga?.chapters?.get(i)?.let { chapter -> val parser = model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser @@ -392,6 +407,15 @@ open class MangaReadFragment : Fragment() { } } + private fun isNotificationPermissionGranted(): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return ActivityCompat.checkSelfPermission( + requireContext(), + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } + return true + } fun onMangaChapterRemoveDownloadClick(i: String){ @@ -426,6 +450,11 @@ open class MangaReadFragment : Fragment() { chapterAdapter.removeDownload(it) } } + ACTION_DOWNLOAD_PROGRESS -> { + val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER) + val progress = intent.getIntExtra("progress", 0) + chapterNumber?.let { chapterAdapter.updateDownloadProgress(it, progress) } + } } } } @@ -478,6 +507,7 @@ open class MangaReadFragment : Fragment() { const val ACTION_DOWNLOAD_STARTED = "ani.dantotsu.ACTION_DOWNLOAD_STARTED" const val ACTION_DOWNLOAD_FINISHED = "ani.dantotsu.ACTION_DOWNLOAD_FINISHED" const val ACTION_DOWNLOAD_FAILED = "ani.dantotsu.ACTION_DOWNLOAD_FAILED" + const val ACTION_DOWNLOAD_PROGRESS = "ani.dantotsu.ACTION_DOWNLOAD_PROGRESS" const val EXTRA_CHAPTER_NUMBER = "extra_chapter_number" } } \ No newline at end of file