Light novel support
This commit is contained in:
parent
32f918450a
commit
c7bc1ffe9e
39 changed files with 2537 additions and 91 deletions
|
@ -17,6 +17,7 @@ import ani.dantotsu.settings.paging.AnimeExtensionAdapter
|
|||
import ani.dantotsu.settings.paging.AnimeExtensionsViewModel
|
||||
import ani.dantotsu.settings.paging.AnimeExtensionsViewModelFactory
|
||||
import ani.dantotsu.settings.paging.OnAnimeInstallClickListener
|
||||
import ani.dantotsu.snackString
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||
|
@ -101,6 +102,7 @@ class AnimeExtensionsFragment : Fragment(),
|
|||
.setContentText("Error: ${error.message}")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
notificationManager.notify(1, builder.build())
|
||||
snackString("Installation failed: ${error.message}")
|
||||
},
|
||||
{
|
||||
val builder = NotificationCompat.Builder(
|
||||
|
@ -113,6 +115,7 @@ class AnimeExtensionsFragment : Fragment(),
|
|||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
notificationManager.notify(1, builder.build())
|
||||
viewModel.invalidatePager()
|
||||
snackString("Extension installed")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ class DevelopersDialogFragment : BottomSheetDialogFragment() {
|
|||
Developer("rebelonion","https://avatars.githubusercontent.com/u/87634197?v=4","Owner and Maintainer","https://github.com/rebelonion"),
|
||||
Developer("Wai What", "https://avatars.githubusercontent.com/u/149729762?v=4", "Icon Designer", "https://github.com/WaiWhat"),
|
||||
Developer("Aayush262", "https://avatars.githubusercontent.com/u/99584765?v=4", "Contributor", "https://github.com/aayush2622"),
|
||||
Developer("MarshMeadow", "https://avatars.githubusercontent.com/u/88599122?v=4", "Beta Icon Designer", "https://github.com/MarshMeadow?tab=repositories"),
|
||||
)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
|
|
|
@ -40,7 +40,7 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
LangSet.setLocale(this)
|
||||
ThemeManager(this).applyTheme()
|
||||
ThemeManager(this).applyTheme()
|
||||
binding = ActivityExtensionsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
|
@ -49,7 +49,7 @@ ThemeManager(this).applyTheme()
|
|||
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
|
||||
|
||||
viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||
override fun getItemCount(): Int = 4
|
||||
override fun getItemCount(): Int = 6
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
|
@ -57,24 +57,45 @@ ThemeManager(this).applyTheme()
|
|||
1 -> AnimeExtensionsFragment()
|
||||
2 -> InstalledMangaExtensionsFragment()
|
||||
3 -> MangaExtensionsFragment()
|
||||
4 -> InstalledNovelExtensionsFragment()
|
||||
5 -> NovelExtensionsFragment()
|
||||
else -> AnimeExtensionsFragment()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val searchView: AutoCompleteTextView = findViewById(R.id.searchViewText)
|
||||
|
||||
tabLayout.addOnTabSelectedListener(
|
||||
object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
searchView.setText("")
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||
tab.text = when (position) {
|
||||
0 -> "Installed Anime"
|
||||
1 -> "Available Anime"
|
||||
2 -> "Installed Manga"
|
||||
3 -> "Available Manga"
|
||||
4 -> "Installed Novels"
|
||||
5 -> "Available Novels"
|
||||
else -> null
|
||||
}
|
||||
}.attach()
|
||||
|
||||
|
||||
val searchView: AutoCompleteTextView = findViewById(R.id.searchViewText)
|
||||
|
||||
searchView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import ani.dantotsu.loadData
|
|||
import ani.dantotsu.others.LanguageMapper
|
||||
import ani.dantotsu.saveData
|
||||
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||
import ani.dantotsu.snackString
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
|
@ -157,6 +158,7 @@ class InstalledAnimeExtensionsFragment : Fragment() {
|
|||
.setContentText("Error: ${error.message}")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
notificationManager.notify(1, builder.build())
|
||||
snackString("Update failed: ${error.message}")
|
||||
},
|
||||
{
|
||||
val builder = NotificationCompat.Builder(
|
||||
|
@ -168,10 +170,12 @@ class InstalledAnimeExtensionsFragment : Fragment() {
|
|||
.setContentText("The extension has been successfully updated.")
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
notificationManager.notify(1, builder.build())
|
||||
snackString("Extension updated")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
||||
snackString("Extension uninstalled")
|
||||
}
|
||||
}
|
||||
}, skipIcons
|
||||
|
|
|
@ -26,6 +26,7 @@ import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
|||
import ani.dantotsu.loadData
|
||||
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||
import ani.dantotsu.others.LanguageMapper
|
||||
import ani.dantotsu.snackString
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
|
@ -137,6 +138,7 @@ class InstalledMangaExtensionsFragment : Fragment() {
|
|||
.setContentText("Error: ${error.message}")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
notificationManager.notify(1, builder.build())
|
||||
snackString("Update failed: ${error.message}")
|
||||
},
|
||||
{
|
||||
val builder = NotificationCompat.Builder(
|
||||
|
@ -148,10 +150,12 @@ class InstalledMangaExtensionsFragment : Fragment() {
|
|||
.setContentText("The extension has been successfully updated.")
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
notificationManager.notify(1, builder.build())
|
||||
snackString("Extension updated")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
mangaExtensionManager.uninstallExtension(pkg.pkgName)
|
||||
snackString("Extension uninstalled")
|
||||
}
|
||||
}
|
||||
}, skipIcons)
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
package ani.dantotsu.settings
|
||||
|
||||
import android.app.AlertDialog
|
||||
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.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
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 androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
||||
import ani.dantotsu.databinding.FragmentNovelExtensionsBinding
|
||||
import ani.dantotsu.loadData
|
||||
import ani.dantotsu.others.LanguageMapper
|
||||
import ani.dantotsu.parsers.novel.NovelExtension
|
||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||
import ani.dantotsu.snackString
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class InstalledNovelExtensionsFragment : Fragment() {
|
||||
private var _binding: FragmentNovelExtensionsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var extensionsRecyclerView: RecyclerView
|
||||
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||
private val novelExtensionManager: NovelExtensionManager = Injekt.get()
|
||||
private val extensionsAdapter = NovelExtensionsAdapter({ pkg ->
|
||||
Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
},
|
||||
{ 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) {
|
||||
novelExtensionManager.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("NovelExtensionsAdapter", "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())
|
||||
snackString("Update failed: ${error.message}")
|
||||
},
|
||||
{
|
||||
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())
|
||||
snackString("Extension updated")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
novelExtensionManager.uninstallExtension(pkg.pkgName, currContext()?:context)
|
||||
snackString("Extension uninstalled")
|
||||
}
|
||||
}
|
||||
}, skipIcons)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentNovelExtensionsBinding.inflate(inflater, container, false)
|
||||
|
||||
extensionsRecyclerView = binding.allNovelExtensionsRecyclerView
|
||||
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
extensionsRecyclerView.adapter = extensionsAdapter
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
novelExtensionManager.installedExtensionsFlow.collect { extensions ->
|
||||
extensionsAdapter.updateData(extensions)
|
||||
}
|
||||
}
|
||||
val extensionsRecyclerView: RecyclerView = binding.allNovelExtensionsRecyclerView
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView();_binding = null
|
||||
}
|
||||
|
||||
|
||||
private class NovelExtensionsAdapter(
|
||||
private val onSettingsClicked: (NovelExtension.Installed) -> Unit,
|
||||
private val onUninstallClicked: (NovelExtension.Installed) -> Unit,
|
||||
skipIcons: Boolean
|
||||
) : ListAdapter<NovelExtension.Installed, NovelExtensionsAdapter.ViewHolder>(
|
||||
DIFF_CALLBACK_INSTALLED
|
||||
) {
|
||||
|
||||
val skipIcons = skipIcons
|
||||
|
||||
fun updateData(newExtensions: List<NovelExtension.Installed>) {
|
||||
Log.d("NovelExtensionsAdapter", "updateData: $newExtensions")
|
||||
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)
|
||||
Log.d("NovelExtensionsAdapter", "onCreateViewHolder: $view")
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||
val nsfw = ""
|
||||
val lang = LanguageMapper.mapLanguageCodeToName("all")
|
||||
holder.extensionNameTextView.text = extension.name
|
||||
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
if (!skipIcons) {
|
||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||
}
|
||||
if (extension.hasUpdate) {
|
||||
holder.closeTextView.setImageResource(R.drawable.ic_round_sync_24)
|
||||
} else {
|
||||
holder.closeTextView.setImageResource(R.drawable.ic_round_delete_24)
|
||||
}
|
||||
holder.closeTextView.setOnClickListener {
|
||||
onUninstallClicked(extension)
|
||||
}
|
||||
holder.settingsImageView.setOnClickListener {
|
||||
onSettingsClicked(extension)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||
val extensionVersionTextView: TextView = view.findViewById(R.id.extensionVersionTextView)
|
||||
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||
val closeTextView: ImageView = view.findViewById(R.id.closeTextView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DIFF_CALLBACK_INSTALLED =
|
||||
object : DiffUtil.ItemCallback<NovelExtension.Installed>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: NovelExtension.Installed,
|
||||
newItem: NovelExtension.Installed
|
||||
): Boolean {
|
||||
return oldItem.pkgName == newItem.pkgName
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: NovelExtension.Installed,
|
||||
newItem: NovelExtension.Installed
|
||||
): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ 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 ani.dantotsu.snackString
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
|
@ -104,6 +105,7 @@ class MangaExtensionsFragment : Fragment(),
|
|||
.setContentText("Error: ${error.message}")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
notificationManager.notify(1, builder.build())
|
||||
snackString("Installation failed: ${error.message}")
|
||||
},
|
||||
{
|
||||
val builder = NotificationCompat.Builder(
|
||||
|
@ -116,6 +118,7 @@ class MangaExtensionsFragment : Fragment(),
|
|||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
notificationManager.notify(1, builder.build())
|
||||
viewModel.invalidatePager()
|
||||
snackString("Extension installed")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package ani.dantotsu.settings
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.databinding.FragmentNovelExtensionsBinding
|
||||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.parsers.novel.FileObserver.fileObserver
|
||||
import ani.dantotsu.parsers.novel.NovelExtension
|
||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||
import ani.dantotsu.settings.paging.NovelExtensionAdapter
|
||||
import ani.dantotsu.settings.paging.NovelExtensionsViewModel
|
||||
import ani.dantotsu.settings.paging.NovelExtensionsViewModelFactory
|
||||
import ani.dantotsu.settings.paging.OnNovelInstallClickListener
|
||||
import ani.dantotsu.snackString
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.observeOn
|
||||
import kotlinx.coroutines.flow.subscribe
|
||||
import kotlinx.coroutines.launch
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class NovelExtensionsFragment : Fragment(),
|
||||
SearchQueryHandler, OnNovelInstallClickListener {
|
||||
private var _binding: FragmentNovelExtensionsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel: NovelExtensionsViewModel by viewModels {
|
||||
NovelExtensionsViewModelFactory(novelExtensionManager)
|
||||
}
|
||||
|
||||
private val adapter by lazy {
|
||||
NovelExtensionAdapter(this)
|
||||
}
|
||||
|
||||
private val novelExtensionManager: NovelExtensionManager = Injekt.get()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentNovelExtensionsBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.allNovelExtensionsRecyclerView.isNestedScrollingEnabled = false
|
||||
binding.allNovelExtensionsRecyclerView.adapter = adapter
|
||||
binding.allNovelExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||
(binding.allNovelExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = true
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.pagerFlow.collectLatest { pagingData ->
|
||||
adapter.submitData(pagingData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
viewModel.invalidatePager() // Force a refresh of the pager
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun updateContentBasedOnQuery(query: String?) {
|
||||
viewModel.setSearchQuery(query ?: "")
|
||||
}
|
||||
|
||||
override fun onInstallClick(pkg: NovelExtension.Available) {
|
||||
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
|
||||
novelExtensionManager.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())
|
||||
snackString("Installation failed: ${error.message}")
|
||||
},
|
||||
{
|
||||
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()
|
||||
snackString("Extension installed")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView();_binding = null
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
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 ani.dantotsu.others.LanguageMapper
|
||||
import ani.dantotsu.parsers.novel.NovelExtension
|
||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||
import com.bumptech.glide.Glide
|
||||
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 NovelExtensionsViewModelFactory(
|
||||
private val novelExtensionManager: NovelExtensionManager
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return NovelExtensionsViewModel(novelExtensionManager) as T
|
||||
}
|
||||
}
|
||||
|
||||
class NovelExtensionsViewModel(
|
||||
private val novelExtensionManager: NovelExtensionManager
|
||||
) : ViewModel() {
|
||||
private val searchQuery = MutableStateFlow("")
|
||||
private var currentPagingSource: NovelExtensionPagingSource? = null
|
||||
|
||||
fun setSearchQuery(query: String) {
|
||||
searchQuery.value = query
|
||||
}
|
||||
|
||||
fun invalidatePager() {
|
||||
currentPagingSource?.invalidate()
|
||||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val pagerFlow: Flow<PagingData<NovelExtension.Available>> = searchQuery.flatMapLatest { query ->
|
||||
Pager(
|
||||
PagingConfig(
|
||||
pageSize = 15,
|
||||
initialLoadSize = 15,
|
||||
prefetchDistance = 15
|
||||
)
|
||||
) {
|
||||
NovelExtensionPagingSource(
|
||||
novelExtensionManager.availableExtensionsFlow,
|
||||
novelExtensionManager.installedExtensionsFlow,
|
||||
searchQuery
|
||||
).also { currentPagingSource = it }
|
||||
}.flow
|
||||
}.cachedIn(viewModelScope)
|
||||
}
|
||||
|
||||
|
||||
class NovelExtensionPagingSource(
|
||||
private val availableExtensionsFlow: StateFlow<List<NovelExtension.Available>>,
|
||||
private val installedExtensionsFlow: StateFlow<List<NovelExtension.Installed>>,
|
||||
private val searchQuery: StateFlow<String>
|
||||
) : PagingSource<Int, NovelExtension.Available>() {
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, NovelExtension.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 isNsfwEnabled: Boolean = loadData("NFSWExtension") ?: true
|
||||
val filteredExtensions = if (query.isEmpty()) {
|
||||
availableExtensions
|
||||
} else {
|
||||
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
||||
}
|
||||
val filternfsw = filteredExtensions
|
||||
/*val filternfsw = if(isNsfwEnabled) { currently not implemented
|
||||
filteredExtensions
|
||||
} else {
|
||||
filteredExtensions.filterNot { it.isNsfw }
|
||||
}*/
|
||||
return try {
|
||||
val sublist = filternfsw.subList(
|
||||
fromIndex = position,
|
||||
toIndex = (position + params.loadSize).coerceAtMost(filternfsw.size)
|
||||
)
|
||||
LoadResult.Page(
|
||||
data = sublist,
|
||||
prevKey = if (position == 0) null else position - params.loadSize,
|
||||
nextKey = if (position + params.loadSize >= filternfsw.size) null else position + params.loadSize
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, NovelExtension.Available>): Int? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class NovelExtensionAdapter(private val clickListener: OnNovelInstallClickListener) :
|
||||
PagingDataAdapter<NovelExtension.Available, NovelExtensionAdapter.NovelExtensionViewHolder>(
|
||||
DIFF_CALLBACK
|
||||
) {
|
||||
|
||||
private val skipIcons = loadData("skip_extension_icons") ?: false
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<NovelExtension.Available>() {
|
||||
override fun areItemsTheSame(oldItem: NovelExtension.Available, newItem: NovelExtension.Available): Boolean {
|
||||
return oldItem.pkgName == newItem.pkgName
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: NovelExtension.Available, newItem: NovelExtension.Available): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NovelExtensionViewHolder {
|
||||
val binding = ItemExtensionAllBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return NovelExtensionViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: NovelExtensionViewHolder, 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 NovelExtensionViewHolder(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: NovelExtension.Available) {
|
||||
val nsfw = ""
|
||||
val lang= LanguageMapper.mapLanguageCodeToName("all")
|
||||
binding.extensionNameTextView.text = extension.name
|
||||
binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OnNovelInstallClickListener {
|
||||
fun onInstallClick(pkg: NovelExtension.Available)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue