basic offline manga fragment
This commit is contained in:
parent
c75df942f2
commit
91d869005c
14 changed files with 502 additions and 32 deletions
|
@ -33,6 +33,7 @@ import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PR
|
|||
import java.net.HttpURLConnection
|
||||
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_STARTED
|
||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
|
||||
|
@ -169,6 +170,7 @@ class MangaDownloaderService : Service() {
|
|||
"Please grant notification permission",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
broadcastDownloadFailed(task.chapter)
|
||||
return@withContext
|
||||
}
|
||||
|
||||
|
@ -223,14 +225,14 @@ 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))
|
||||
//downloadsManager.exportDownloads(Download(task.title, task.chapter, Download.Type.MANGA))
|
||||
broadcastDownloadFinished(task.chapter)
|
||||
snackString("${task.title} - ${task.chapter} Download finished")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun saveToDisk(fileName: String, bitmap: Bitmap, title: String, chapter: String) {
|
||||
private fun saveToDisk(fileName: String, bitmap: Bitmap, title: String, chapter: String) {
|
||||
try {
|
||||
// Define the directory within the private external storage space
|
||||
val directory = File(
|
||||
|
@ -262,7 +264,7 @@ class MangaDownloaderService : Service() {
|
|||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val directory = File(
|
||||
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${task.title}/${task.chapter}"
|
||||
"Dantotsu/Manga/${task.title}"
|
||||
)
|
||||
if (!directory.exists()) directory.mkdirs()
|
||||
|
||||
|
@ -272,7 +274,7 @@ class MangaDownloaderService : Service() {
|
|||
SChapterImpl() // Provide an instance of SChapterImpl
|
||||
})
|
||||
.create()
|
||||
val mediaJson = gson.toJson(task.sourceMedia) // Assuming sourceMedia is part of DownloadTask
|
||||
val mediaJson = gson.toJson(task.sourceMedia)
|
||||
val media = gson.fromJson(mediaJson, Media::class.java)
|
||||
if (media != null) {
|
||||
media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") }
|
||||
|
@ -329,6 +331,13 @@ class MangaDownloaderService : Service() {
|
|||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
private fun broadcastDownloadFailed(chapterNumber: String) {
|
||||
val intent = Intent(ACTION_DOWNLOAD_FAILED).apply {
|
||||
putExtra(EXTRA_CHAPTER_NUMBER, chapterNumber)
|
||||
}
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
private val cancelReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == ACTION_CANCEL_DOWNLOAD) {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package ani.dantotsu.download.manga
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import ani.dantotsu.R
|
||||
|
||||
|
||||
class OfflineMangaAdapter(private val context: Context, private val items: List<OfflineMangaModel>) : BaseAdapter() {
|
||||
private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Any? {
|
||||
return items[position]
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var view = convertView
|
||||
if (view == null) {
|
||||
view = inflater.inflate(R.layout.item_media_compact, parent, false)
|
||||
}
|
||||
|
||||
val item = getItem(position) as OfflineMangaModel
|
||||
val imageView = view!!.findViewById<ImageView>(R.id.itemCompactImage)
|
||||
val titleTextView = view.findViewById<TextView>(R.id.itemCompactTitle)
|
||||
val itemScore = view.findViewById<TextView>(R.id.itemCompactScore)
|
||||
val itemScoreBG = view.findViewById<View>(R.id.itemCompactScoreBG)
|
||||
val ongoing = view.findViewById<CardView>(R.id.itemCompactOngoing)
|
||||
// Bind item data to the views
|
||||
// For example:
|
||||
imageView.setImageURI(item.image)
|
||||
titleTextView.text = item.title
|
||||
itemScore.text = item.score
|
||||
if (item.isOngoing) {
|
||||
ongoing.visibility = View.VISIBLE
|
||||
} else {
|
||||
ongoing.visibility = View.GONE
|
||||
}
|
||||
return view
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package ani.dantotsu.download.manga
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.GridView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.updatePaddingRelative
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.Refresh
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.FragmentMangaBinding
|
||||
import ani.dantotsu.download.Download
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.px
|
||||
import ani.dantotsu.statusBarHeight
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.InstanceCreator
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class OfflineMangaFragment: Fragment() {
|
||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||
private var downloads: List<OfflineMangaModel> = listOf()
|
||||
private lateinit var gridView: GridView
|
||||
private lateinit var adapter: OfflineMangaAdapter
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_manga_offline, container, false)
|
||||
gridView = view.findViewById(R.id.gridView)
|
||||
getDownloads()
|
||||
adapter = OfflineMangaAdapter(requireContext(), downloads)
|
||||
gridView.adapter = adapter
|
||||
gridView.setOnItemClickListener { parent, view, position, id ->
|
||||
// Get the OfflineMangaModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineMangaModel
|
||||
val media = downloadManager.mangaDownloads.filter { it.title == item.title }.first()
|
||||
startActivity(
|
||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||
.putExtra("media", getMedia(media))
|
||||
)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
var height = statusBarHeight
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
|
||||
if (displayCutout != null) {
|
||||
if (displayCutout.boundingRects.size > 0) {
|
||||
height = max(
|
||||
statusBarHeight,
|
||||
min(
|
||||
displayCutout.boundingRects[0].width(),
|
||||
displayCutout.boundingRects[0].height()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val refreshLayout = view.findViewById<SwipeRefreshLayout>(R.id.mangaRefresh)
|
||||
refreshLayout.setSlingshotDistance(height + 128)
|
||||
refreshLayout.setProgressViewEndTarget(false, height + 128)
|
||||
refreshLayout.setOnRefreshListener {
|
||||
Refresh.activity[this.hashCode()]!!.postValue(true)
|
||||
}
|
||||
|
||||
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
||||
var visible = false
|
||||
fun animate() {
|
||||
val start = if (visible) 0f else 1f
|
||||
val end = if (!visible) 0f else 1f
|
||||
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
|
||||
duration = 300
|
||||
interpolator = OvershootInterpolator(2f)
|
||||
start()
|
||||
}
|
||||
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
|
||||
duration = 300
|
||||
interpolator = OvershootInterpolator(2f)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
scrollTop.setOnClickListener {
|
||||
//TODO: scroll to top
|
||||
}
|
||||
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
getDownloads()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
downloads = listOf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
downloads = listOf()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
downloads = listOf()
|
||||
}
|
||||
private fun getDownloads() {
|
||||
val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||
val newDownloads = mutableListOf<OfflineMangaModel>()
|
||||
for (title in titles) {
|
||||
val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
|
||||
val download = _downloads.first()
|
||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||
newDownloads += offlineMangaModel
|
||||
}
|
||||
downloads = newDownloads
|
||||
}
|
||||
|
||||
private fun getMedia(download: Download): Media? {
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${download.title}"
|
||||
)
|
||||
//load media.json and convert to media class with gson
|
||||
try {
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||
SChapterImpl() // Provide an instance of SChapterImpl
|
||||
})
|
||||
.create()
|
||||
val media = File(directory, "media.json")
|
||||
val mediaJson = media.readText()
|
||||
return gson.fromJson(mediaJson, Media::class.java)
|
||||
}
|
||||
catch (e: Exception){
|
||||
logger("Error loading media.json: ${e.message}")
|
||||
logger(e.printStackTrace())
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadOfflineMangaModel(download: Download): OfflineMangaModel{
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${download.title}"
|
||||
)
|
||||
//load media.json and convert to media class with gson
|
||||
try {
|
||||
val media = File(directory, "media.json")
|
||||
val mediaJson = media.readText()
|
||||
val mediaModel = getMedia(download)!!
|
||||
val cover = File(directory, "cover.jpg")
|
||||
val coverUri: Uri? = if (cover.exists()) {
|
||||
Uri.fromFile(cover)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val title = mediaModel.nameMAL?:"unknown"
|
||||
val score = if (mediaModel.userScore != 0) mediaModel.userScore.toString() else
|
||||
if (mediaModel.meanScore == null) "?" else mediaModel.meanScore.toString()
|
||||
val isOngoing = false
|
||||
val isUserScored = mediaModel.userScore != 0
|
||||
return OfflineMangaModel(title, score, isOngoing, isUserScored, coverUri)
|
||||
}
|
||||
catch (e: Exception){
|
||||
logger("Error loading media.json: ${e.message}")
|
||||
logger(e.printStackTrace())
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
return OfflineMangaModel("unknown", "0", false, false, null)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package ani.dantotsu.download.manga
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
data class OfflineMangaModel(val title: String, val score: String, val isOngoing: Boolean, val isUserScored: Boolean, val image: Uri?) {
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue