feat: custom downloader and downloader location (#313)
* feat: custom downloader (novel broken) * fix: send headers to ffmpeg ffmpeg can be a real bitch to work with * fix: offline page for new download system * feat: novel to new system | load freezing * chore: clean manifest * fix: notification incrementing * feat: changing the downloads dir
This commit is contained in:
parent
75e90541c9
commit
720b40afa7
35 changed files with 1162 additions and 1018 deletions
|
@ -13,6 +13,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
|
@ -53,6 +54,7 @@ import ani.dantotsu.settings.saving.PrefName
|
|||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.statusBarHeight
|
||||
import ani.dantotsu.themes.ThemeManager
|
||||
import ani.dantotsu.util.LauncherWrapper
|
||||
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -66,7 +68,7 @@ import kotlin.math.abs
|
|||
|
||||
|
||||
class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListener {
|
||||
|
||||
lateinit var launcher: LauncherWrapper
|
||||
lateinit var binding: ActivityMediaBinding
|
||||
private val scope = lifecycleScope
|
||||
private val model: MediaDetailsViewModel by viewModels()
|
||||
|
@ -92,6 +94,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
onBackPressedDispatcher.onBackPressed()
|
||||
return
|
||||
}
|
||||
val contract = ActivityResultContracts.OpenDocumentTree()
|
||||
launcher = LauncherWrapper(this, contract)
|
||||
|
||||
mediaSingleton = null
|
||||
ThemeManager(this).applyTheme(MediaSingleton.bitmap)
|
||||
MediaSingleton.bitmap = null
|
||||
|
@ -576,4 +581,4 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
companion object {
|
||||
var mediaSingleton: Media? = null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import ani.dantotsu.download.DownloadedType
|
|||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.parsers.SubtitleType
|
||||
import ani.dantotsu.snackString
|
||||
import com.anggrayudi.storage.file.openOutputStream
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -51,21 +52,17 @@ class SubtitleDownloader {
|
|||
downloadedType: DownloadedType
|
||||
) {
|
||||
try {
|
||||
val directory = DownloadsManager.getDirectory(
|
||||
val directory = DownloadsManager.getSubDirectory(
|
||||
context,
|
||||
downloadedType.type,
|
||||
false,
|
||||
downloadedType.title,
|
||||
downloadedType.chapter
|
||||
)
|
||||
if (!directory.exists()) { //just in case
|
||||
directory.mkdirs()
|
||||
}
|
||||
) ?: throw Exception("Could not create directory")
|
||||
val type = loadSubtitleType(url)
|
||||
val subtiteFile = File(directory, "subtitle.${type}")
|
||||
if (subtiteFile.exists()) {
|
||||
subtiteFile.delete()
|
||||
}
|
||||
subtiteFile.createNewFile()
|
||||
directory.findFile("subtitle.${type}")?.delete()
|
||||
val subtitleFile = directory.createFile("*/*", "subtitle.${type}")
|
||||
?: throw Exception("Could not create subtitle file")
|
||||
|
||||
val client = Injekt.get<NetworkHelper>().client
|
||||
val request = Request.Builder().url(url).build()
|
||||
|
@ -77,7 +74,8 @@ class SubtitleDownloader {
|
|||
}
|
||||
|
||||
reponse.body.byteStream().use { input ->
|
||||
subtiteFile.outputStream().use { output ->
|
||||
subtitleFile.openOutputStream(context, false).use { output ->
|
||||
if (output == null) throw Exception("Could not open output stream")
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import android.view.ViewGroup
|
|||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.math.MathUtils
|
||||
|
@ -34,8 +35,8 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.DownloadsManager.Companion.findValidName
|
||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||
import ani.dantotsu.download.video.ExoplayerDownloadService
|
||||
import ani.dantotsu.dp
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
|
@ -54,6 +55,8 @@ import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
|||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
|
||||
import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
|
@ -422,7 +425,19 @@ class AnimeWatchFragment : Fragment() {
|
|||
}
|
||||
|
||||
fun onAnimeEpisodeDownloadClick(i: String) {
|
||||
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true)
|
||||
activity?.let{
|
||||
if (!hasDirAccess(it)) {
|
||||
(it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success ->
|
||||
if (success) {
|
||||
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true)
|
||||
} else {
|
||||
snackString("Permission is required to download")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager, isDownload = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onAnimeEpisodeStopDownloadClick(i: String) {
|
||||
|
@ -442,8 +457,9 @@ class AnimeWatchFragment : Fragment() {
|
|||
i,
|
||||
MediaType.ANIME
|
||||
)
|
||||
)
|
||||
episodeAdapter.purgeDownload(i)
|
||||
) {
|
||||
episodeAdapter.purgeDownload(i)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
|
@ -454,20 +470,15 @@ class AnimeWatchFragment : Fragment() {
|
|||
i,
|
||||
MediaType.ANIME
|
||||
)
|
||||
)
|
||||
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
|
||||
val id = PrefManager.getAnimeDownloadPreferences().getString(
|
||||
taskName,
|
||||
""
|
||||
) ?: ""
|
||||
PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply()
|
||||
DownloadService.sendRemoveDownload(
|
||||
requireContext(),
|
||||
ExoplayerDownloadService::class.java,
|
||||
id,
|
||||
true
|
||||
)
|
||||
episodeAdapter.deleteDownload(i)
|
||||
) {
|
||||
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
|
||||
val id = PrefManager.getAnimeDownloadPreferences().getString(
|
||||
taskName,
|
||||
""
|
||||
) ?: ""
|
||||
PrefManager.getAnimeDownloadPreferences().edit().remove(taskName).apply()
|
||||
episodeAdapter.deleteDownload(i)
|
||||
}
|
||||
}
|
||||
|
||||
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||
|
@ -531,7 +542,7 @@ class AnimeWatchFragment : Fragment() {
|
|||
episodeAdapter.updateType(style ?: PrefManager.getVal(PrefName.AnimeDefaultView))
|
||||
episodeAdapter.notifyItemRangeInserted(0, arr.size)
|
||||
for (download in downloadManager.animeDownloadedTypes) {
|
||||
if (download.title == media.mainName()) {
|
||||
if (download.title == media.mainName().findValidName()) {
|
||||
episodeAdapter.stopDownload(download.chapter)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.annotation.OptIn
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.offline.DownloadIndex
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.updateProgress
|
||||
|
@ -18,10 +17,12 @@ import ani.dantotsu.currContext
|
|||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||
import ani.dantotsu.databinding.ItemEpisodeGridBinding
|
||||
import ani.dantotsu.databinding.ItemEpisodeListBinding
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.DownloadsManager.Companion.getDirSize
|
||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaNameAdapter
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.setAnimation
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import com.bumptech.glide.Glide
|
||||
|
@ -56,15 +57,7 @@ class EpisodeAdapter(
|
|||
var arr: List<Episode> = arrayListOf(),
|
||||
var offlineMode: Boolean
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private lateinit var index: DownloadIndex
|
||||
|
||||
|
||||
init {
|
||||
if (offlineMode) {
|
||||
index = Helper.downloadManager(fragment.requireContext()).downloadIndex
|
||||
}
|
||||
}
|
||||
val context = fragment.requireContext()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return (when (viewType) {
|
||||
|
@ -248,17 +241,8 @@ class EpisodeAdapter(
|
|||
// Find the position of the chapter and notify only that item
|
||||
val position = arr.indexOfFirst { it.number == episodeNumber }
|
||||
if (position != -1) {
|
||||
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(
|
||||
media.mainName(),
|
||||
episodeNumber
|
||||
)
|
||||
val id = PrefManager.getAnimeDownloadPreferences().getString(
|
||||
taskName,
|
||||
""
|
||||
) ?: ""
|
||||
val size = try {
|
||||
val download = index.getDownload(id)
|
||||
bytesToHuman(download?.bytesDownloaded ?: 0)
|
||||
bytesToHuman(getDirSize(context, MediaType.ANIME, media.mainName(), episodeNumber))
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ import ani.dantotsu.connections.discord.RPC
|
|||
import ani.dantotsu.connections.updateProgress
|
||||
import ani.dantotsu.databinding.ActivityExoplayerBinding
|
||||
import ani.dantotsu.defaultHeaders
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import ani.dantotsu.download.DownloadsManager.Companion.getSubDirectory
|
||||
import ani.dantotsu.dp
|
||||
import ani.dantotsu.getCurrentBrightnessValue
|
||||
import ani.dantotsu.hideSystemBars
|
||||
|
@ -114,6 +114,7 @@ import ani.dantotsu.logError
|
|||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.media.MediaNameAdapter
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.SubtitleDownloader
|
||||
import ani.dantotsu.okHttpClient
|
||||
import ani.dantotsu.others.AniSkip
|
||||
|
@ -394,7 +395,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
isCastApiAvailable = GoogleApiAvailability.getInstance()
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
try {
|
||||
castContext = CastContext.getSharedInstance(this, Executors.newSingleThreadExecutor()).result
|
||||
castContext =
|
||||
CastContext.getSharedInstance(this, Executors.newSingleThreadExecutor()).result
|
||||
castPlayer = CastPlayer(castContext!!)
|
||||
castPlayer!!.setSessionAvailabilityListener(this)
|
||||
} catch (e: Exception) {
|
||||
|
@ -442,41 +444,43 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
}, AUDIO_CONTENT_TYPE_MOVIE, AUDIOFOCUS_GAIN)
|
||||
|
||||
if (System.getInt(contentResolver, System.ACCELEROMETER_ROTATION, 0) != 1) {
|
||||
if (PrefManager.getVal(PrefName.RotationPlayer)) {
|
||||
orientationListener =
|
||||
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
when (orientation) {
|
||||
in 45..135 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
if (PrefManager.getVal(PrefName.RotationPlayer)) {
|
||||
orientationListener =
|
||||
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
when (orientation) {
|
||||
in 45..135 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
}
|
||||
|
||||
in 225..315 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
|
||||
in 315..360, in 0..45 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
}
|
||||
in 225..315 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
in 315..360, in 0..45 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
}
|
||||
}
|
||||
orientationListener?.enable()
|
||||
}
|
||||
orientationListener?.enable()
|
||||
}
|
||||
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
exoRotate.setOnClickListener {
|
||||
requestedOrientation = rotation
|
||||
it.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
exoRotate.setOnClickListener {
|
||||
requestedOrientation = rotation
|
||||
it.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
setupSubFormatting(playerView)
|
||||
|
||||
|
@ -1089,10 +1093,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
"nothing" -> mutableListOf(
|
||||
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||
)
|
||||
|
||||
"dantotsu" -> mutableListOf(
|
||||
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||
RPC.Link("Watch on Dantotsu", getString(R.string.dantotsu))
|
||||
)
|
||||
|
||||
"anilist" -> {
|
||||
val userId = PrefManager.getVal<String>(PrefName.AnilistUserId)
|
||||
val anilistLink = "https://anilist.co/user/$userId/"
|
||||
|
@ -1101,6 +1107,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
RPC.Link("View My AniList", anilistLink)
|
||||
)
|
||||
}
|
||||
|
||||
else -> mutableListOf()
|
||||
}
|
||||
val presence = RPC.createPresence(
|
||||
|
@ -1113,7 +1120,12 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
ep.number
|
||||
),
|
||||
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}",
|
||||
largeImage = media.cover?.let { RPC.Link(media.userPreferredName, it) },
|
||||
largeImage = media.cover?.let {
|
||||
RPC.Link(
|
||||
media.userPreferredName,
|
||||
it
|
||||
)
|
||||
},
|
||||
smallImage = RPC.Link("Dantotsu", Discord.small_Image),
|
||||
buttons = buttons
|
||||
)
|
||||
|
@ -1161,7 +1173,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
if (PrefManager.getVal(PrefName.Cast)) {
|
||||
playerView.findViewById<CustomCastButton>(R.id.exo_cast).apply {
|
||||
visibility = View.VISIBLE
|
||||
if(PrefManager.getVal(PrefName.UseInternalCast)) {
|
||||
if (PrefManager.getVal(PrefName.UseInternalCast)) {
|
||||
try {
|
||||
CastButtonFactory.setUpMediaRouteButton(context, this)
|
||||
dialogFactory = CustomCastThemeFactory()
|
||||
|
@ -1324,7 +1336,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = (PrefManager.getNullableCustomVal("continueAnimeList", listOf<Int>(), List::class.java) as List<Int>).toMutableList()
|
||||
val list = (PrefManager.getNullableCustomVal(
|
||||
"continueAnimeList",
|
||||
listOf<Int>(),
|
||||
List::class.java
|
||||
) as List<Int>).toMutableList()
|
||||
if (list.contains(media.id)) list.remove(media.id)
|
||||
list.add(media.id)
|
||||
PrefManager.setCustomVal("continueAnimeList", list)
|
||||
|
@ -1418,7 +1434,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
}
|
||||
val dafuckDataSourceFactory = DefaultDataSource.Factory(this)
|
||||
cacheFactory = CacheDataSource.Factory().apply {
|
||||
setCache(Helper.getSimpleCache(this@ExoplayerView))
|
||||
setCache(VideoCache.getInstance(this@ExoplayerView))
|
||||
if (ext.server.offline) {
|
||||
setUpstreamDataSourceFactory(dafuckDataSourceFactory)
|
||||
} else {
|
||||
|
@ -1435,15 +1451,28 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
|
||||
val downloadedMediaItem = if (ext.server.offline) {
|
||||
val key = ext.server.name
|
||||
downloadId = PrefManager.getAnimeDownloadPreferences()
|
||||
.getString(key, null)
|
||||
if (downloadId != null) {
|
||||
Helper.downloadManager(this)
|
||||
.downloadIndex.getDownload(downloadId!!)?.request?.toMediaItem()
|
||||
val titleName = ext.server.name.split("/").first()
|
||||
val episodeName = ext.server.name.split("/").last()
|
||||
|
||||
val directory = getSubDirectory(this, MediaType.ANIME, false, titleName, episodeName)
|
||||
if (directory != null) {
|
||||
val files = directory.listFiles()
|
||||
println(files)
|
||||
val docFile = directory.listFiles().firstOrNull {
|
||||
it.name?.endsWith(".mp4") == true || it.name?.endsWith(".mkv") == true
|
||||
}
|
||||
if (docFile != null) {
|
||||
val uri = docFile.uri
|
||||
MediaItem.Builder().setUri(uri).setMimeType(mimeType).build()
|
||||
} else {
|
||||
snackString("File not found")
|
||||
null
|
||||
}
|
||||
} else {
|
||||
snackString("Download not found")
|
||||
snackString("Directory not found")
|
||||
null
|
||||
}
|
||||
|
||||
} else null
|
||||
|
||||
mediaItem = if (downloadedMediaItem == null) {
|
||||
|
@ -1818,7 +1847,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
|
||||
if (!functionstarted && !disappeared && PrefManager.getVal(PrefName.AutoHideTimeStamps)) {
|
||||
disappearSkip()
|
||||
} else if (!PrefManager.getVal<Boolean>(PrefName.AutoHideTimeStamps)){
|
||||
} else if (!PrefManager.getVal<Boolean>(PrefName.AutoHideTimeStamps)) {
|
||||
skipTimeButton.visibility = View.VISIBLE
|
||||
exoSkip.visibility = View.GONE
|
||||
skipTimeText.text = new.skipType.getType()
|
||||
|
@ -2157,11 +2186,16 @@ class CustomCastButton : MediaRouteButton {
|
|||
fun setCastCallback(castCallback: () -> Unit) {
|
||||
this.castCallback = castCallback
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
override fun performClick(): Boolean {
|
||||
return if (PrefManager.getVal(PrefName.UseInternalCast)) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -34,6 +35,7 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.DownloadsManager.Companion.findValidName
|
||||
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||
import ani.dantotsu.download.manga.MangaServiceDataSingleton
|
||||
import ani.dantotsu.dp
|
||||
|
@ -56,6 +58,8 @@ import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
|||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog
|
||||
import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
|
@ -190,7 +194,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
)
|
||||
|
||||
for (download in downloadManager.mangaDownloadedTypes) {
|
||||
if (download.title == media.mainName()) {
|
||||
if (download.title == media.mainName().findValidName()) {
|
||||
chapterAdapter.stopDownload(download.chapter)
|
||||
}
|
||||
}
|
||||
|
@ -434,51 +438,65 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
}
|
||||
|
||||
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
|
||||
parser?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val images = parser.imageList(chapter.sChapter)
|
||||
|
||||
// Create a download task
|
||||
val downloadTask = MangaDownloaderService.DownloadTask(
|
||||
title = media.mainName(),
|
||||
chapter = chapter.title!!,
|
||||
imageData = images,
|
||||
sourceMedia = media,
|
||||
retries = 2,
|
||||
simultaneousDownloads = 2
|
||||
activity?.let {
|
||||
if (!isNotificationPermissionGranted()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ActivityCompat.requestPermissions(
|
||||
it,
|
||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
fun continueDownload() {
|
||||
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 {
|
||||
val images = parser.imageList(chapter.sChapter)
|
||||
|
||||
MangaServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||
// Create a download task
|
||||
val downloadTask = MangaDownloaderService.DownloadTask(
|
||||
title = media.mainName(),
|
||||
chapter = chapter.title!!,
|
||||
imageData = images,
|
||||
sourceMedia = media,
|
||||
retries = 2,
|
||||
simultaneousDownloads = 2
|
||||
)
|
||||
|
||||
// If the service is not already running, start it
|
||||
if (!MangaServiceDataSingleton.isServiceRunning) {
|
||||
val intent = Intent(context, MangaDownloaderService::class.java)
|
||||
withContext(Dispatchers.Main) {
|
||||
ContextCompat.startForegroundService(requireContext(), intent)
|
||||
MangaServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||
|
||||
// If the service is not already running, start it
|
||||
if (!MangaServiceDataSingleton.isServiceRunning) {
|
||||
val intent = Intent(context, MangaDownloaderService::class.java)
|
||||
withContext(Dispatchers.Main) {
|
||||
ContextCompat.startForegroundService(requireContext(), intent)
|
||||
}
|
||||
MangaServiceDataSingleton.isServiceRunning = true
|
||||
}
|
||||
|
||||
// Inform the adapter that the download has started
|
||||
withContext(Dispatchers.Main) {
|
||||
chapterAdapter.startDownload(i)
|
||||
}
|
||||
}
|
||||
MangaServiceDataSingleton.isServiceRunning = true
|
||||
}
|
||||
|
||||
// Inform the adapter that the download has started
|
||||
withContext(Dispatchers.Main) {
|
||||
chapterAdapter.startDownload(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasDirAccess(it)) {
|
||||
(it as MediaDetailsActivity).accessAlertDialog(it.launcher) { success ->
|
||||
if (success) {
|
||||
continueDownload()
|
||||
} else {
|
||||
snackString("Permission is required to download")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continueDownload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,8 +518,9 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
i,
|
||||
MediaType.MANGA
|
||||
)
|
||||
)
|
||||
chapterAdapter.deleteDownload(i)
|
||||
) {
|
||||
chapterAdapter.deleteDownload(i)
|
||||
}
|
||||
}
|
||||
|
||||
fun onMangaChapterStopDownloadClick(i: String) {
|
||||
|
@ -518,8 +537,9 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
i,
|
||||
MediaType.MANGA
|
||||
)
|
||||
)
|
||||
chapterAdapter.purgeDownload(i)
|
||||
) {
|
||||
chapterAdapter.purgeDownload(i)
|
||||
}
|
||||
}
|
||||
|
||||
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||
|
|
|
@ -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
|
||||
|
@ -176,6 +177,10 @@ abstract class BaseImageAdapter(
|
|||
it.load(localFile.absoluteFile)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
} else if (link.url.startsWith("content://")) {
|
||||
it.load(Uri.parse(link.url))
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
} else {
|
||||
mangaCache.get(link.url)?.let { imageData ->
|
||||
val bitmap = imageData.fetchAndProcessImage(
|
||||
|
@ -186,6 +191,7 @@ abstract class BaseImageAdapter(
|
|||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
?.let {
|
||||
|
|
|
@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
|
@ -94,23 +95,23 @@ class NovelReadFragment : Fragment(),
|
|||
)
|
||||
)
|
||||
) {
|
||||
val file = File(
|
||||
context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"${DownloadsManager.novelLocation}/${media.mainName()}/${novel.name}/0.epub"
|
||||
)
|
||||
if (!file.exists()) return false
|
||||
val fileUri = FileProvider.getUriForFile(
|
||||
requireContext(),
|
||||
"${requireContext().packageName}.provider",
|
||||
file
|
||||
)
|
||||
val intent = Intent(context, NovelReaderActivity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
setDataAndType(fileUri, "application/epub+zip")
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
try {
|
||||
val directory =
|
||||
DownloadsManager.getSubDirectory(context?:currContext()!!, MediaType.NOVEL, false, novel.name)
|
||||
val file = directory?.findFile(novel.name)
|
||||
if (file?.exists() == false) return false
|
||||
val fileUri = file?.uri ?: return false
|
||||
val intent = Intent(context, NovelReaderActivity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
setDataAndType(fileUri, "application/epub+zip")
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
startActivity(intent)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.log(e)
|
||||
return false
|
||||
}
|
||||
startActivity(intent)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -135,7 +136,7 @@ class NovelReadFragment : Fragment(),
|
|||
novel.name,
|
||||
MediaType.NOVEL
|
||||
)
|
||||
)
|
||||
) {}
|
||||
}
|
||||
|
||||
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue