parent acb0225699
author Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> 1698992132 -0500 committer Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> 1698992691 -0500 manga downloading base Update README.md Update README.md Update README.md Update README.md Update README.md
This commit is contained in:
parent
acb0225699
commit
20acd71b1a
24 changed files with 763 additions and 64 deletions
|
@ -22,7 +22,7 @@ data class Media(
|
|||
val userPreferredName: String,
|
||||
|
||||
var cover: String? = null,
|
||||
val banner: String? = null,
|
||||
var banner: String? = null,
|
||||
var relation: String? = null,
|
||||
var popularity: Int? = null,
|
||||
|
||||
|
|
|
@ -3,12 +3,14 @@ package ani.dantotsu.media
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.media.anime.Episode
|
||||
import ani.dantotsu.media.anime.SelectorDialogFragment
|
||||
|
@ -30,6 +32,8 @@ import ani.dantotsu.snackString
|
|||
import ani.dantotsu.tryWithSuspend
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.parsers.AnimeSources
|
||||
import ani.dantotsu.parsers.AniyomiAdapter
|
||||
import ani.dantotsu.parsers.DynamicMangaParser
|
||||
|
@ -44,6 +48,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
class MediaDetailsViewModel : ViewModel() {
|
||||
val scrolledToTop = MutableLiveData(true)
|
||||
|
@ -258,7 +263,28 @@ class MediaDetailsViewModel : ViewModel() {
|
|||
|
||||
private val mangaChapter = MutableLiveData<MangaChapter?>(null)
|
||||
fun getMangaChapter(): LiveData<MangaChapter?> = mangaChapter
|
||||
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean {
|
||||
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, series: String, post: Boolean = true): Boolean {
|
||||
//check if the chapter has been downloaded already
|
||||
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||
if(downloadsManager.mangaDownloads.contains(Download(series, chapter.title!!, Download.Type.MANGA))) {
|
||||
val download = downloadsManager.mangaDownloads.find { it.title == series && it.chapter == chapter.title!! } ?: return false
|
||||
//look in the downloads folder for the chapter and add all the numerically named images to the chapter
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/$series/${chapter.title!!}"
|
||||
)
|
||||
val images = mutableListOf<MangaImage>()
|
||||
directory.listFiles()?.forEach {
|
||||
if (it.nameWithoutExtension.toIntOrNull() != null) {
|
||||
images.add(MangaImage(FileUrl(it.absolutePath), false))
|
||||
}
|
||||
}
|
||||
//sort the images by name
|
||||
images.sortBy { it.url.url }
|
||||
chapter.addImages(images)
|
||||
if (post) mangaChapter.postValue(chapter)
|
||||
return true
|
||||
}
|
||||
return tryWithSuspend(true) {
|
||||
chapter.addImages(
|
||||
mangaReadSources?.get(selected.sourceIndex)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
|
||||
|
|
|
@ -10,6 +10,9 @@ import android.os.Build
|
|||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.LruCache
|
||||
import android.widget.Toast
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.snackString
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -26,22 +29,23 @@ data class ImageData(
|
|||
try {
|
||||
// Fetch the image
|
||||
val response = httpSource.getImage(page)
|
||||
println("Response: ${response.code}")
|
||||
println("Response: ${response.message}")
|
||||
logger("Response: ${response.code}")
|
||||
logger("Response: ${response.message}")
|
||||
|
||||
// Convert the Response to an InputStream
|
||||
val inputStream = response.body?.byteStream()
|
||||
val inputStream = response.body.byteStream()
|
||||
|
||||
// Convert InputStream to Bitmap
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
|
||||
inputStream?.close()
|
||||
saveImage(bitmap, context.contentResolver, page.imageUrl!!, Bitmap.CompressFormat.JPEG, 100)
|
||||
inputStream.close()
|
||||
//saveImage(bitmap, context.contentResolver, page.imageUrl!!, Bitmap.CompressFormat.JPEG, 100)
|
||||
|
||||
return@withContext bitmap
|
||||
} catch (e: Exception) {
|
||||
// Handle any exceptions
|
||||
println("An error occurred: ${e.message}")
|
||||
logger("An error occurred: ${e.message}")
|
||||
snackString("An error occurred: ${e.message}")
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +61,7 @@ fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String
|
|||
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga")
|
||||
}
|
||||
|
||||
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
val uri: Uri? = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
uri?.let {
|
||||
contentResolver.openOutputStream(it)?.use { os ->
|
||||
|
@ -65,7 +69,7 @@ fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String
|
|||
}
|
||||
}
|
||||
} else {
|
||||
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
|
||||
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Manga")
|
||||
if (!directory.exists()) {
|
||||
directory.mkdirs()
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ data class MangaChapter(
|
|||
var link: String,
|
||||
var title: String? = null,
|
||||
var description: String? = null,
|
||||
var sChapter: SChapter
|
||||
var sChapter: SChapter,
|
||||
) : Serializable {
|
||||
constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.databinding.ItemChapterListBinding
|
||||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||
import ani.dantotsu.media.Media
|
||||
|
@ -48,12 +49,71 @@ class MangaChapterAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private val activeDownloads = mutableSetOf<String>()
|
||||
private val downloadedChapters = mutableSetOf<String>()
|
||||
|
||||
fun startDownload(chapterNumber: String) {
|
||||
activeDownloads.add(chapterNumber)
|
||||
// Find the position of the chapter and notify only that item
|
||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||
if (position != -1) {
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopDownload(chapterNumber: String) {
|
||||
activeDownloads.remove(chapterNumber)
|
||||
downloadedChapters.add(chapterNumber)
|
||||
// Find the position of the chapter and notify only that item
|
||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||
if (position != -1) {
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteDownload(chapterNumber: String) {
|
||||
downloadedChapters.remove(chapterNumber)
|
||||
// Find the position of the chapter and notify only that item
|
||||
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||
if (position != -1) {
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ChapterListViewHolder(val binding: ItemChapterListBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(chapterNumber: String) {
|
||||
if (activeDownloads.contains(chapterNumber)) {
|
||||
// Show spinner
|
||||
binding.itemDownload.setImageResource(R.drawable.spinner_icon_manga)
|
||||
} 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)
|
||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
||||
}
|
||||
binding.itemDownload.setOnClickListener {
|
||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
||||
val chapterNumber = arr[bindingAdapterPosition].number
|
||||
if(activeDownloads.contains(chapterNumber)) {
|
||||
fragment.onMangaChapterStopDownloadClick(chapterNumber)
|
||||
return@setOnClickListener
|
||||
}else if(downloadedChapters.contains(chapterNumber)) {
|
||||
fragment.onMangaChapterRemoveDownloadClick(chapterNumber)
|
||||
return@setOnClickListener
|
||||
}else {
|
||||
fragment.onMangaChapterDownloadClick(chapterNumber)
|
||||
startDownload(chapterNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +140,7 @@ class MangaChapterAdapter(
|
|||
is ChapterListViewHolder -> {
|
||||
val binding = holder.binding
|
||||
val ep = arr[position]
|
||||
holder.bind(ep.number)
|
||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
||||
binding.itemChapterNumber.text = ep.number
|
||||
if (!ep.title.isNullOrEmpty()) {
|
||||
|
|
|
@ -2,6 +2,11 @@ package ani.dantotsu.media.manga
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
|
@ -10,6 +15,7 @@ import android.view.ViewGroup
|
|||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.math.MathUtils.clamp
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
|
@ -20,10 +26,15 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||
import ani.dantotsu.download.manga.ServiceDataSingleton
|
||||
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.parsers.DynamicMangaParser
|
||||
import ani.dantotsu.parsers.HMangaSources
|
||||
import ani.dantotsu.parsers.MangaParser
|
||||
import ani.dantotsu.parsers.MangaSources
|
||||
|
@ -41,8 +52,12 @@ import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -62,6 +77,8 @@ open class MangaReadFragment : Fragment() {
|
|||
private lateinit var headerAdapter: MangaReadAdapter
|
||||
private lateinit var chapterAdapter: MangaChapterAdapter
|
||||
|
||||
val downloadManager = Injekt.get<DownloadsManager>()
|
||||
|
||||
var screenWidth = 0f
|
||||
private var progress = View.VISIBLE
|
||||
|
||||
|
@ -81,6 +98,11 @@ open class MangaReadFragment : Fragment() {
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val intentFilter = IntentFilter().apply {
|
||||
addAction(ACTION_DOWNLOAD_STARTED)
|
||||
addAction(ACTION_DOWNLOAD_FINISHED)
|
||||
}
|
||||
requireContext().registerReceiver(downloadStatusReceiver, intentFilter)
|
||||
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
||||
screenWidth = resources.displayMetrics.widthPixels.dp
|
||||
|
||||
|
@ -132,6 +154,10 @@ open class MangaReadFragment : Fragment() {
|
|||
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
||||
chapterAdapter = MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
||||
|
||||
for (download in downloadManager.mangaDownloads){
|
||||
chapterAdapter.stopDownload(download.chapter)
|
||||
}
|
||||
|
||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter)
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
@ -325,6 +351,57 @@ open class MangaReadFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
fun onMangaChapterDownloadClick(i: String) {
|
||||
model.continueMedia = false
|
||||
media.manga?.chapters?.get(i)?.let { chapter ->
|
||||
val parser = model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser
|
||||
parser?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
// Fetch the image list and set it in the singleton
|
||||
ServiceDataSingleton.imageData = parser.imageList("", chapter.sChapter)
|
||||
|
||||
// Now that imageData is set, start the service
|
||||
ServiceDataSingleton.sourceMedia = media
|
||||
val intent = Intent(context, MangaDownloaderService::class.java).apply {
|
||||
putExtra("title", media.nameMAL)
|
||||
putExtra("chapter", chapter.title)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
chapterAdapter.startDownload(i)
|
||||
ContextCompat.startForegroundService(requireContext(), intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onMangaChapterRemoveDownloadClick(i: String){
|
||||
downloadManager.removeDownload(Download(media.nameMAL!!, i, Download.Type.MANGA))
|
||||
chapterAdapter.deleteDownload(i)
|
||||
}
|
||||
fun onMangaChapterStopDownloadClick(i: String) {
|
||||
val intent = Intent(requireContext(), MangaDownloaderService::class.java)
|
||||
requireContext().stopService(intent)
|
||||
downloadManager.removeDownload(Download(media.nameMAL!!, i, Download.Type.MANGA))
|
||||
chapterAdapter.deleteDownload(i)
|
||||
}
|
||||
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
ACTION_DOWNLOAD_STARTED -> {
|
||||
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||
chapterNumber?.let { chapterAdapter.startDownload(it) }
|
||||
}
|
||||
ACTION_DOWNLOAD_FINISHED -> {
|
||||
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||
chapterNumber?.let { chapterAdapter.stopDownload(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun reload() {
|
||||
val selected = model.loadSelected(media)
|
||||
|
@ -353,6 +430,7 @@ open class MangaReadFragment : Fragment() {
|
|||
override fun onDestroy() {
|
||||
model.mangaReadSources?.flushText()
|
||||
super.onDestroy()
|
||||
requireContext().unregisterReceiver(downloadStatusReceiver)
|
||||
}
|
||||
|
||||
private var state: Parcelable? = null
|
||||
|
@ -366,4 +444,10 @@ open class MangaReadFragment : Fragment() {
|
|||
super.onPause()
|
||||
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_DOWNLOAD_STARTED = "ani.dantotsu.ACTION_DOWNLOAD_STARTED"
|
||||
const val ACTION_DOWNLOAD_FINISHED = "ani.dantotsu.ACTION_DOWNLOAD_FINISHED"
|
||||
const val EXTRA_CHAPTER_NUMBER = "extra_chapter_number"
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.net.Uri
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
@ -29,6 +30,7 @@ import kotlinx.coroutines.withContext
|
|||
import ani.dantotsu.media.manga.MangaCache
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
abstract class BaseImageAdapter(
|
||||
val activity: MangaReaderActivity,
|
||||
|
@ -151,8 +153,9 @@ abstract class BaseImageAdapter(
|
|||
Glide.with(this@loadBitmap)
|
||||
.asBitmap()
|
||||
.let {
|
||||
if (link.url.startsWith("file://")) {
|
||||
it.load(link.url)
|
||||
val fileUri = Uri.fromFile(File(link.url)).toString()
|
||||
if (fileUri.startsWith("file://")) {
|
||||
it.load(fileUri)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
} else {
|
||||
|
|
|
@ -47,7 +47,7 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
|
|||
loaded = true
|
||||
binding.selectorAutoText.text = chp.title
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
if(model.loadMangaChapterImages(chp, m.selected!!)) {
|
||||
if(model.loadMangaChapterImages(chp, m.selected!!, m.nameMAL!!)) {
|
||||
val activity = currActivity()
|
||||
activity?.runOnUiThread {
|
||||
tryWith { dismiss() }
|
||||
|
|
|
@ -311,7 +311,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
scope.launch(Dispatchers.IO) { model.loadMangaChapterImages(chapter, media.selected!!) }
|
||||
scope.launch(Dispatchers.IO) { model.loadMangaChapterImages(chapter, media.selected!!, media.nameMAL!!) }
|
||||
}
|
||||
|
||||
private val snapHelper = PagerSnapHelper()
|
||||
|
@ -700,6 +700,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
model.loadMangaChapterImages(
|
||||
chapters[chaptersArr.getOrNull(currentChapterIndex + 1) ?: return@launch]!!,
|
||||
media.selected!!,
|
||||
media.nameMAL!!,
|
||||
false
|
||||
)
|
||||
loading = false
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue