mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-31 03:11:01 +00:00
feat: novel to new system | load freezing
This commit is contained in:
@@ -4,7 +4,6 @@ package ani.dantotsu.download.anime
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
@@ -25,6 +24,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
@@ -33,6 +33,7 @@ import ani.dantotsu.currActivity
|
|||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.download.DownloadsManager.Companion.findValidName
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
@@ -56,9 +57,13 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|||||||
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
|
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
||||||
|
|
||||||
@@ -67,6 +72,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
private lateinit var gridView: GridView
|
private lateinit var gridView: GridView
|
||||||
private lateinit var adapter: OfflineAnimeAdapter
|
private lateinit var adapter: OfflineAnimeAdapter
|
||||||
private lateinit var total: TextView
|
private lateinit var total: TextView
|
||||||
|
private var downloadsJob: Job = Job()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -113,10 +119,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
})
|
})
|
||||||
var style: Int = PrefManager.getVal(PrefName.OfflineView)
|
var style: Int = PrefManager.getVal(PrefName.OfflineView)
|
||||||
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
||||||
val layoutcompact = view.findViewById<ImageView>(R.id.downloadedGrid)
|
val layoutCompact = view.findViewById<ImageView>(R.id.downloadedGrid)
|
||||||
var selected = when (style) {
|
var selected = when (style) {
|
||||||
0 -> layoutList
|
0 -> layoutList
|
||||||
1 -> layoutcompact
|
1 -> layoutCompact
|
||||||
else -> layoutList
|
else -> layoutList
|
||||||
}
|
}
|
||||||
selected.alpha = 1f
|
selected.alpha = 1f
|
||||||
@@ -137,7 +143,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
grid()
|
grid()
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutcompact.setOnClickListener {
|
layoutCompact.setOnClickListener {
|
||||||
selected(it as ImageView)
|
selected(it as ImageView)
|
||||||
style = 1
|
style = 1
|
||||||
PrefManager.setVal(PrefName.OfflineView, style)
|
PrefManager.setVal(PrefName.OfflineView, style)
|
||||||
@@ -157,11 +163,11 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
private fun grid() {
|
private fun grid() {
|
||||||
gridView.visibility = View.VISIBLE
|
gridView.visibility = View.VISIBLE
|
||||||
getDownloads()
|
|
||||||
val fadeIn = AlphaAnimation(0f, 1f)
|
val fadeIn = AlphaAnimation(0f, 1f)
|
||||||
fadeIn.duration = 300 // animations pog
|
fadeIn.duration = 300 // animations pog
|
||||||
gridView.layoutAnimation = LayoutAnimationController(fadeIn)
|
gridView.layoutAnimation = LayoutAnimationController(fadeIn)
|
||||||
adapter = OfflineAnimeAdapter(requireContext(), downloads, this)
|
adapter = OfflineAnimeAdapter(requireContext(), downloads, this)
|
||||||
|
getDownloads()
|
||||||
gridView.adapter = adapter
|
gridView.adapter = adapter
|
||||||
gridView.scheduleLayoutAnimation()
|
gridView.scheduleLayoutAnimation()
|
||||||
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
|
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
|
||||||
@@ -169,20 +175,22 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
// Get the OfflineAnimeModel that was clicked
|
// Get the OfflineAnimeModel that was clicked
|
||||||
val item = adapter.getItem(position) as OfflineAnimeModel
|
val item = adapter.getItem(position) as OfflineAnimeModel
|
||||||
val media =
|
val media =
|
||||||
downloadManager.animeDownloadedTypes.firstOrNull { it.title == item.title }
|
downloadManager.animeDownloadedTypes.firstOrNull { it.title == item.title.findValidName() }
|
||||||
media?.let {
|
media?.let {
|
||||||
val mediaModel = getMedia(it)
|
lifecycleScope.launch {
|
||||||
if (mediaModel == null) {
|
val mediaModel = getMedia(it)
|
||||||
snackString("Error loading media.json")
|
if (mediaModel == null) {
|
||||||
return@let
|
snackString("Error loading media.json")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
MediaDetailsActivity.mediaSingleton = mediaModel
|
||||||
|
ContextCompat.startActivity(
|
||||||
|
requireActivity(),
|
||||||
|
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||||
|
.putExtra("download", true),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
MediaDetailsActivity.mediaSingleton = mediaModel
|
|
||||||
ContextCompat.startActivity(
|
|
||||||
requireActivity(),
|
|
||||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
|
||||||
.putExtra("download", true),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
} ?: run {
|
} ?: run {
|
||||||
snackString("no media found")
|
snackString("no media found")
|
||||||
}
|
}
|
||||||
@@ -206,8 +214,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
snackString("No media found") // if this happens, terrible things have happened
|
snackString("No media found") // if this happens, terrible things have happened
|
||||||
}
|
}
|
||||||
getDownloads()
|
getDownloads()
|
||||||
adapter.setItems(downloads)
|
|
||||||
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
|
|
||||||
}
|
}
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
builder.setNegativeButton("No") { _, _ ->
|
||||||
// Do nothing
|
// Do nothing
|
||||||
@@ -235,7 +241,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
|
|
||||||
gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
|
gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
|
||||||
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
|
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
|
||||||
// Implement behavior for different scroll states if needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
@@ -258,7 +263,6 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
getDownloads()
|
getDownloads()
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@@ -278,20 +282,34 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
|
|
||||||
private fun getDownloads() {
|
private fun getDownloads() {
|
||||||
downloads = listOf()
|
downloads = listOf()
|
||||||
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
|
if (downloadsJob.isActive) {
|
||||||
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
downloadsJob.cancel()
|
||||||
for (title in animeTitles) {
|
}
|
||||||
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
|
downloadsJob = Job()
|
||||||
val download = tDownloads.first()
|
CoroutineScope(Dispatchers.IO + downloadsJob).launch {
|
||||||
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
val animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
|
||||||
newAnimeDownloads += offlineAnimeModel
|
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
||||||
|
for (title in animeTitles) {
|
||||||
|
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
|
||||||
|
val download = tDownloads.first()
|
||||||
|
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
||||||
|
newAnimeDownloads += offlineAnimeModel
|
||||||
|
}
|
||||||
|
downloads = newAnimeDownloads
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
adapter.setItems(downloads)
|
||||||
|
total.text = if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
downloads = newAnimeDownloads
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
/**
|
||||||
val type = downloadedType.type.asText()
|
* Load media.json file from the directory and convert it to Media class
|
||||||
//load media.json and convert to media class with gson
|
* @param downloadedType DownloadedType object
|
||||||
|
* @return Media object
|
||||||
|
*/
|
||||||
|
private suspend fun getMedia(downloadedType: DownloadedType): Media? {
|
||||||
return try {
|
return try {
|
||||||
val directory = DownloadsManager.getSubDirectory(
|
val directory = DownloadsManager.getSubDirectory(
|
||||||
context ?: currContext()!!, downloadedType.type,
|
context ?: currContext()!!, downloadedType.type,
|
||||||
@@ -310,10 +328,11 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
.create()
|
.create()
|
||||||
val media = directory?.findFile("media.json")
|
val media = directory?.findFile("media.json")
|
||||||
?: return null
|
?: return null
|
||||||
val mediaJson = media.openInputStream(context?:currContext()!!)?.bufferedReader().use {
|
val mediaJson =
|
||||||
it?.readText()
|
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
|
||||||
}
|
it?.readText()
|
||||||
?: return null
|
}
|
||||||
|
?: return null
|
||||||
gson.fromJson(mediaJson, Media::class.java)
|
gson.fromJson(mediaJson, Media::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("Error loading media.json: ${e.message}")
|
Logger.log("Error loading media.json: ${e.message}")
|
||||||
@@ -323,9 +342,13 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
|
/**
|
||||||
|
* Load OfflineAnimeModel from the directory
|
||||||
|
* @param downloadedType DownloadedType object
|
||||||
|
* @return OfflineAnimeModel object
|
||||||
|
*/
|
||||||
|
private suspend fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
|
||||||
val type = downloadedType.type.asText()
|
val type = downloadedType.type.asText()
|
||||||
//load media.json and convert to media class with gson
|
|
||||||
try {
|
try {
|
||||||
val directory = DownloadsManager.getSubDirectory(
|
val directory = DownloadsManager.getSubDirectory(
|
||||||
context ?: currContext()!!, downloadedType.type,
|
context ?: currContext()!!, downloadedType.type,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package ani.dantotsu.download.manga
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
@@ -23,6 +22,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginBottom
|
import androidx.core.view.marginBottom
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.bottomBar
|
import ani.dantotsu.bottomBar
|
||||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
@@ -50,9 +50,13 @@ import com.google.gson.GsonBuilder
|
|||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
||||||
|
|
||||||
@@ -61,6 +65,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
private lateinit var gridView: GridView
|
private lateinit var gridView: GridView
|
||||||
private lateinit var adapter: OfflineMangaAdapter
|
private lateinit var adapter: OfflineMangaAdapter
|
||||||
private lateinit var total: TextView
|
private lateinit var total: TextView
|
||||||
|
private var downloadsJob: Job = Job()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -150,11 +155,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
|
|
||||||
private fun grid() {
|
private fun grid() {
|
||||||
gridView.visibility = View.VISIBLE
|
gridView.visibility = View.VISIBLE
|
||||||
getDownloads()
|
|
||||||
val fadeIn = AlphaAnimation(0f, 1f)
|
val fadeIn = AlphaAnimation(0f, 1f)
|
||||||
fadeIn.duration = 300 // animations pog
|
fadeIn.duration = 300 // animations pog
|
||||||
gridView.layoutAnimation = LayoutAnimationController(fadeIn)
|
gridView.layoutAnimation = LayoutAnimationController(fadeIn)
|
||||||
adapter = OfflineMangaAdapter(requireContext(), downloads, this)
|
adapter = OfflineMangaAdapter(requireContext(), downloads, this)
|
||||||
|
getDownloads()
|
||||||
gridView.adapter = adapter
|
gridView.adapter = adapter
|
||||||
gridView.scheduleLayoutAnimation()
|
gridView.scheduleLayoutAnimation()
|
||||||
total.text =
|
total.text =
|
||||||
@@ -166,14 +171,15 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
|
downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title }
|
||||||
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
|
?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title }
|
||||||
media?.let {
|
media?.let {
|
||||||
|
lifecycleScope.launch {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||||
.putExtra("media", getMedia(it))
|
.putExtra("media", getMedia(it))
|
||||||
.putExtra("download", true),
|
.putExtra("download", true),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
snackString("no media found")
|
snackString("no media found")
|
||||||
}
|
}
|
||||||
@@ -196,9 +202,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
builder.setPositiveButton("Yes") { _, _ ->
|
builder.setPositiveButton("Yes") { _, _ ->
|
||||||
downloadManager.removeMedia(item.title, type)
|
downloadManager.removeMedia(item.title, type)
|
||||||
getDownloads()
|
getDownloads()
|
||||||
adapter.setItems(downloads)
|
|
||||||
total.text =
|
|
||||||
if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List"
|
|
||||||
}
|
}
|
||||||
builder.setNegativeButton("No") { _, _ ->
|
builder.setNegativeButton("No") { _, _ ->
|
||||||
// Do nothing
|
// Do nothing
|
||||||
@@ -227,7 +230,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
|
|
||||||
gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
|
gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
|
||||||
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
|
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
|
||||||
// Implement behavior for different scroll states if needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
@@ -250,7 +252,6 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
getDownloads()
|
getDownloads()
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@@ -270,33 +271,51 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
|
|
||||||
private fun getDownloads() {
|
private fun getDownloads() {
|
||||||
downloads = listOf()
|
downloads = listOf()
|
||||||
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
|
if (downloadsJob.isActive) {
|
||||||
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
downloadsJob.cancel()
|
||||||
for (title in mangaTitles) {
|
|
||||||
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
|
|
||||||
val download = tDownloads.first()
|
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
|
||||||
newMangaDownloads += offlineMangaModel
|
|
||||||
}
|
}
|
||||||
downloads = newMangaDownloads
|
downloads = listOf()
|
||||||
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
|
downloadsJob = Job()
|
||||||
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
CoroutineScope(Dispatchers.IO + downloadsJob).launch {
|
||||||
for (title in novelTitles) {
|
val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct()
|
||||||
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
|
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
val download = tDownloads.first()
|
for (title in mangaTitles) {
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title }
|
||||||
newNovelDownloads += offlineMangaModel
|
val download = tDownloads.first()
|
||||||
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
|
newMangaDownloads += offlineMangaModel
|
||||||
|
}
|
||||||
|
downloads = newMangaDownloads
|
||||||
|
val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct()
|
||||||
|
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
|
for (title in novelTitles) {
|
||||||
|
val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title }
|
||||||
|
val download = tDownloads.first()
|
||||||
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
|
newNovelDownloads += offlineMangaModel
|
||||||
|
}
|
||||||
|
downloads += newNovelDownloads
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
adapter.setItems(downloads)
|
||||||
|
total.text =
|
||||||
|
if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List"
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
downloads += newNovelDownloads
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
/**
|
||||||
val type = downloadedType.type.asText()
|
* Load media.json file from the directory and convert it to Media class
|
||||||
//load media.json and convert to media class with gson
|
* @param downloadedType DownloadedType object
|
||||||
|
* @return Media object
|
||||||
|
*/
|
||||||
|
private suspend fun getMedia(downloadedType: DownloadedType): Media? {
|
||||||
return try {
|
return try {
|
||||||
val directory = getSubDirectory(context?:currContext()!!, downloadedType.type,
|
val directory = getSubDirectory(
|
||||||
false, downloadedType.title)
|
context ?: currContext()!!, downloadedType.type,
|
||||||
|
false, downloadedType.title
|
||||||
|
)
|
||||||
val gson = GsonBuilder()
|
val gson = GsonBuilder()
|
||||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
SChapterImpl() // Provide an instance of SChapterImpl
|
SChapterImpl() // Provide an instance of SChapterImpl
|
||||||
@@ -304,9 +323,10 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
.create()
|
.create()
|
||||||
val media = directory?.findFile("media.json")
|
val media = directory?.findFile("media.json")
|
||||||
?: return null
|
?: return null
|
||||||
val mediaJson = media.openInputStream(context?:currContext()!!)?.bufferedReader().use {
|
val mediaJson =
|
||||||
it?.readText()
|
media.openInputStream(context ?: currContext()!!)?.bufferedReader().use {
|
||||||
}
|
it?.readText()
|
||||||
|
}
|
||||||
gson.fromJson(mediaJson, Media::class.java)
|
gson.fromJson(mediaJson, Media::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log("Error loading media.json: ${e.message}")
|
Logger.log("Error loading media.json: ${e.message}")
|
||||||
@@ -316,12 +336,14 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
private suspend fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
||||||
val type = downloadedType.type.asText()
|
val type = downloadedType.type.asText()
|
||||||
//load media.json and convert to media class with gson
|
//load media.json and convert to media class with gson
|
||||||
try {
|
try {
|
||||||
val directory = getSubDirectory(context?:currContext()!!, downloadedType.type,
|
val directory = getSubDirectory(
|
||||||
false, downloadedType.title)
|
context ?: currContext()!!, downloadedType.type,
|
||||||
|
false, downloadedType.title
|
||||||
|
)
|
||||||
val mediaModel = getMedia(downloadedType)!!
|
val mediaModel = getMedia(downloadedType)!!
|
||||||
val cover = directory?.findFile("cover.jpg")
|
val cover = directory?.findFile("cover.jpg")
|
||||||
val coverUri: Uri? = if (cover?.exists() == true) {
|
val coverUri: Uri? = if (cover?.exists() == true) {
|
||||||
|
|||||||
@@ -16,15 +16,19 @@ import androidx.core.app.ActivityCompat
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaType
|
import ani.dantotsu.media.MediaType
|
||||||
import ani.dantotsu.media.novel.NovelReadFragment
|
import ani.dantotsu.media.novel.NovelReadFragment
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.util.Logger
|
import ani.dantotsu.util.Logger
|
||||||
|
import com.anggrayudi.storage.file.forceDelete
|
||||||
|
import com.anggrayudi.storage.file.openOutputStream
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
@@ -250,24 +254,25 @@ class NovelDownloaderService : Service() {
|
|||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
throw IOException("Failed to download file: ${response.message}")
|
throw IOException("Failed to download file: ${response.message}")
|
||||||
}
|
}
|
||||||
|
val directory = getSubDirectory(
|
||||||
|
this@NovelDownloaderService,
|
||||||
|
MediaType.NOVEL,
|
||||||
|
false,
|
||||||
|
task.title,
|
||||||
|
task.chapter
|
||||||
|
) ?: throw Exception("Directory not found")
|
||||||
|
directory.findFile("0.epub")?.forceDelete(this@NovelDownloaderService)
|
||||||
|
|
||||||
val file = File(
|
val file = directory.createFile("application/epub+zip", "0.epub")
|
||||||
this@NovelDownloaderService.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
?: throw Exception("File not created")
|
||||||
"Dantotsu/Novel/${task.title}/${task.chapter}/0.epub"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create directories if they don't exist
|
|
||||||
file.parentFile?.takeIf { !it.exists() }?.mkdirs()
|
|
||||||
|
|
||||||
// Overwrite existing file
|
|
||||||
if (file.exists()) file.delete()
|
|
||||||
|
|
||||||
//download cover
|
//download cover
|
||||||
task.coverUrl?.let {
|
task.coverUrl?.let {
|
||||||
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
|
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
|
||||||
}
|
}
|
||||||
|
val outputStream = this@NovelDownloaderService.contentResolver.openOutputStream(file.uri) ?: throw Exception("Could not open OutputStream")
|
||||||
|
|
||||||
val sink = file.sink().buffer()
|
val sink = outputStream.sink().buffer()
|
||||||
val responseBody = response.body
|
val responseBody = response.body
|
||||||
val totalBytes = responseBody.contentLength()
|
val totalBytes = responseBody.contentLength()
|
||||||
var downloadedBytes = 0L
|
var downloadedBytes = 0L
|
||||||
@@ -352,13 +357,16 @@ class NovelDownloaderService : Service() {
|
|||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun saveMediaInfo(task: DownloadTask) {
|
private fun saveMediaInfo(task: DownloadTask) {
|
||||||
launchIO {
|
launchIO {
|
||||||
val directory = File(
|
val directory =
|
||||||
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
DownloadsManager.getSubDirectory(
|
||||||
"Dantotsu/Novel/${task.title}"
|
this@NovelDownloaderService,
|
||||||
)
|
MediaType.NOVEL,
|
||||||
if (!directory.exists()) directory.mkdirs()
|
false,
|
||||||
|
task.title
|
||||||
val file = File(directory, "media.json")
|
) ?: throw Exception("Directory not found")
|
||||||
|
directory.findFile("media.json")?.forceDelete(this@NovelDownloaderService)
|
||||||
|
val file = directory.createFile("application/json", "media.json")
|
||||||
|
?: throw Exception("File not created")
|
||||||
val gson = GsonBuilder()
|
val gson = GsonBuilder()
|
||||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
SChapterImpl() // Provide an instance of SChapterImpl
|
SChapterImpl() // Provide an instance of SChapterImpl
|
||||||
@@ -372,33 +380,47 @@ class NovelDownloaderService : Service() {
|
|||||||
|
|
||||||
val jsonString = gson.toJson(media)
|
val jsonString = gson.toJson(media)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
file.writeText(jsonString)
|
try {
|
||||||
|
file.openOutputStream(this@NovelDownloaderService, false).use { output ->
|
||||||
|
if (output == null) throw Exception("Output stream is null")
|
||||||
|
output.write(jsonString.toByteArray())
|
||||||
|
}
|
||||||
|
} catch (e: android.system.ErrnoException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
Toast.makeText(
|
||||||
|
this@NovelDownloaderService,
|
||||||
|
"Error while saving: ${e.localizedMessage}",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private suspend fun downloadImage(url: String, directory: File, name: String): String? =
|
private suspend fun downloadImage(url: String, directory: DocumentFile, name: String): String? =
|
||||||
withContext(
|
withContext(
|
||||||
Dispatchers.IO
|
Dispatchers.IO
|
||||||
) {
|
) {
|
||||||
var connection: HttpURLConnection? = null
|
var connection: HttpURLConnection? = null
|
||||||
println("Downloading url $url")
|
Logger.log("Downloading url $url")
|
||||||
try {
|
try {
|
||||||
connection = URL(url).openConnection() as HttpURLConnection
|
connection = URL(url).openConnection() as HttpURLConnection
|
||||||
connection.connect()
|
connection.connect()
|
||||||
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
|
throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
|
||||||
}
|
}
|
||||||
|
directory.findFile(name)?.forceDelete(this@NovelDownloaderService)
|
||||||
val file = File(directory, name)
|
val file =
|
||||||
FileOutputStream(file).use { output ->
|
directory.createFile("image/jpeg", name) ?: throw Exception("File not created")
|
||||||
|
file.openOutputStream(this@NovelDownloaderService, false).use { output ->
|
||||||
|
if (output == null) throw Exception("Output stream is null")
|
||||||
connection.inputStream.use { input ->
|
connection.inputStream.use { input ->
|
||||||
input.copyTo(output)
|
input.copyTo(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@withContext file.absolutePath
|
return@withContext file.uri.toString()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
import ani.dantotsu.download.DownloadedType
|
import ani.dantotsu.download.DownloadedType
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
@@ -94,23 +95,23 @@ class NovelReadFragment : Fragment(),
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val file = File(
|
try {
|
||||||
context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
val directory =
|
||||||
"a/${media.mainName()}/${novel.name}/0.epub" //FIXME
|
DownloadsManager.getSubDirectory(context?:currContext()!!, MediaType.NOVEL, false, novel.name)
|
||||||
)
|
val file = directory?.findFile(novel.name)
|
||||||
if (!file.exists()) return false
|
if (file?.exists() == false) return false
|
||||||
val fileUri = FileProvider.getUriForFile(
|
val fileUri = file?.uri ?: return false
|
||||||
requireContext(),
|
val intent = Intent(context, NovelReaderActivity::class.java).apply {
|
||||||
"${requireContext().packageName}.provider",
|
action = Intent.ACTION_VIEW
|
||||||
file
|
setDataAndType(fileUri, "application/epub+zip")
|
||||||
)
|
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
val intent = Intent(context, NovelReaderActivity::class.java).apply {
|
}
|
||||||
action = Intent.ACTION_VIEW
|
startActivity(intent)
|
||||||
setDataAndType(fileUri, "application/epub+zip")
|
return true
|
||||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
} catch (e: Exception) {
|
||||||
|
Logger.log(e)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
startActivity(intent)
|
|
||||||
return true
|
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
||||||
import ani.dantotsu.media.MediaNameAdapter
|
import ani.dantotsu.media.MediaNameAdapter
|
||||||
|
import ani.dantotsu.media.MediaType
|
||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@@ -11,6 +14,7 @@ import java.io.File
|
|||||||
|
|
||||||
class OfflineNovelParser : NovelParser() {
|
class OfflineNovelParser : NovelParser() {
|
||||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||||
|
private val context = Injekt.get<Application>()
|
||||||
|
|
||||||
override val hostUrl: String = "Offline"
|
override val hostUrl: String = "Offline"
|
||||||
override val name: String = "Offline"
|
override val name: String = "Offline"
|
||||||
@@ -21,19 +25,16 @@ class OfflineNovelParser : NovelParser() {
|
|||||||
|
|
||||||
override suspend fun loadBook(link: String, extra: Map<String, String>?): Book {
|
override suspend fun loadBook(link: String, extra: Map<String, String>?): Book {
|
||||||
//link should be a directory
|
//link should be a directory
|
||||||
val directory = File(
|
val directory = getSubDirectory(context, MediaType.NOVEL, false, link)
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
|
||||||
"Dantotsu/Novel/$link"
|
|
||||||
)
|
|
||||||
val chapters = mutableListOf<Book>()
|
val chapters = mutableListOf<Book>()
|
||||||
if (directory.exists()) {
|
if (directory?.exists() == true) {
|
||||||
directory.listFiles()?.forEach {
|
directory.listFiles().forEach {
|
||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
val chapter = Book(
|
val chapter = Book(
|
||||||
it.name,
|
it.name?:"Unknown",
|
||||||
it.absolutePath + "/cover.jpg",
|
it.uri.toString(),
|
||||||
null,
|
null,
|
||||||
listOf(it.absolutePath + "/0.epub")
|
listOf(it.uri.toString())
|
||||||
)
|
)
|
||||||
chapters.add(chapter)
|
chapters.add(chapter)
|
||||||
}
|
}
|
||||||
@@ -60,20 +61,16 @@ class OfflineNovelParser : NovelParser() {
|
|||||||
val returnList: MutableList<ShowResponse> = mutableListOf()
|
val returnList: MutableList<ShowResponse> = mutableListOf()
|
||||||
for (title in returnTitles) {
|
for (title in returnTitles) {
|
||||||
//need to search the subdirectories for the ShowResponses
|
//need to search the subdirectories for the ShowResponses
|
||||||
val directory = File(
|
val directory = getSubDirectory(context, MediaType.NOVEL, false, title)
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
|
||||||
"Dantotsu/Novel/$title"
|
|
||||||
)
|
|
||||||
val names = mutableListOf<String>()
|
val names = mutableListOf<String>()
|
||||||
if (directory.exists()) {
|
if (directory?.exists() == true) {
|
||||||
directory.listFiles()?.forEach {
|
directory.listFiles().forEach {
|
||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
names.add(it.name)
|
names.add(it.name?: "Unknown")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val cover =
|
val cover = directory?.findFile("cover.jpg")?.uri.toString()
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + "/Dantotsu/Novel/$title/cover.jpg"
|
|
||||||
names.forEach {
|
names.forEach {
|
||||||
returnList.add(ShowResponse(it, it, cover))
|
returnList.add(ShowResponse(it, it, cover))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user