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 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 ani.dantotsu.R import ani.dantotsu.currContext 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.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.launch import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Locale class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { 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 onDestroyView() { super.onDestroyView();_binding = null } override fun updateContentBasedOnQuery(query: String?) { extensionsAdapter.filter(query ?: "", novelExtensionManager.installedExtensionsFlow.value) } private class NovelExtensionsAdapter( private val onSettingsClicked: (NovelExtension.Installed) -> Unit, private val onUninstallClicked: (NovelExtension.Installed) -> Unit, skipIcons: Boolean ) : ListAdapter( DIFF_CALLBACK_INSTALLED ) { val skipIcons = skipIcons fun updateData(newExtensions: List) { 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) } } fun filter(query: String, currentList: List) { val filteredList = ArrayList() for (extension in currentList) { if (extension.name.lowercase(Locale.ROOT).contains(query.lowercase(Locale.ROOT))) { filteredList.add(extension) } } submitList(filteredList) } 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() { 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 } } } } }