offline anime
This commit is contained in:
parent
52dadf34cf
commit
98cb11e841
17 changed files with 630 additions and 22 deletions
|
@ -14,6 +14,7 @@ import android.provider.Settings
|
|||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.AnticipateInterpolator
|
||||
import android.widget.TextView
|
||||
import androidx.activity.addCallback
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package ani.dantotsu.download.anime
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.download.anime.OfflineAnimeModel
|
||||
import ani.dantotsu.download.anime.OfflineAnimeSearchListener
|
||||
|
||||
|
||||
class OfflineAnimeAdapter(
|
||||
private val context: Context,
|
||||
private var items: List<OfflineAnimeModel>,
|
||||
private val searchListener: OfflineAnimeSearchListener
|
||||
) : BaseAdapter() {
|
||||
private val inflater: LayoutInflater =
|
||||
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
private var originalItems: List<OfflineAnimeModel> = items
|
||||
private var style =
|
||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
|
||||
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Any {
|
||||
return items[position]
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
|
||||
val view: View = convertView ?: when (style) {
|
||||
0 -> inflater.inflate(R.layout.item_media_large, parent, false) // large view
|
||||
1 -> inflater.inflate(R.layout.item_media_compact, parent, false) // compact view
|
||||
else -> inflater.inflate(R.layout.item_media_compact, parent, false) // compact view
|
||||
}
|
||||
|
||||
val item = getItem(position) as OfflineAnimeModel
|
||||
val imageView = view.findViewById<ImageView>(R.id.itemCompactImage)
|
||||
val titleTextView = view.findViewById<TextView>(R.id.itemCompactTitle)
|
||||
val itemScore = view.findViewById<TextView>(R.id.itemCompactScore)
|
||||
val itemScoreBG = view.findViewById<View>(R.id.itemCompactScoreBG)
|
||||
val ongoing = view.findViewById<CardView>(R.id.itemCompactOngoing)
|
||||
val totalchapter = view.findViewById<TextView>(R.id.itemCompactTotal)
|
||||
val typeimage = view.findViewById<ImageView>(R.id.itemCompactTypeImage)
|
||||
val type = view.findViewById<TextView>(R.id.itemCompactRelation)
|
||||
val typeView = view.findViewById<LinearLayout>(R.id.itemCompactType)
|
||||
|
||||
if (style == 0) {
|
||||
val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view
|
||||
val chapters = view.findViewById<TextView>(R.id.itemTotal)
|
||||
chapters.text = " Chapters"
|
||||
bannerView.setImageURI(item.banner)
|
||||
totalchapter.text = item.totalEpisode
|
||||
} else if (style == 1) {
|
||||
val readchapter =
|
||||
view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view
|
||||
readchapter.text = item.watchedEpisode
|
||||
totalchapter.text = " | " + item.totalEpisode
|
||||
}
|
||||
|
||||
// Bind item data to the views
|
||||
typeimage.setImageResource(R.drawable.ic_round_movie_filter_24)
|
||||
type.text = item.type
|
||||
typeView.visibility = View.VISIBLE
|
||||
imageView.setImageURI(item.image)
|
||||
titleTextView.text = item.title
|
||||
itemScore.text = item.score
|
||||
|
||||
if (item.isOngoing) {
|
||||
ongoing.visibility = View.VISIBLE
|
||||
} else {
|
||||
ongoing.visibility = View.GONE
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
fun onSearchQuery(query: String) {
|
||||
// Implement the filtering logic here, for example:
|
||||
items = if (query.isEmpty()) {
|
||||
// Return the original list if the query is empty
|
||||
originalItems
|
||||
} else {
|
||||
// Filter the list based on the query
|
||||
originalItems.filter { it.title.contains(query, ignoreCase = true) }
|
||||
}
|
||||
notifyDataSetChanged() // Notify the adapter that the data set has changed
|
||||
}
|
||||
|
||||
fun setItems(items: List<OfflineAnimeModel>) {
|
||||
this.items = items
|
||||
this.originalItems = items
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun notifyNewGrid() {
|
||||
style =
|
||||
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("offline_view", 0)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
package ani.dantotsu.download.anime
|
||||
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
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.view.animation.OvershootInterpolator
|
||||
import android.widget.AbsListView
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.GridView
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.ThemedSpinnerAdapter.Helper
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.core.view.updatePaddingRelative
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import ani.dantotsu.R
|
||||
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.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.px
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.SettingsDialogFragment
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.statusBarHeight
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.InstanceCreator
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnimeImpl
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisodeImpl
|
||||
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
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
||||
|
||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||
private var downloads: List<OfflineAnimeModel> = listOf()
|
||||
private lateinit var gridView: GridView
|
||||
private lateinit var adapter: OfflineAnimeAdapter
|
||||
|
||||
@OptIn(UnstableApi::class) override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_manga_offline, container, false)
|
||||
|
||||
val textInputLayout = view.findViewById<TextInputLayout>(R.id.offlineMangaSearchBar)
|
||||
textInputLayout.hint = "Anime"
|
||||
val currentColor = textInputLayout.boxBackgroundColor
|
||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||
val materialCardView = view.findViewById<MaterialCardView>(R.id.offlineMangaAvatarContainer)
|
||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||
val typedValue = TypedValue()
|
||||
requireContext().theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||
val color = typedValue.data
|
||||
|
||||
val animeTitleContainer = view.findViewById<LinearLayout>(R.id.animeTitleContainer)
|
||||
animeTitleContainer.updatePadding(top = statusBarHeight)
|
||||
|
||||
val animeUserAvatar = view.findViewById<ShapeableImageView>(R.id.offlineMangaUserAvatar)
|
||||
animeUserAvatar.setSafeOnClickListener {
|
||||
val dialogFragment =
|
||||
SettingsDialogFragment.newInstance2(SettingsDialogFragment.Companion.PageType2.OfflineANIME)
|
||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||
}
|
||||
|
||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||
?.getBoolean("colorOverflow", false) ?: false
|
||||
if (!colorOverflow) {
|
||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||
}
|
||||
|
||||
val searchView = view.findViewById<AutoCompleteTextView>(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 = context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||
?.getInt("offline_view", 0)
|
||||
val layoutList = view.findViewById<ImageView>(R.id.downloadedList)
|
||||
val layoutcompact = view.findViewById<ImageView>(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
|
||||
context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
||||
?.putInt("offline_view", style!!)?.apply()
|
||||
gridView.visibility = View.GONE
|
||||
gridView = view.findViewById(R.id.gridView)
|
||||
gridView.adapter = adapter
|
||||
gridView.scheduleLayoutAnimation()
|
||||
gridView.visibility = View.VISIBLE
|
||||
adapter.notifyNewGrid()
|
||||
|
||||
}
|
||||
|
||||
layoutcompact.setOnClickListener {
|
||||
selected(it as ImageView)
|
||||
style = 1
|
||||
context?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.edit()
|
||||
?.putInt("offline_view", style!!)?.apply()
|
||||
gridView.visibility = View.GONE
|
||||
gridView = view.findViewById(R.id.gridView1)
|
||||
gridView.adapter = adapter
|
||||
gridView.scheduleLayoutAnimation()
|
||||
gridView.visibility = View.VISIBLE
|
||||
adapter.notifyNewGrid()
|
||||
}
|
||||
|
||||
gridView =
|
||||
if (style == 0) view.findViewById(R.id.gridView) else view.findViewById(R.id.gridView1)
|
||||
gridView.visibility = View.VISIBLE
|
||||
getDownloads()
|
||||
|
||||
val fadeIn = AlphaAnimation(0f, 1f)
|
||||
fadeIn.duration = 200 // animations pog
|
||||
val animation = LayoutAnimationController(fadeIn)
|
||||
|
||||
gridView.layoutAnimation = animation
|
||||
adapter = OfflineAnimeAdapter(requireContext(), downloads, this)
|
||||
gridView.adapter = adapter
|
||||
gridView.scheduleLayoutAnimation()
|
||||
gridView.setOnItemClickListener { parent, view, position, id ->
|
||||
// Get the OfflineAnimeModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineAnimeModel
|
||||
val media =
|
||||
downloadManager.animeDownloadedTypes.firstOrNull { it.title == item.title }
|
||||
media?.let {
|
||||
startActivity(
|
||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||
.putExtra("media", getMedia(it))
|
||||
.putExtra("download", true)
|
||||
)
|
||||
} ?: run {
|
||||
snackString("no media found")
|
||||
}
|
||||
}
|
||||
|
||||
val total = view.findViewById<TextView>(R.id.total)
|
||||
total.text =
|
||||
if (gridView.count > 0) "Anime (${gridView.count})" else "Empty List"
|
||||
gridView.setOnItemLongClickListener { parent, view, position, id ->
|
||||
// Get the OfflineAnimeModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineAnimeModel
|
||||
val type: DownloadedType.Type =
|
||||
DownloadedType.Type.ANIME
|
||||
|
||||
// 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)
|
||||
val mediaIds = requireContext().getSharedPreferences(getString(R.string.anime_downloads), Context.MODE_PRIVATE)
|
||||
?.all?.filter { it.key.contains(item.title) }?.values ?: emptySet()
|
||||
for (mediaId in mediaIds) {
|
||||
ani.dantotsu.download.video.Helper.downloadManager(requireContext())
|
||||
.removeDownload(mediaId.toString())
|
||||
}
|
||||
getDownloads()
|
||||
adapter.setItems(downloads)
|
||||
}
|
||||
builder.setNegativeButton("No") { _, _ ->
|
||||
// Do nothing
|
||||
}
|
||||
val dialog = builder.show()
|
||||
dialog.window?.setDimAmount(0.8f)
|
||||
true
|
||||
}
|
||||
view.rootView.fitsSystemWindows = true
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onSearchQuery(query: String) {
|
||||
adapter.onSearchQuery(query)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
var height = statusBarHeight
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
|
||||
if (displayCutout != null) {
|
||||
if (displayCutout.boundingRects.size > 0) {
|
||||
height = max(
|
||||
statusBarHeight,
|
||||
min(
|
||||
displayCutout.boundingRects[0].width(),
|
||||
displayCutout.boundingRects[0].height()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val mangaRefresh = view.findViewById<FrameLayout>(R.id.mangaRefresh)
|
||||
mangaRefresh.updatePaddingRelative(bottom = navBarHeight + 160f.px)
|
||||
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
||||
val visible = false
|
||||
|
||||
fun animate() {
|
||||
val start = if (visible) 0f else 1f
|
||||
val end = if (!visible) 0f else 1f
|
||||
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
|
||||
duration = 300
|
||||
interpolator = OvershootInterpolator(2f)
|
||||
start()
|
||||
}
|
||||
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
|
||||
duration = 300
|
||||
interpolator = OvershootInterpolator(2f)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
scrollTop.setOnClickListener {
|
||||
gridView.smoothScrollToPosition(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 < -height
|
||||
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
||||
}
|
||||
})
|
||||
initActivity(requireActivity())
|
||||
|
||||
}
|
||||
|
||||
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 animeTitles = downloadManager.animeDownloadedTypes.map { it.title }.distinct()
|
||||
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
|
||||
for (title in animeTitles) {
|
||||
val _downloads = downloadManager.animeDownloadedTypes.filter { it.title == title }
|
||||
val download = _downloads.first()
|
||||
val offlineAnimeModel = loadOfflineAnimeModel(download)
|
||||
newAnimeDownloads += offlineAnimeModel
|
||||
}
|
||||
downloads = newAnimeDownloads
|
||||
}
|
||||
|
||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
||||
val type = if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} 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<SChapter> {
|
||||
SChapterImpl() // Provide an instance of SChapterImpl
|
||||
})
|
||||
.registerTypeAdapter(SAnime::class.java, InstanceCreator<SAnime> {
|
||||
SAnimeImpl() // Provide an instance of SAnimeImpl
|
||||
})
|
||||
.registerTypeAdapter(SEpisode::class.java, InstanceCreator<SEpisode> {
|
||||
SEpisodeImpl() // Provide an instance of SEpisodeImpl
|
||||
})
|
||||
.create()
|
||||
val media = File(directory, "media.json")
|
||||
val mediaJson = media.readText()
|
||||
gson.fromJson(mediaJson, Media::class.java)
|
||||
} catch (e: Exception) {
|
||||
logger("Error loading media.json: ${e.message}")
|
||||
logger(e.printStackTrace())
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
|
||||
val type = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (downloadedType.type == 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 media = File(directory, "media.json")
|
||||
val mediaJson = media.readText()
|
||||
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.nameMAL ?: mediaModel.nameRomaji
|
||||
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 readEpisode = (mediaModel.userProgress ?: "~").toString()
|
||||
val totalEpisode = "${mediaModel.anime?.totalEpisodes ?: "??"}"
|
||||
val chapters = " Chapters"
|
||||
return OfflineAnimeModel(
|
||||
title,
|
||||
score,
|
||||
totalEpisode,
|
||||
readEpisode,
|
||||
type,
|
||||
chapters,
|
||||
isOngoing,
|
||||
isUserScored,
|
||||
coverUri,
|
||||
bannerUri
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger("Error loading media.json: ${e.message}")
|
||||
logger(e.printStackTrace())
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
return OfflineAnimeModel(
|
||||
"unknown",
|
||||
"0",
|
||||
"??",
|
||||
"??",
|
||||
"movie",
|
||||
"hmm",
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OfflineAnimeSearchListener {
|
||||
fun onSearchQuery(query: String)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ani.dantotsu.download.anime
|
||||
|
||||
import android.net.Uri
|
||||
data class OfflineAnimeModel (
|
||||
val title: String,
|
||||
val score: String,
|
||||
val totalEpisode: String,
|
||||
val watchedEpisode: String,
|
||||
val type: String,
|
||||
val episodes: String,
|
||||
val isOngoing: Boolean,
|
||||
val isUserScored: Boolean,
|
||||
val image: Uri?,
|
||||
val banner: Uri?,
|
||||
)
|
|
@ -61,12 +61,12 @@ class OfflineMangaAdapter(
|
|||
val chapters = view.findViewById<TextView>(R.id.itemTotal)
|
||||
chapters.text = " Chapters"
|
||||
bannerView.setImageURI(item.banner)
|
||||
totalchapter.text = item.totalchapter
|
||||
totalchapter.text = item.totalChapter
|
||||
} else if (style == 1) {
|
||||
val readchapter =
|
||||
view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view
|
||||
readchapter.text = item.readchapter
|
||||
totalchapter.text = " | " + item.totalchapter
|
||||
readchapter.text = item.readChapter
|
||||
totalchapter.text = " | " + item.totalChapter
|
||||
}
|
||||
|
||||
// Bind item data to the views
|
||||
|
|
|
@ -18,11 +18,15 @@ import android.view.animation.LayoutAnimationController
|
|||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.AbsListView
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.GridView
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.core.view.updatePaddingRelative
|
||||
import androidx.fragment.app.Fragment
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.currActivity
|
||||
|
@ -33,6 +37,8 @@ import ani.dantotsu.initActivity
|
|||
import ani.dantotsu.logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.px
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.SettingsDialogFragment
|
||||
import ani.dantotsu.snackString
|
||||
|
@ -75,6 +81,9 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
requireContext().theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||
val color = typedValue.data
|
||||
|
||||
val animeTitleContainer = view.findViewById<LinearLayout>(R.id.animeTitleContainer)
|
||||
animeTitleContainer.updatePadding(top = statusBarHeight)
|
||||
|
||||
val animeUserAvatar = view.findViewById<ShapeableImageView>(R.id.offlineMangaUserAvatar)
|
||||
animeUserAvatar.setSafeOnClickListener {
|
||||
val dialogFragment =
|
||||
|
@ -204,6 +213,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
dialog.window?.setDimAmount(0.8f)
|
||||
true
|
||||
}
|
||||
view.rootView.fitsSystemWindows = true
|
||||
|
||||
return view
|
||||
}
|
||||
|
@ -230,8 +240,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
}
|
||||
}
|
||||
}
|
||||
val mangaRefresh = view.findViewById<FrameLayout>(R.id.mangaRefresh)
|
||||
mangaRefresh.updatePaddingRelative(bottom = navBarHeight + 160f.px)
|
||||
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
||||
var visible = false
|
||||
|
||||
fun animate() {
|
||||
val start = if (visible) 0f else 1f
|
||||
val end = if (!visible) 0f else 1f
|
||||
|
|
|
@ -5,8 +5,8 @@ import android.net.Uri
|
|||
data class OfflineMangaModel(
|
||||
val title: String,
|
||||
val score: String,
|
||||
val totalchapter: String,
|
||||
val readchapter: String,
|
||||
val totalChapter: String,
|
||||
val readChapter: String,
|
||||
val type: String,
|
||||
val chapters: String,
|
||||
val isOngoing: Boolean,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ani.dantotsu.home
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Build
|
||||
|
@ -19,7 +20,9 @@ import androidx.lifecycle.Lifecycle
|
|||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.ZoomOutPageTransformer
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.ActivityNoInternetBinding
|
||||
import ani.dantotsu.download.anime.OfflineAnimeFragment
|
||||
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||
import ani.dantotsu.initActivity
|
||||
import ani.dantotsu.loadData
|
||||
|
@ -113,12 +116,11 @@ class NoInternet : AppCompatActivity() {
|
|||
override fun getItemCount(): Int = 3
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
when (position) {
|
||||
0 -> return OfflineFragment()
|
||||
1 -> return OfflineFragment()
|
||||
2 -> return OfflineMangaFragment()
|
||||
}
|
||||
return LoginFragment()
|
||||
return when (position) {
|
||||
0 -> OfflineAnimeFragment()
|
||||
2 -> OfflineMangaFragment()
|
||||
else -> OfflineFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import android.view.GestureDetector
|
|||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.viewModels
|
||||
|
@ -77,6 +78,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
ThemeManager(this).applyTheme(MediaSingleton.bitmap)
|
||||
MediaSingleton.bitmap = null
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityMediaBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||
|
@ -85,8 +87,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
|
||||
initActivity(this)
|
||||
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||
if (!uiSettings.immersiveMode) this.window.statusBarColor =
|
||||
ContextCompat.getColor(this, R.color.nav_bg_inv)
|
||||
|
||||
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
|
||||
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
|
||||
|
@ -445,7 +445,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", screenWidth)
|
||||
.setDuration(duration).start()
|
||||
binding.mediaBanner.pause()
|
||||
if (!uiSettings.immersiveMode) this.window.statusBarColor = color
|
||||
}
|
||||
if (percentage <= percent && isCollapsed) {
|
||||
isCollapsed = false
|
||||
|
@ -458,7 +457,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f)
|
||||
.setDuration(duration).start()
|
||||
if (uiSettings.bannerAnimations) binding.mediaBanner.resume()
|
||||
if (!uiSettings.immersiveMode) this.window.statusBarColor = color
|
||||
}
|
||||
if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue(
|
||||
false
|
||||
|
|
|
@ -279,7 +279,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||
if (video != null) {
|
||||
Helper.startAnimeDownloadService(
|
||||
requireActivity(),
|
||||
media!!.userPreferredName,
|
||||
media!!.nameMAL ?: media!!.nameRomaji,
|
||||
episode.number,
|
||||
video,
|
||||
null,
|
||||
|
|
|
@ -56,7 +56,7 @@ abstract class BaseParser {
|
|||
* **/
|
||||
open suspend fun autoSearch(mediaObj: Media): ShowResponse? {
|
||||
var response: ShowResponse? = loadSavedShowResponse(mediaObj.id)
|
||||
if (response != null && this !is OfflineMangaParser) {
|
||||
if (response != null && this !is OfflineMangaParser && this !is OfflineAnimeParser) {
|
||||
saveShowResponse(mediaObj.id, response, true)
|
||||
} else {
|
||||
setUserText("Searching : ${mediaObj.mainName()}")
|
||||
|
|
|
@ -13,14 +13,15 @@ import ani.dantotsu.MainActivity
|
|||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.databinding.BottomSheetSettingsBinding
|
||||
import ani.dantotsu.download.anime.OfflineAnimeFragment
|
||||
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||
import ani.dantotsu.home.AnimeFragment
|
||||
import ani.dantotsu.home.HomeFragment
|
||||
import ani.dantotsu.home.LoginFragment
|
||||
import ani.dantotsu.home.MangaFragment
|
||||
import ani.dantotsu.home.NoInternet
|
||||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.incognitoNotification
|
||||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.offline.OfflineFragment
|
||||
import ani.dantotsu.openLinkInBrowser
|
||||
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
||||
|
@ -145,7 +146,10 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
|
|||
|
||||
PageType.ANIME -> {
|
||||
val intent = Intent(activity, NoInternet::class.java)
|
||||
intent.putExtra("FRAGMENT_CLASS_NAME", OfflineFragment::class.java.name)
|
||||
intent.putExtra(
|
||||
"FRAGMENT_CLASS_NAME",
|
||||
OfflineAnimeFragment::class.java.name
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,15 @@ import android.app.Activity
|
|||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import ani.dantotsu.R
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.color.DynamicColorsOptions
|
||||
|
||||
|
||||
class ThemeManager(private val context: Context) {
|
||||
class ThemeManager(private val context: Activity) {
|
||||
fun applyTheme(fromImage: Bitmap? = null) {
|
||||
val useOLED = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||
.getBoolean("use_oled", false) && isDarkThemeActive(context)
|
||||
|
@ -54,9 +57,24 @@ class ThemeManager(private val context: Context) {
|
|||
else -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
|
||||
}
|
||||
|
||||
val window = context.window
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
window.statusBarColor = 0x00000000
|
||||
context.setTheme(themeToApply)
|
||||
}
|
||||
|
||||
fun setWindowFlag(activity: Activity, bits: Int, on: Boolean) {
|
||||
val win: Window = activity.window
|
||||
val winParams: WindowManager.LayoutParams = win.attributes
|
||||
if (on) {
|
||||
winParams.flags = winParams.flags or bits
|
||||
} else {
|
||||
winParams.flags = winParams.flags and bits.inv()
|
||||
}
|
||||
win.setAttributes(winParams)
|
||||
}
|
||||
|
||||
private fun applyDynamicColors(
|
||||
useMaterialYou: Boolean,
|
||||
context: Context,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
tools:context=".home.MangaFragment">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/mangaRefresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
|
@ -16,6 +17,8 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<color name="nav_bg_inv">#001C1C1C</color>
|
||||
<color name="nav_tab">#40ffffff</color>
|
||||
<color name="nav_tab_disabled">#40ffffff</color>
|
||||
<color name="status">#54000000</color>
|
||||
<color name="status">#00000000</color>
|
||||
<color name="nav_status">#80000000</color>
|
||||
<color name="filler">#29FF6B08</color>
|
||||
<color name="chip">#b3aead</color>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<color name="nav_bg_inv">#00FFFFFF</color>
|
||||
<color name="nav_tab">#40000000</color>
|
||||
<color name="nav_tab_disabled">#19000000</color>
|
||||
<color name="status">#54EEEEEE</color>
|
||||
<color name="status">#00000000</color>
|
||||
<color name="button_icon">#A9FFFFFF</color>
|
||||
<color name="nav_status">#80FFFFFF</color>
|
||||
<color name="fav">#ad5edd</color>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">?android:colorBackground</item>
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="elevationOverlayEnabled">false</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
@ -27,7 +28,7 @@
|
|||
</style>
|
||||
|
||||
<style name="Theme.Dantotsu" parent="Theme.Base">
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Dantotsu.NoActionBar">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue