package ani.dantotsu.download.manga import android.content.Intent import android.net.Uri import android.os.Bundle import android.os.Environment import android.text.Editable import android.text.TextWatcher import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AlphaAnimation import android.view.animation.LayoutAnimationController import android.widget.AbsListView import android.widget.AutoCompleteTextView import android.widget.GridView import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import androidx.core.view.marginBottom import androidx.fragment.app.Fragment import ani.dantotsu.R import ani.dantotsu.bottomBar import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.currActivity import ani.dantotsu.currContext import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity import ani.dantotsu.util.Logger import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.navBarHeight import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.textfield.TextInputLayout import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapterImpl import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { private val downloadManager = Injekt.get() private var downloads: List = listOf() private lateinit var gridView: GridView private lateinit var adapter: OfflineMangaAdapter private lateinit var total: TextView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_offline_page, container, false) val textInputLayout = view.findViewById(R.id.offlineMangaSearchBar) textInputLayout.hint = "Manga" val currentColor = textInputLayout.boxBackgroundColor val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() textInputLayout.boxBackgroundColor = semiTransparentColor val materialCardView = view.findViewById(R.id.offlineMangaAvatarContainer) materialCardView.setCardBackgroundColor(semiTransparentColor) val typedValue = TypedValue() requireContext().theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) val color = typedValue.data val animeUserAvatar = view.findViewById(R.id.offlineMangaUserAvatar) animeUserAvatar.setSafeOnClickListener { val dialogFragment = SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.OfflineMANGA) dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog") } if (!(PrefManager.getVal(PrefName.ImmersiveMode) as Boolean)) { view.rootView.fitsSystemWindows = true } textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000 materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000) val searchView = view.findViewById(R.id.animeSearchBarText) searchView.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) { } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { onSearchQuery(s.toString()) } }) var style: Int = PrefManager.getVal(PrefName.OfflineView) val layoutList = view.findViewById(R.id.downloadedList) val layoutcompact = view.findViewById(R.id.downloadedGrid) var selected = when (style) { 0 -> layoutList 1 -> layoutcompact else -> layoutList } selected.alpha = 1f fun selected(it: ImageView) { selected.alpha = 0.33f selected = it selected.alpha = 1f } layoutList.setOnClickListener { selected(it as ImageView) style = 0 PrefManager.setVal(PrefName.OfflineView, style) gridView.visibility = View.GONE gridView = view.findViewById(R.id.gridView) adapter.notifyNewGrid() grid() } layoutcompact.setOnClickListener { selected(it as ImageView) style = 1 PrefManager.setVal(PrefName.OfflineView, style) gridView.visibility = View.GONE gridView = view.findViewById(R.id.gridView1) adapter.notifyNewGrid() grid() } gridView = if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1) total = view.findViewById(R.id.total) grid() return view } private fun grid() { gridView.visibility = View.VISIBLE getDownloads() val fadeIn = AlphaAnimation(0f, 1f) fadeIn.duration = 300 // animations pog gridView.layoutAnimation = LayoutAnimationController(fadeIn) adapter = OfflineMangaAdapter(requireContext(), downloads, this) gridView.adapter = adapter gridView.scheduleLayoutAnimation() total.text = if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List" gridView.setOnItemClickListener { _, _, position, _ -> // Get the OfflineMangaModel that was clicked val item = adapter.getItem(position) as OfflineMangaModel val media = downloadManager.mangaDownloadedTypes.firstOrNull { it.title == item.title } ?: downloadManager.novelDownloadedTypes.firstOrNull { it.title == item.title } media?.let { ContextCompat.startActivity( requireActivity(), Intent(requireContext(), MediaDetailsActivity::class.java) .putExtra("media", getMedia(it)) .putExtra("download", true), null ) } ?: run { snackString("no media found") } } gridView.setOnItemLongClickListener { _, _, position, _ -> // Get the OfflineMangaModel that was clicked val item = adapter.getItem(position) as OfflineMangaModel val type: DownloadedType.Type = if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) { DownloadedType.Type.MANGA } else { DownloadedType.Type.NOVEL } // Alert dialog to confirm deletion val builder = androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup) builder.setTitle("Delete ${item.title}?") builder.setMessage("Are you sure you want to delete ${item.title}?") builder.setPositiveButton("Yes") { _, _ -> downloadManager.removeMedia(item.title, type) getDownloads() adapter.setItems(downloads) total.text = if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List" } builder.setNegativeButton("No") { _, _ -> // Do nothing } val dialog = builder.show() dialog.window?.setDimAmount(0.8f) true } } override fun onSearchQuery(query: String) { adapter.onSearchQuery(query) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initActivity(requireActivity()) val scrollTop = view.findViewById(R.id.mangaPageScrollTop) scrollTop.setOnClickListener { gridView.smoothScrollToPositionFromTop(0, 0) } // Assuming 'scrollTop' is a view that you want to hide/show scrollTop.visibility = View.GONE gridView.setOnScrollListener(object : AbsListView.OnScrollListener { override fun onScrollStateChanged(view: AbsListView, scrollState: Int) { // Implement behavior for different scroll states if needed } override fun onScroll( view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int ) { val first = view.getChildAt(0) val visibility = first != null && first.top < 0 scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE scrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() } }) } override fun onResume() { super.onResume() getDownloads() adapter.notifyDataSetChanged() } override fun onPause() { super.onPause() downloads = listOf() } override fun onDestroy() { super.onDestroy() downloads = listOf() } override fun onStop() { super.onStop() downloads = listOf() } private fun getDownloads() { downloads = listOf() val mangaTitles = downloadManager.mangaDownloadedTypes.map { it.title }.distinct() val newMangaDownloads = mutableListOf() for (title in mangaTitles) { val tDownloads = downloadManager.mangaDownloadedTypes.filter { it.title == title } val download = tDownloads.first() val offlineMangaModel = loadOfflineMangaModel(download) newMangaDownloads += offlineMangaModel } downloads = newMangaDownloads val novelTitles = downloadManager.novelDownloadedTypes.map { it.title }.distinct() val newNovelDownloads = mutableListOf() for (title in novelTitles) { val tDownloads = downloadManager.novelDownloadedTypes.filter { it.title == title } val download = tDownloads.first() val offlineMangaModel = loadOfflineMangaModel(download) newNovelDownloads += offlineMangaModel } downloads += newNovelDownloads } private fun getMedia(downloadedType: DownloadedType): Media? { val type = when (downloadedType.type) { DownloadedType.Type.MANGA -> "Manga" DownloadedType.Type.ANIME -> "Anime" else -> "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$type/${downloadedType.title}" ) //load media.json and convert to media class with gson return try { val gson = GsonBuilder() .registerTypeAdapter(SChapter::class.java, InstanceCreator { SChapterImpl() // Provide an instance of SChapterImpl }) .create() val media = File(directory, "media.json") val mediaJson = media.readText() gson.fromJson(mediaJson, Media::class.java) } catch (e: Exception) { Logger.log("Error loading media.json: ${e.message}") Logger.log(e) Injekt.get().logException(e) null } } private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { val type = when (downloadedType.type) { DownloadedType.Type.MANGA -> "Manga" DownloadedType.Type.ANIME -> "Anime" else -> "Novel" } val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$type/${downloadedType.title}" ) //load media.json and convert to media class with gson try { val mediaModel = getMedia(downloadedType)!! val cover = File(directory, "cover.jpg") val coverUri: Uri? = if (cover.exists()) { Uri.fromFile(cover) } else null val banner = File(directory, "banner.jpg") val bannerUri: Uri? = if (banner.exists()) { Uri.fromFile(banner) } else null val title = mediaModel.mainName() val score = ((if (mediaModel.userScore == 0) (mediaModel.meanScore ?: 0) else mediaModel.userScore) / 10.0).toString() val isOngoing = mediaModel.status == currActivity()!!.getString(R.string.status_releasing) val isUserScored = mediaModel.userScore != 0 val readchapter = (mediaModel.userProgress ?: "~").toString() val totalchapter = "${mediaModel.manga?.totalChapters ?: "??"}" val chapters = " Chapters" return OfflineMangaModel( title, score, totalchapter, readchapter, type, chapters, isOngoing, isUserScored, coverUri, bannerUri ) } catch (e: Exception) { Logger.log("Error loading media.json: ${e.message}") Logger.log(e) Injekt.get().logException(e) return OfflineMangaModel( "unknown", "0", "??", "??", "movie", "hmm", isOngoing = false, isUserScored = false, null, null ) } } } interface OfflineMangaSearchListener { fun onSearchQuery(query: String) }