subtitle, image, and extension page fixes
This commit is contained in:
parent
866bd3b3a9
commit
20bea76e6c
18 changed files with 895 additions and 670 deletions
|
@ -96,6 +96,7 @@ dependencies {
|
||||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||||
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
||||||
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
||||||
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
|
|
||||||
// string matching
|
// string matching
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
|
|
|
@ -64,6 +64,8 @@ import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
|
@ -74,9 +76,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
private var load = false
|
private var load = false
|
||||||
|
|
||||||
private var uiSettings = UserInterfaceSettings()
|
private var uiSettings = UserInterfaceSettings()
|
||||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
|
||||||
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
|
|
|
@ -1205,7 +1205,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
else -> MimeTypes.TEXT_SSA
|
else -> MimeTypes.TEXT_SSA
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.setId("2")
|
.setId("69")
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
println("sub: $sub")
|
println("sub: $sub")
|
||||||
|
@ -1221,7 +1221,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
else -> MimeTypes.TEXT_UNKNOWN
|
else -> MimeTypes.TEXT_UNKNOWN
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.setId("2")
|
.setId("69")
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1302,6 +1302,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
)
|
)
|
||||||
.setMaxVideoSize(1, 1)
|
.setMaxVideoSize(1, 1)
|
||||||
//.setOverrideForType(
|
//.setOverrideForType(
|
||||||
|
// TrackSelectionOverride(trackSelector, 2))
|
||||||
)
|
)
|
||||||
|
|
||||||
if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) {
|
if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) {
|
||||||
|
@ -1352,6 +1353,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
}
|
}
|
||||||
playerView.player = exoPlayer
|
playerView.player = exoPlayer
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mediaSession = MediaSession.Builder(this, exoPlayer).build()
|
mediaSession = MediaSession.Builder(this, exoPlayer).build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -1572,6 +1574,27 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTracksChanged(tracks: Tracks) {
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
|
tracks.groups.forEach {
|
||||||
|
println("Track__: $it")
|
||||||
|
println("Track__: ${it.length}")
|
||||||
|
println("Track__: ${it.isSelected}")
|
||||||
|
println("Track__: ${it.type}")
|
||||||
|
println("Track__: ${it.mediaTrackGroup.id}")
|
||||||
|
if (it.type == 3 && it.mediaTrackGroup.id == "1:"){
|
||||||
|
playerView.player?.trackSelectionParameters =
|
||||||
|
playerView.player?.trackSelectionParameters?.buildUpon()
|
||||||
|
?.setOverrideForType(
|
||||||
|
TrackSelectionOverride(it.mediaTrackGroup, it.length - 1))
|
||||||
|
?.build()!!
|
||||||
|
}else if(it.type == 3){
|
||||||
|
playerView.player?.trackSelectionParameters =
|
||||||
|
playerView.player?.trackSelectionParameters?.buildUpon()
|
||||||
|
?.addOverride(
|
||||||
|
TrackSelectionOverride(it.mediaTrackGroup, listOf()))
|
||||||
|
?.build()!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("Track: ${tracks.groups.size}")
|
||||||
if (tracks.groups.size <= 2) exoQuality.visibility = View.GONE
|
if (tracks.groups.size <= 2) exoQuality.visibility = View.GONE
|
||||||
else {
|
else {
|
||||||
exoQuality.visibility = View.VISIBLE
|
exoQuality.visibility = View.VISIBLE
|
||||||
|
|
|
@ -156,10 +156,6 @@ abstract class BaseImageAdapter(
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
} else {
|
} else {
|
||||||
println("bitmap from cache")
|
|
||||||
println(link.url)
|
|
||||||
println(mangaCache.get(link.url))
|
|
||||||
println("cache size: ${mangaCache.size()}")
|
|
||||||
mangaCache.get(link.url)?.let { imageData ->
|
mangaCache.get(link.url)?.let { imageData ->
|
||||||
val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source, context = this@loadBitmap)
|
val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source, context = this@loadBitmap)
|
||||||
it.load(bitmap)
|
it.load(bitmap)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import ani.dantotsu.BottomSheetDialogFragment
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.BottomSheetImageBinding
|
import ani.dantotsu.databinding.BottomSheetImageBinding
|
||||||
|
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap
|
||||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old
|
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old
|
||||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
|
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
@ -78,7 +79,11 @@ class ImageViewDialog : BottomSheetDialogFragment() {
|
||||||
val binding = _binding ?: return@launch
|
val binding = _binding ?: return@launch
|
||||||
|
|
||||||
var bitmap = requireContext().loadBitmap_old(image, trans1 ?: listOf())
|
var bitmap = requireContext().loadBitmap_old(image, trans1 ?: listOf())
|
||||||
val bitmap2 = if (image2 != null) requireContext().loadBitmap_old(image2, trans2 ?: listOf()) else null
|
var bitmap2 = if (image2 != null) requireContext().loadBitmap_old(image2, trans2 ?: listOf()) else null
|
||||||
|
if (bitmap == null) {
|
||||||
|
bitmap = requireContext().loadBitmap(image, trans1 ?: listOf())
|
||||||
|
bitmap2 = if (image2 != null) requireContext().loadBitmap(image2, trans2 ?: listOf()) else null
|
||||||
|
}
|
||||||
|
|
||||||
bitmap = if (bitmap2 != null && bitmap != null) mergeBitmap(bitmap, bitmap2,) else bitmap
|
bitmap = if (bitmap2 != null && bitmap != null) mergeBitmap(bitmap, bitmap2,) else bitmap
|
||||||
|
|
||||||
|
|
|
@ -57,17 +57,17 @@ abstract class BaseParser {
|
||||||
setUserText("Searching : ${mediaObj.mainName()}")
|
setUserText("Searching : ${mediaObj.mainName()}")
|
||||||
val results = search(mediaObj.mainName())
|
val results = search(mediaObj.mainName())
|
||||||
val sortedResults = if (results.isNotEmpty()) {
|
val sortedResults = if (results.isNotEmpty()) {
|
||||||
results.sortedByDescending { FuzzySearch.ratio(it.name, mediaObj.mainName()) }
|
results.sortedByDescending { FuzzySearch.ratio(it.name.lowercase(), mediaObj.mainName().lowercase()) }
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
response = sortedResults.firstOrNull()
|
response = sortedResults.firstOrNull()
|
||||||
|
|
||||||
if (response == null || FuzzySearch.ratio(response.name, mediaObj.mainName()) < 100) {
|
if (response == null || FuzzySearch.ratio(response.name.lowercase(), mediaObj.mainName().lowercase()) < 100) {
|
||||||
setUserText("Searching : ${mediaObj.nameRomaji}")
|
setUserText("Searching : ${mediaObj.nameRomaji}")
|
||||||
val romajiResults = search(mediaObj.nameRomaji)
|
val romajiResults = search(mediaObj.nameRomaji)
|
||||||
val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
|
val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
|
||||||
romajiResults.sortedByDescending { FuzzySearch.ratio(it.name, mediaObj.nameRomaji) }
|
romajiResults.sortedByDescending { FuzzySearch.ratio(it.name.lowercase(), mediaObj.nameRomaji.lowercase()) }
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
@ -78,10 +78,10 @@ abstract class BaseParser {
|
||||||
logger("No exact match found in results. Using closest match from RomajiResults.")
|
logger("No exact match found in results. Using closest match from RomajiResults.")
|
||||||
closestRomaji
|
closestRomaji
|
||||||
} else {
|
} else {
|
||||||
val romajiRatio = FuzzySearch.ratio(closestRomaji?.name ?: "", mediaObj.nameRomaji)
|
val romajiRatio = FuzzySearch.ratio(closestRomaji?.name?.lowercase() ?: "", mediaObj.nameRomaji.lowercase())
|
||||||
val mainNameRatio = FuzzySearch.ratio(response.name, mediaObj.mainName())
|
val mainNameRatio = FuzzySearch.ratio(response.name.lowercase(), mediaObj.mainName().lowercase())
|
||||||
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name}")
|
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name.lowercase()}")
|
||||||
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name ?: "None"}")
|
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name?.lowercase() ?: "None"}")
|
||||||
|
|
||||||
if (romajiRatio > mainNameRatio) {
|
if (romajiRatio > mainNameRatio) {
|
||||||
logger("RomajiResults has a closer match. Replacing response.")
|
logger("RomajiResults has a closer match. Replacing response.")
|
||||||
|
|
|
@ -2,110 +2,79 @@ package ani.dantotsu.settings
|
||||||
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
|
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.settings.paging.AnimeExtensionAdapter
|
||||||
import com.bumptech.glide.Glide
|
import ani.dantotsu.settings.paging.AnimeExtensionsViewModel
|
||||||
|
import ani.dantotsu.settings.paging.AnimeExtensionsViewModelFactory
|
||||||
|
import ani.dantotsu.settings.paging.OnAnimeInstallClickListener
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class AnimeExtensionsFragment : Fragment(),
|
class AnimeExtensionsFragment : Fragment(),
|
||||||
SearchQueryHandler {
|
SearchQueryHandler, OnAnimeInstallClickListener {
|
||||||
private var _binding: FragmentAnimeExtensionsBinding? = null
|
private var _binding: FragmentAnimeExtensionsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
val skipIcons = loadData("skip_extension_icons") ?: false
|
private val viewModel: AnimeExtensionsViewModel by viewModels {
|
||||||
|
AnimeExtensionsViewModelFactory(animeExtensionManager)
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var extensionsRecyclerView: RecyclerView
|
private val adapter by lazy {
|
||||||
private lateinit var allextenstionsRecyclerView: RecyclerView
|
AnimeExtensionAdapter(this)
|
||||||
private val animeExtensionManager: AnimeExtensionManager = Injekt.get<AnimeExtensionManager>()
|
}
|
||||||
private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
|
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
|
||||||
val context = requireContext() // Store context in a variable
|
|
||||||
val notificationManager =
|
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
|
||||||
|
|
||||||
if (pkg.hasUpdate) {
|
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
|
||||||
animeExtensionManager.updateExtension(pkg)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
override fun onCreateView(
|
||||||
.subscribe(
|
inflater: LayoutInflater,
|
||||||
{ installStep ->
|
container: ViewGroup?,
|
||||||
val builder = NotificationCompat.Builder(
|
savedInstanceState: Bundle?
|
||||||
context,
|
): View {
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_sync_24)
|
binding.allAnimeExtensionsRecyclerView.isNestedScrollingEnabled = true
|
||||||
.setContentTitle("Updating extension")
|
binding.allAnimeExtensionsRecyclerView.adapter = adapter
|
||||||
.setContentText("Step: $installStep")
|
binding.allAnimeExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
(binding.allAnimeExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = false
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
lifecycleScope.launch {
|
||||||
{ error ->
|
viewModel.pagerFlow.collectLatest {
|
||||||
FirebaseCrashlytics.getInstance().recordException(error)
|
adapter.submitData(it)
|
||||||
Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_ERROR
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_info_24)
|
|
||||||
.setContentTitle("Update failed: ${error.message}")
|
|
||||||
.setContentText("Error: ${error.message}")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
)
|
|
||||||
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
|
||||||
.setContentTitle("Update complete")
|
|
||||||
.setContentText("The extension has been successfully updated.")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, skipIcons)
|
|
||||||
|
|
||||||
private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope, { pkgName ->
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateContentBasedOnQuery(query: String?) {
|
||||||
|
viewModel.setSearchQuery(query ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInstallClick(pkg: AnimeExtension.Available) {
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
if (isAdded) {
|
if (isAdded) {
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
// Start the installation process
|
// Start the installation process
|
||||||
animeExtensionManager.installExtension(pkgName)
|
animeExtensionManager.installExtension(pkg)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ installStep ->
|
{ installStep ->
|
||||||
|
@ -136,63 +105,15 @@ class AnimeExtensionsFragment : Fragment(),
|
||||||
context,
|
context,
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
)
|
)
|
||||||
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
.setSmallIcon(R.drawable.ic_round_download_24)
|
||||||
.setContentTitle("Installation complete")
|
.setContentTitle("Installation complete")
|
||||||
.setContentText("The extension has been successfully installed.")
|
.setContentText("The extension has been successfully installed.")
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
notificationManager.notify(1, builder.build())
|
notificationManager.notify(1, builder.build())
|
||||||
|
viewModel.invalidatePager()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, skipIcons)
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
|
|
||||||
|
|
||||||
extensionsRecyclerView = binding.animeExtensionsRecyclerView
|
|
||||||
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
extensionsRecyclerView.adapter = extensionsAdapter
|
|
||||||
|
|
||||||
allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView
|
|
||||||
allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
allextenstionsRecyclerView.adapter = allExtensionsAdapter
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
animeExtensionManager.installedExtensionsFlow.collect { extensions ->
|
|
||||||
extensionsAdapter.updateData(extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lifecycleScope.launch {
|
|
||||||
combine(
|
|
||||||
animeExtensionManager.availableExtensionsFlow,
|
|
||||||
animeExtensionManager.installedExtensionsFlow
|
|
||||||
) { availableExtensions, installedExtensions ->
|
|
||||||
// Pair of available and installed extensions
|
|
||||||
Pair(availableExtensions, installedExtensions)
|
|
||||||
}.collect { pair ->
|
|
||||||
val (availableExtensions, installedExtensions) = pair
|
|
||||||
|
|
||||||
allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateContentBasedOnQuery(query: String?) {
|
|
||||||
if (query.isNullOrEmpty()) {
|
|
||||||
allExtensionsAdapter.filter("") // Reset the filter
|
|
||||||
allextenstionsRecyclerView.visibility = View.VISIBLE
|
|
||||||
extensionsRecyclerView.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
allExtensionsAdapter.filter(query)
|
|
||||||
allextenstionsRecyclerView.visibility = View.VISIBLE
|
|
||||||
extensionsRecyclerView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -200,158 +121,4 @@ class AnimeExtensionsFragment : Fragment(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class AnimeExtensionsAdapter(
|
|
||||||
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
|
|
||||||
skipIcons: Boolean
|
|
||||||
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
|
||||||
DIFF_CALLBACK_INSTALLED
|
|
||||||
) {
|
|
||||||
|
|
||||||
val skipIcons = skipIcons
|
|
||||||
|
|
||||||
fun updateData(newExtensions: List<AnimeExtension.Installed>) {
|
|
||||||
submitList(newExtensions) // Use submitList instead of manual list handling
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_extension, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
|
||||||
if (!skipIcons) {
|
|
||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
|
||||||
}
|
|
||||||
if (extension.hasUpdate) {
|
|
||||||
holder.closeTextView.text = "Update"
|
|
||||||
holder.closeTextView.setTextColor(
|
|
||||||
ContextCompat.getColor(
|
|
||||||
holder.itemView.context,
|
|
||||||
R.color.warning
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
holder.closeTextView.text = "Uninstall"
|
|
||||||
}
|
|
||||||
holder.closeTextView.setOnClickListener {
|
|
||||||
onUninstallClicked(extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val DIFF_CALLBACK_INSTALLED =
|
|
||||||
object : DiffUtil.ItemCallback<AnimeExtension.Installed>() {
|
|
||||||
override fun areItemsTheSame(
|
|
||||||
oldItem: AnimeExtension.Installed,
|
|
||||||
newItem: AnimeExtension.Installed
|
|
||||||
): Boolean {
|
|
||||||
return oldItem.pkgName == newItem.pkgName
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
|
||||||
oldItem: AnimeExtension.Installed,
|
|
||||||
newItem: AnimeExtension.Installed
|
|
||||||
): Boolean {
|
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class AllAnimeExtensionsAdapter(
|
|
||||||
private val coroutineScope: CoroutineScope,
|
|
||||||
private val onButtonClicked: (AnimeExtension.Available) -> Unit,
|
|
||||||
skipIcons: Boolean
|
|
||||||
) : ListAdapter<AnimeExtension.Available, AllAnimeExtensionsAdapter.ViewHolder>(
|
|
||||||
DIFF_CALLBACK_AVAILABLE
|
|
||||||
) {
|
|
||||||
val skipIcons = skipIcons
|
|
||||||
|
|
||||||
fun updateData(
|
|
||||||
newExtensions: List<AnimeExtension.Available>,
|
|
||||||
installedExtensions: List<AnimeExtension.Installed> = emptyList()
|
|
||||||
) {
|
|
||||||
coroutineScope.launch(Dispatchers.Default) {
|
|
||||||
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
|
|
||||||
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
|
|
||||||
|
|
||||||
// Switch back to main thread to update UI
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
submitList(filteredExtensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
|
||||||
parent: ViewGroup,
|
|
||||||
viewType: Int
|
|
||||||
): AllAnimeExtensionsAdapter.ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_extension_all, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val extension = getItem(position)
|
|
||||||
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
|
||||||
|
|
||||||
if (!skipIcons) {
|
|
||||||
Glide.with(holder.itemView.context)
|
|
||||||
.load(extension.iconUrl)
|
|
||||||
.into(holder.extensionIconImageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.closeTextView.text = "Install"
|
|
||||||
holder.closeTextView.setOnClickListener {
|
|
||||||
onButtonClicked(extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun filter(query: String) {
|
|
||||||
val filteredExtensions = if (query.isEmpty()) {
|
|
||||||
currentList
|
|
||||||
} else {
|
|
||||||
currentList.filter { it.name.contains(query, ignoreCase = true) }
|
|
||||||
}
|
|
||||||
submitList(filteredExtensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val DIFF_CALLBACK_AVAILABLE =
|
|
||||||
object : DiffUtil.ItemCallback<AnimeExtension.Available>() {
|
|
||||||
override fun areItemsTheSame(
|
|
||||||
oldItem: AnimeExtension.Available,
|
|
||||||
newItem: AnimeExtension.Available
|
|
||||||
): Boolean {
|
|
||||||
return oldItem.pkgName == newItem.pkgName
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
|
||||||
oldItem: AnimeExtension.Available,
|
|
||||||
newItem: AnimeExtension.Available
|
|
||||||
): Boolean {
|
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -70,12 +70,14 @@ class ExtensionsActivity : AppCompatActivity() {
|
||||||
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
|
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
|
||||||
|
|
||||||
viewPager.adapter = object : FragmentStateAdapter(this) {
|
viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||||
override fun getItemCount(): Int = 2
|
override fun getItemCount(): Int = 4
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
return when (position) {
|
return when (position) {
|
||||||
0 -> AnimeExtensionsFragment()
|
0 -> InstalledAnimeExtensionsFragment()
|
||||||
1 -> MangaExtensionsFragment()
|
1 -> AnimeExtensionsFragment()
|
||||||
|
2 -> InstalledMangaExtensionsFragment()
|
||||||
|
3 -> MangaExtensionsFragment()
|
||||||
else -> AnimeExtensionsFragment()
|
else -> AnimeExtensionsFragment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,8 +85,10 @@ class ExtensionsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||||
tab.text = when (position) {
|
tab.text = when (position) {
|
||||||
0 -> "Anime" // Your tab title
|
0 -> "Installed Anime"
|
||||||
1 -> "Manga" // Your tab title
|
1 -> "Available Anime"
|
||||||
|
2 -> "Installed Manga"
|
||||||
|
3 -> "Available Manga"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class InstalledAnimeExtensionsFragment : Fragment() {
|
||||||
|
private var _binding: FragmentAnimeExtensionsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var extensionsRecyclerView: RecyclerView
|
||||||
|
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
|
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
|
||||||
|
private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
|
||||||
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
|
val context = requireContext() // Store context in a variable
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
||||||
|
|
||||||
|
if (pkg.hasUpdate) {
|
||||||
|
animeExtensionManager.updateExtension(pkg)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Updating extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Update failed: ${error.message}")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
||||||
|
.setContentTitle("Update complete")
|
||||||
|
.setContentText("The extension has been successfully updated.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, skipIcons)
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
extensionsRecyclerView = binding.allAnimeExtensionsRecyclerView
|
||||||
|
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
extensionsRecyclerView.adapter = extensionsAdapter
|
||||||
|
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
animeExtensionManager.installedExtensionsFlow.collect { extensions ->
|
||||||
|
extensionsAdapter.updateData(extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val extensionsRecyclerView: RecyclerView = binding.allAnimeExtensionsRecyclerView
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView();_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AnimeExtensionsAdapter(
|
||||||
|
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
|
skipIcons: Boolean
|
||||||
|
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
||||||
|
DIFF_CALLBACK_INSTALLED
|
||||||
|
) {
|
||||||
|
|
||||||
|
val skipIcons = skipIcons
|
||||||
|
|
||||||
|
fun updateData(newExtensions: List<AnimeExtension.Installed>) {
|
||||||
|
submitList(newExtensions) // Use submitList instead of manual list handling
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_extension, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||||
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
if (!skipIcons) {
|
||||||
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
|
}
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
holder.closeTextView.text = "Update"
|
||||||
|
holder.closeTextView.setTextColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
holder.itemView.context,
|
||||||
|
R.color.warning
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
holder.closeTextView.text = "Uninstall"
|
||||||
|
}
|
||||||
|
holder.closeTextView.setOnClickListener {
|
||||||
|
onUninstallClicked(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
|
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_INSTALLED =
|
||||||
|
object : DiffUtil.ItemCallback<AnimeExtension.Installed>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: AnimeExtension.Installed,
|
||||||
|
newItem: AnimeExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: AnimeExtension.Installed,
|
||||||
|
newItem: AnimeExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class InstalledMangaExtensionsFragment : Fragment() {
|
||||||
|
private var _binding: FragmentMangaExtensionsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var extensionsRecyclerView: RecyclerView
|
||||||
|
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
|
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
|
||||||
|
private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
|
||||||
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
|
val context = requireContext() // Store context in a variable
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
||||||
|
|
||||||
|
if (pkg.hasUpdate) {
|
||||||
|
mangaExtensionManager.updateExtension(pkg)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Updating extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Update failed: ${error.message}")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
||||||
|
.setContentTitle("Update complete")
|
||||||
|
.setContentText("The extension has been successfully updated.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mangaExtensionManager.uninstallExtension(pkg.pkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, skipIcons)
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
extensionsRecyclerView = binding.allMangaExtensionsRecyclerView
|
||||||
|
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
extensionsRecyclerView.adapter = extensionsAdapter
|
||||||
|
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
mangaExtensionManager.installedExtensionsFlow.collect { extensions ->
|
||||||
|
extensionsAdapter.updateData(extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val extensionsRecyclerView: RecyclerView = binding.allMangaExtensionsRecyclerView
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView();_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MangaExtensionsAdapter(
|
||||||
|
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
|
||||||
|
skipIcons: Boolean
|
||||||
|
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
||||||
|
DIFF_CALLBACK_INSTALLED
|
||||||
|
) {
|
||||||
|
|
||||||
|
val skipIcons = skipIcons
|
||||||
|
|
||||||
|
fun updateData(newExtensions: List<MangaExtension.Installed>) {
|
||||||
|
submitList(newExtensions) // Use submitList instead of manual list handling
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_extension, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||||
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
if (!skipIcons) {
|
||||||
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
|
}
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
holder.closeTextView.text = "Update"
|
||||||
|
holder.closeTextView.setTextColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
holder.itemView.context,
|
||||||
|
R.color.warning
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
holder.closeTextView.text = "Uninstall"
|
||||||
|
}
|
||||||
|
holder.closeTextView.setOnClickListener {
|
||||||
|
onUninstallClicked(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
|
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_INSTALLED =
|
||||||
|
object : DiffUtil.ItemCallback<MangaExtension.Installed>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: MangaExtension.Installed,
|
||||||
|
newItem: MangaExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: MangaExtension.Installed,
|
||||||
|
newItem: MangaExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -2,150 +2,46 @@ package ani.dantotsu.settings
|
||||||
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.FragmentMangaBinding
|
|
||||||
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import ani.dantotsu.settings.paging.MangaExtensionAdapter
|
||||||
|
import ani.dantotsu.settings.paging.MangaExtensionsViewModel
|
||||||
|
import ani.dantotsu.settings.paging.MangaExtensionsViewModelFactory
|
||||||
|
import ani.dantotsu.settings.paging.OnMangaInstallClickListener
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
class MangaExtensionsFragment : Fragment(),
|
class MangaExtensionsFragment : Fragment(),
|
||||||
SearchQueryHandler {
|
SearchQueryHandler, OnMangaInstallClickListener {
|
||||||
private var _binding: FragmentMangaExtensionsBinding? = null
|
private var _binding: FragmentMangaExtensionsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
val skipIcons = loadData("skip_extension_icons") ?: false
|
private val viewModel: MangaExtensionsViewModel by viewModels {
|
||||||
|
MangaExtensionsViewModelFactory(mangaExtensionManager)
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var extensionsRecyclerView: RecyclerView
|
private val adapter by lazy {
|
||||||
private lateinit var allextenstionsRecyclerView: RecyclerView
|
MangaExtensionAdapter(this)
|
||||||
private val mangaExtensionManager: MangaExtensionManager = Injekt.get<MangaExtensionManager>()
|
}
|
||||||
private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
|
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
|
||||||
val context = requireContext() // Store context in a variable
|
|
||||||
val notificationManager =
|
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
|
||||||
|
|
||||||
if (pkg.hasUpdate) {
|
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
|
||||||
mangaExtensionManager.updateExtension(pkg)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
|
||||||
.subscribe(
|
|
||||||
{ installStep ->
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_sync_24)
|
|
||||||
.setContentTitle("Updating extension")
|
|
||||||
.setContentText("Step: $installStep")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{ error ->
|
|
||||||
FirebaseCrashlytics.getInstance().recordException(error)
|
|
||||||
Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_ERROR
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_info_24)
|
|
||||||
.setContentTitle("Update failed")
|
|
||||||
.setContentText("Error: ${error.message}")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
)
|
|
||||||
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
|
||||||
.setContentTitle("Update complete")
|
|
||||||
.setContentText("The extension has been successfully updated.")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
mangaExtensionManager.uninstallExtension(pkg.pkgName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, skipIcons)
|
|
||||||
|
|
||||||
private val allExtensionsAdapter =
|
|
||||||
AllMangaExtensionsAdapter(lifecycleScope, { pkgName ->
|
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
|
||||||
val context = requireContext()
|
|
||||||
val notificationManager =
|
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
|
|
||||||
// Start the installation process
|
|
||||||
mangaExtensionManager.installExtension(pkgName)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ installStep ->
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_sync_24)
|
|
||||||
.setContentTitle("Installing extension")
|
|
||||||
.setContentText("Step: $installStep")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{ error ->
|
|
||||||
FirebaseCrashlytics.getInstance().recordException(error)
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_ERROR
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_info_24)
|
|
||||||
.setContentTitle("Installation failed: ${error.message}")
|
|
||||||
.setContentText("Error: ${error.message}")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{
|
|
||||||
val builder = NotificationCompat.Builder(
|
|
||||||
context,
|
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
)
|
|
||||||
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
|
||||||
.setContentTitle("Installation complete")
|
|
||||||
.setContentText("The extension has been successfully installed.")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, skipIcons)
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -154,44 +50,70 @@ class MangaExtensionsFragment : Fragment(),
|
||||||
): View {
|
): View {
|
||||||
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
|
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
extensionsRecyclerView = binding.mangaExtensionsRecyclerView
|
binding.allMangaExtensionsRecyclerView.isNestedScrollingEnabled = true
|
||||||
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
binding.allMangaExtensionsRecyclerView.adapter = adapter
|
||||||
extensionsRecyclerView.adapter = extensionsAdapter
|
binding.allMangaExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
|
(binding.allMangaExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = false
|
||||||
allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView
|
|
||||||
allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
allextenstionsRecyclerView.adapter = allExtensionsAdapter
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
mangaExtensionManager.installedExtensionsFlow.collect { extensions ->
|
viewModel.pagerFlow.collectLatest {
|
||||||
extensionsAdapter.updateData(extensions)
|
adapter.submitData(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
|
||||||
combine(
|
|
||||||
mangaExtensionManager.availableExtensionsFlow,
|
|
||||||
mangaExtensionManager.installedExtensionsFlow
|
|
||||||
) { availableExtensions, installedExtensions ->
|
|
||||||
// Pair of available and installed extensions
|
|
||||||
Pair(availableExtensions, installedExtensions)
|
|
||||||
}.collect { pair ->
|
|
||||||
val (availableExtensions, installedExtensions) = pair
|
|
||||||
allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateContentBasedOnQuery(query: String?) {
|
override fun updateContentBasedOnQuery(query: String?) {
|
||||||
if (query.isNullOrEmpty()) {
|
viewModel.setSearchQuery(query ?: "")
|
||||||
allExtensionsAdapter.filter("") // Reset the filter
|
}
|
||||||
allextenstionsRecyclerView.visibility = View.VISIBLE
|
|
||||||
extensionsRecyclerView.visibility = View.VISIBLE
|
override fun onInstallClick(pkg: MangaExtension.Available) {
|
||||||
} else {
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
allExtensionsAdapter.filter(query)
|
val context = requireContext()
|
||||||
allextenstionsRecyclerView.visibility = View.VISIBLE
|
val notificationManager =
|
||||||
extensionsRecyclerView.visibility = View.GONE
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
// Start the installation process
|
||||||
|
mangaExtensionManager.installExtension(pkg)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Installing extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Installation failed: ${error.message}")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_download_24)
|
||||||
|
.setContentTitle("Installation complete")
|
||||||
|
.setContentText("The extension has been successfully installed.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
viewModel.invalidatePager()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,165 +121,6 @@ class MangaExtensionsFragment : Fragment(),
|
||||||
super.onDestroyView();_binding = null
|
super.onDestroyView();_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MangaExtensionsAdapter(
|
|
||||||
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
|
|
||||||
skipIcons: Boolean
|
|
||||||
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
|
||||||
DIFF_CALLBACK_INSTALLED
|
|
||||||
) {
|
|
||||||
|
|
||||||
val skipIcons = skipIcons
|
|
||||||
|
|
||||||
// Use submitList to update data
|
|
||||||
fun updateData(newExtensions: List<MangaExtension.Installed>) {
|
|
||||||
submitList(newExtensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_extension, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val extension = getItem(position) // Use getItem from ListAdapter
|
|
||||||
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
|
||||||
if (!skipIcons) {
|
|
||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extension.hasUpdate) {
|
|
||||||
holder.closeTextView.text = "Update"
|
|
||||||
holder.closeTextView.setTextColor(
|
|
||||||
ContextCompat.getColor(
|
|
||||||
holder.itemView.context,
|
|
||||||
R.color.warning
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
holder.closeTextView.text = "Uninstall"
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.closeTextView.setOnClickListener {
|
|
||||||
onUninstallClicked(extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val DIFF_CALLBACK_INSTALLED =
|
|
||||||
object : DiffUtil.ItemCallback<MangaExtension.Installed>() {
|
|
||||||
override fun areItemsTheSame(
|
|
||||||
oldItem: MangaExtension.Installed,
|
|
||||||
newItem: MangaExtension.Installed
|
|
||||||
): Boolean {
|
|
||||||
return oldItem.pkgName == newItem.pkgName
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
|
||||||
oldItem: MangaExtension.Installed,
|
|
||||||
newItem: MangaExtension.Installed
|
|
||||||
): Boolean {
|
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class AllMangaExtensionsAdapter(
|
|
||||||
private val coroutineScope: CoroutineScope,
|
|
||||||
private val onButtonClicked: (MangaExtension.Available) -> Unit,
|
|
||||||
skipIcons: Boolean
|
|
||||||
) : ListAdapter<MangaExtension.Available, AllMangaExtensionsAdapter.ViewHolder>(
|
|
||||||
DIFF_CALLBACK_AVAILABLE
|
|
||||||
) {
|
|
||||||
init {
|
|
||||||
setHasStableIds(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val skipIcons = skipIcons
|
|
||||||
|
|
||||||
// Use submitList to update the data
|
|
||||||
fun updateData(
|
|
||||||
newExtensions: List<MangaExtension.Available>,
|
|
||||||
installedExtensions: List<MangaExtension.Installed> = emptyList()
|
|
||||||
) {
|
|
||||||
coroutineScope.launch(Dispatchers.Default) {
|
|
||||||
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
|
|
||||||
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
|
|
||||||
|
|
||||||
// Switch back to main thread to update UI
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
submitList(filteredExtensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_extension_all, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val extension = getItem(position) // Use getItem from ListAdapter
|
|
||||||
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
|
||||||
if (!skipIcons) {
|
|
||||||
Glide.with(holder.itemView.context)
|
|
||||||
.load(extension.iconUrl)
|
|
||||||
.into(holder.extensionIconImageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.closeTextView.text = "Install"
|
|
||||||
holder.closeTextView.setOnClickListener {
|
|
||||||
onButtonClicked(extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering function
|
|
||||||
fun filter(query: String) {
|
|
||||||
val filteredExtensions = if (query.isEmpty()) {
|
|
||||||
currentList
|
|
||||||
} else {
|
|
||||||
currentList.filter { it.name.contains(query, ignoreCase = true) }
|
|
||||||
}
|
|
||||||
submitList(filteredExtensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val DIFF_CALLBACK_AVAILABLE =
|
|
||||||
object : DiffUtil.ItemCallback<MangaExtension.Available>() {
|
|
||||||
override fun areItemsTheSame(
|
|
||||||
oldItem: MangaExtension.Available,
|
|
||||||
newItem: MangaExtension.Available
|
|
||||||
): Boolean {
|
|
||||||
return oldItem.pkgName == newItem.pkgName
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
|
||||||
oldItem: MangaExtension.Available,
|
|
||||||
newItem: MangaExtension.Available
|
|
||||||
): Boolean {
|
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
package ani.dantotsu.settings.paging
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingDataAdapter
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.paging.cachedIn
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.databinding.ItemExtensionAllBinding
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
|
||||||
|
|
||||||
|
class AnimeExtensionsViewModelFactory(
|
||||||
|
private val animeExtensionManager: AnimeExtensionManager
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return AnimeExtensionsViewModel(animeExtensionManager) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AnimeExtensionsViewModel(
|
||||||
|
private val animeExtensionManager: AnimeExtensionManager
|
||||||
|
) : ViewModel() {
|
||||||
|
private val searchQuery = MutableStateFlow("")
|
||||||
|
private var currentPagingSource: AnimeExtensionPagingSource? = null
|
||||||
|
fun setSearchQuery(query: String) {
|
||||||
|
searchQuery.value = query
|
||||||
|
}
|
||||||
|
fun invalidatePager() {
|
||||||
|
currentPagingSource?.invalidate()
|
||||||
|
}
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val pagerFlow: Flow<PagingData<AnimeExtension.Available>> = searchQuery.flatMapLatest { query ->
|
||||||
|
Pager(
|
||||||
|
PagingConfig(
|
||||||
|
pageSize = 15,
|
||||||
|
initialLoadSize = 15,
|
||||||
|
prefetchDistance = 15
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
AnimeExtensionPagingSource(
|
||||||
|
animeExtensionManager.availableExtensionsFlow,
|
||||||
|
animeExtensionManager.installedExtensionsFlow,
|
||||||
|
searchQuery
|
||||||
|
).also { currentPagingSource = it }
|
||||||
|
}.flow
|
||||||
|
}.cachedIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimeExtensionPagingSource(
|
||||||
|
private val availableExtensionsFlow: StateFlow<List<AnimeExtension.Available>>,
|
||||||
|
private val installedExtensionsFlow: StateFlow<List<AnimeExtension.Installed>>,
|
||||||
|
private val searchQuery: StateFlow<String>
|
||||||
|
) : PagingSource<Int, AnimeExtension.Available>() {
|
||||||
|
|
||||||
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AnimeExtension.Available> {
|
||||||
|
val position = params.key ?: 0
|
||||||
|
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
|
||||||
|
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
|
||||||
|
val query = searchQuery.first()
|
||||||
|
val filteredExtensions = if (query.isEmpty()) {
|
||||||
|
availableExtensions
|
||||||
|
} else {
|
||||||
|
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val sublist = filteredExtensions.subList(
|
||||||
|
fromIndex = position,
|
||||||
|
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
|
||||||
|
)
|
||||||
|
LoadResult.Page(
|
||||||
|
data = sublist,
|
||||||
|
prevKey = if (position == 0) null else position - params.loadSize,
|
||||||
|
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoadResult.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRefreshKey(state: PagingState<Int, AnimeExtension.Available>): Int? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListener) :
|
||||||
|
PagingDataAdapter<AnimeExtension.Available, AnimeExtensionAdapter.AnimeExtensionViewHolder>(
|
||||||
|
DIFF_CALLBACK
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<AnimeExtension.Available>() {
|
||||||
|
override fun areItemsTheSame(oldItem: AnimeExtension.Available, newItem: AnimeExtension.Available): Boolean {
|
||||||
|
// Your logic here
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: AnimeExtension.Available, newItem: AnimeExtension.Available): Boolean {
|
||||||
|
// Your logic here
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimeExtensionViewHolder {
|
||||||
|
val binding = ItemExtensionAllBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return AnimeExtensionViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: AnimeExtensionViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position)
|
||||||
|
if (extension != null) {
|
||||||
|
if (!skipIcons) {
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(extension.iconUrl)
|
||||||
|
.into(holder.extensionIconImageView)
|
||||||
|
}
|
||||||
|
holder.bind(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class AnimeExtensionViewHolder(private val binding: ItemExtensionAllBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
init {
|
||||||
|
binding.closeTextView.setOnClickListener {
|
||||||
|
val extension = getItem(bindingAdapterPosition)
|
||||||
|
if (extension != null) {
|
||||||
|
clickListener.onInstallClick(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
||||||
|
fun bind(extension: AnimeExtension.Available) {
|
||||||
|
binding.extensionNameTextView.text = extension.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnAnimeInstallClickListener {
|
||||||
|
fun onInstallClick(pkg: AnimeExtension.Available)
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package ani.dantotsu.settings.paging
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.PagingDataAdapter
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.paging.cachedIn
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.databinding.ItemExtensionAllBinding
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import java.lang.Math.min
|
||||||
|
|
||||||
|
class MangaExtensionsViewModelFactory(
|
||||||
|
private val mangaExtensionManager: MangaExtensionManager
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return MangaExtensionsViewModel(mangaExtensionManager) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MangaExtensionsViewModel(
|
||||||
|
private val mangaExtensionManager: MangaExtensionManager
|
||||||
|
) : ViewModel() {
|
||||||
|
private val searchQuery = MutableStateFlow("")
|
||||||
|
private var currentPagingSource: MangaExtensionPagingSource? = null
|
||||||
|
|
||||||
|
fun setSearchQuery(query: String) {
|
||||||
|
searchQuery.value = query
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invalidatePager() {
|
||||||
|
currentPagingSource?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val pagerFlow: Flow<PagingData<MangaExtension.Available>> = searchQuery.flatMapLatest { query ->
|
||||||
|
Pager(
|
||||||
|
PagingConfig(
|
||||||
|
pageSize = 15,
|
||||||
|
initialLoadSize = 15,
|
||||||
|
prefetchDistance = 15
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
MangaExtensionPagingSource(
|
||||||
|
mangaExtensionManager.availableExtensionsFlow,
|
||||||
|
mangaExtensionManager.installedExtensionsFlow,
|
||||||
|
searchQuery
|
||||||
|
).also { currentPagingSource = it }
|
||||||
|
}.flow
|
||||||
|
}.cachedIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MangaExtensionPagingSource(
|
||||||
|
private val availableExtensionsFlow: StateFlow<List<MangaExtension.Available>>,
|
||||||
|
private val installedExtensionsFlow: StateFlow<List<MangaExtension.Installed>>,
|
||||||
|
private val searchQuery: StateFlow<String>
|
||||||
|
) : PagingSource<Int, MangaExtension.Available>() {
|
||||||
|
|
||||||
|
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MangaExtension.Available> {
|
||||||
|
val position = params.key ?: 0
|
||||||
|
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
|
||||||
|
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
|
||||||
|
val query = searchQuery.first()
|
||||||
|
val filteredExtensions = if (query.isEmpty()) {
|
||||||
|
availableExtensions
|
||||||
|
} else {
|
||||||
|
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val sublist = filteredExtensions.subList(
|
||||||
|
fromIndex = position,
|
||||||
|
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
|
||||||
|
)
|
||||||
|
LoadResult.Page(
|
||||||
|
data = sublist,
|
||||||
|
prevKey = if (position == 0) null else position - params.loadSize,
|
||||||
|
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoadResult.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRefreshKey(state: PagingState<Int, MangaExtension.Available>): Int? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListener) :
|
||||||
|
PagingDataAdapter<MangaExtension.Available, MangaExtensionAdapter.MangaExtensionViewHolder>(
|
||||||
|
DIFF_CALLBACK
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<MangaExtension.Available>() {
|
||||||
|
override fun areItemsTheSame(oldItem: MangaExtension.Available, newItem: MangaExtension.Available): Boolean {
|
||||||
|
// Your logic here
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: MangaExtension.Available, newItem: MangaExtension.Available): Boolean {
|
||||||
|
// Your logic here
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaExtensionViewHolder {
|
||||||
|
val binding = ItemExtensionAllBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return MangaExtensionViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: MangaExtensionViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position)
|
||||||
|
if (extension != null) {
|
||||||
|
if (!skipIcons) {
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(extension.iconUrl)
|
||||||
|
.into(holder.extensionIconImageView)
|
||||||
|
}
|
||||||
|
holder.bind(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class MangaExtensionViewHolder(private val binding: ItemExtensionAllBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
init {
|
||||||
|
binding.closeTextView.setOnClickListener {
|
||||||
|
val extension = getItem(bindingAdapterPosition)
|
||||||
|
if (extension != null) {
|
||||||
|
clickListener.onInstallClick(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
||||||
|
fun bind(extension: MangaExtension.Available) {
|
||||||
|
binding.extensionNameTextView.text = extension.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnMangaInstallClickListener {
|
||||||
|
fun onInstallClick(pkg: MangaExtension.Available)
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class Track(val url: String, val lang: String)
|
data class Track(val url: String, val lang: String) : Serializable
|
||||||
|
|
||||||
open class Video(
|
open class Video(
|
||||||
val url: String = "",
|
val url: String = "",
|
||||||
|
|
|
@ -108,8 +108,11 @@ internal class AnimeExtensionInstaller(private val context: Context) {
|
||||||
// Get the current download status
|
// Get the current download status
|
||||||
.map {
|
.map {
|
||||||
downloadManager.query(query).use { cursor ->
|
downloadManager.query(query).use { cursor ->
|
||||||
cursor.moveToFirst()
|
if (cursor.moveToFirst()) {
|
||||||
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
||||||
|
} else {
|
||||||
|
DownloadManager.STATUS_FAILED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ignore duplicate results
|
// Ignore duplicate results
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
app:cardBackgroundColor="#000000"
|
app:cardBackgroundColor="?attr/colorSurface"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:contentPadding="8dp"
|
app:contentPadding="8dp"
|
||||||
app:strokeColor="?attr/colorSecondary"
|
app:strokeColor="?attr/colorSecondary"
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:fontFamily="@font/poppins_bold"
|
android:fontFamily="@font/poppins_bold"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textColor="?android:colorBackground"
|
android:textColor="?attr/colorOnSurface"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|center_horizontal"
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
app:cardBackgroundColor="#000000"
|
app:cardBackgroundColor="?attr/colorSurface"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:contentPadding="8dp"
|
app:contentPadding="8dp"
|
||||||
app:strokeColor="?attr/colorSecondary"
|
app:strokeColor="?attr/colorSecondary"
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:fontFamily="@font/poppins_bold"
|
android:fontFamily="@font/poppins_bold"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textColor="?android:colorBackground"
|
android:textColor="?attr/colorOnSurface"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
@ -268,7 +268,11 @@
|
||||||
android:layout_marginEnd="48dp"
|
android:layout_marginEnd="48dp"
|
||||||
android:fontFamily="@font/poppins"
|
android:fontFamily="@font/poppins"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
android:textColor="?android:colorBackground"
|
android:textColor="@color/bg_white"
|
||||||
|
android:shadowColor="#000"
|
||||||
|
android:shadowDx="1"
|
||||||
|
android:shadowDy="1"
|
||||||
|
android:shadowRadius="1"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
tools:ignore="TextContrastCheck"
|
tools:ignore="TextContrastCheck"
|
||||||
tools:text="@string/popular_anime" />
|
tools:text="@string/popular_anime" />
|
||||||
|
|
|
@ -1,34 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/allAnimeExtensionsRecyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:orientation="vertical"
|
android:layout_weight="1" />
|
||||||
android:paddingStart="32dp"
|
|
||||||
android:paddingEnd="32dp">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
</LinearLayout>
|
||||||
android:id="@+id/animeExtensionsRecyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:text="All Extensions"
|
|
||||||
android:textSize="28sp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/allAnimeExtensionsRecyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
|
@ -1,35 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.core.widget.NestedScrollView
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/allMangaExtensionsRecyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:orientation="vertical"
|
android:layout_weight="1" />
|
||||||
android:paddingStart="32dp"
|
|
||||||
android:paddingEnd="32dp">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
</LinearLayout>
|
||||||
android:id="@+id/mangaExtensionsRecyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:text="All Extensions"
|
|
||||||
android:textSize="28sp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/allMangaExtensionsRecyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue