webview for extensions
This commit is contained in:
parent
26b6564825
commit
ff02280239
29 changed files with 447 additions and 100 deletions
|
@ -190,6 +190,24 @@
|
||||||
<data android:host="discord.dantotsu.com" />
|
<data android:host="discord.dantotsu.com" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".others.webview.CookieCatcher"
|
||||||
|
android:configChanges="orientation|screenSize|layoutDirection"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter android:label="Discord Login for Dantotsu">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="dantotsu" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="discord.dantotsu.com" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".connections.anilist.UrlMedia"
|
android:name=".connections.anilist.UrlMedia"
|
||||||
android:configChanges="orientation|screenSize|layoutDirection"
|
android:configChanges="orientation|screenSize|layoutDirection"
|
||||||
|
|
|
@ -945,14 +945,19 @@ fun checkCountry(context: Context): Boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
const val INCOGNITO_CHANNEL_ID = 26
|
const val INCOGNITO_CHANNEL_ID = 26
|
||||||
|
|
||||||
@SuppressLint("LaunchActivityFromNotification")
|
@SuppressLint("LaunchActivityFromNotification")
|
||||||
fun incognitoNotification(context: Context){
|
fun incognitoNotification(context: Context) {
|
||||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val notificationManager =
|
||||||
val incognito = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("incognito", false)
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val incognito = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("incognito", false)
|
||||||
if (incognito) {
|
if (incognito) {
|
||||||
val intent = Intent(context, NotificationClickReceiver::class.java)
|
val intent = Intent(context, NotificationClickReceiver::class.java)
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
PendingIntent.FLAG_IMMUTABLE)
|
context, 0, intent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
val builder = NotificationCompat.Builder(context, Notifications.CHANNEL_INCOGNITO_MODE)
|
val builder = NotificationCompat.Builder(context, Notifications.CHANNEL_INCOGNITO_MODE)
|
||||||
.setSmallIcon(R.drawable.ic_incognito_24)
|
.setSmallIcon(R.drawable.ic_incognito_24)
|
||||||
.setContentTitle("Incognito Mode")
|
.setContentTitle("Incognito Mode")
|
||||||
|
|
|
@ -198,24 +198,23 @@ class MainActivity : AppCompatActivity() {
|
||||||
else -> 1
|
else -> 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uiSettings.defaultStartUpTab
|
uiSettings.defaultStartUpTab
|
||||||
}
|
}
|
||||||
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val offline = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
val offline = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
.getBoolean("offlineMode", false)
|
.getBoolean("offlineMode", false)
|
||||||
if (!isOnline(this)) {
|
if (!isOnline(this)) {
|
||||||
snackString(this@MainActivity.getString(R.string.no_internet_connection))
|
snackString(this@MainActivity.getString(R.string.no_internet_connection))
|
||||||
startActivity(Intent(this, NoInternet::class.java))
|
startActivity(Intent(this, NoInternet::class.java))
|
||||||
} else {
|
} else {
|
||||||
if (offline){
|
if (offline) {
|
||||||
snackString(this@MainActivity.getString(R.string.no_internet_connection))
|
snackString(this@MainActivity.getString(R.string.no_internet_connection))
|
||||||
startActivity(Intent(this, NoInternet::class.java))
|
startActivity(Intent(this, NoInternet::class.java))
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
val model: AnilistHomeViewModel by viewModels()
|
val model: AnilistHomeViewModel by viewModels()
|
||||||
model.genres.observe(this) { it ->
|
model.genres.observe(this) { it ->
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
|
@ -226,15 +225,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding.mainProgressBar.visibility = View.GONE
|
binding.mainProgressBar.visibility = View.GONE
|
||||||
val mainViewPager = binding.viewpager
|
val mainViewPager = binding.viewpager
|
||||||
mainViewPager.isUserInputEnabled = false
|
mainViewPager.isUserInputEnabled = false
|
||||||
mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
|
mainViewPager.adapter =
|
||||||
|
ViewPagerAdapter(supportFragmentManager, lifecycle)
|
||||||
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
||||||
navbar.setOnTabSelectListener(object :
|
navbar.setOnTabSelectListener(object :
|
||||||
AnimatedBottomBar.OnTabSelectListener {
|
AnimatedBottomBar.OnTabSelectListener {
|
||||||
override fun onTabSelected(
|
override fun onTabSelected(
|
||||||
lastIndex: Int,
|
lastIndex: Int,
|
||||||
lastTab: AnimatedBottomBar.Tab?,
|
lastTab: AnimatedBottomBar.Tab?,
|
||||||
newIndex: Int,
|
newIndex: Int,
|
||||||
newTab: AnimatedBottomBar.Tab
|
newTab: AnimatedBottomBar.Tab
|
||||||
) {
|
) {
|
||||||
navbar.animate().translationZ(12f).setDuration(200).start()
|
navbar.animate().translationZ(12f).setDuration(200).start()
|
||||||
selectedOption = newIndex
|
selectedOption = newIndex
|
||||||
|
@ -242,7 +242,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
navbar.selectTabAt(selectedOption)
|
navbar.selectTabAt(selectedOption)
|
||||||
mainViewPager.post { mainViewPager.setCurrentItem(selectedOption, false) }
|
mainViewPager.post {
|
||||||
|
mainViewPager.setCurrentItem(
|
||||||
|
selectedOption,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.mainProgressBar.visibility = View.GONE
|
binding.mainProgressBar.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
@ -262,8 +267,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
media.cameFromContinue = cont
|
media.cameFromContinue = cont
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(this@MainActivity, MediaDetailsActivity::class.java)
|
Intent(this@MainActivity, MediaDetailsActivity::class.java)
|
||||||
.putExtra("media", media as Serializable)
|
.putExtra("media", media as Serializable)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
snackString(this@MainActivity.getString(R.string.anilist_not_found))
|
snackString(this@MainActivity.getString(R.string.anilist_not_found))
|
||||||
|
@ -282,8 +287,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
val md = "Open settings & click +Add Links & select Anilist & Mal urls"
|
val md = "Open settings & click +Add Links & select Anilist & Mal urls"
|
||||||
addView(TextView(this@MainActivity).apply {
|
addView(TextView(this@MainActivity).apply {
|
||||||
val markWon =
|
val markWon =
|
||||||
Markwon.builder(this@MainActivity)
|
Markwon.builder(this@MainActivity)
|
||||||
.usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
|
.usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
|
||||||
markWon.setMarkdown(this, md)
|
markWon.setMarkdown(this, md)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -296,8 +301,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
saveData("allow_opening_links", true, this@MainActivity)
|
saveData("allow_opening_links", true, this@MainActivity)
|
||||||
tryWith(true) {
|
tryWith(true) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
|
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
|
||||||
.setData(Uri.parse("package:$packageName"))
|
.setData(Uri.parse("package:$packageName"))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
|
|
@ -43,7 +43,8 @@ class AnimeNameAdapter {
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
return if (removedNumber.equals(text, true)) {
|
return if (removedNumber.equals(text, true)) {
|
||||||
val failedEpisodeNumberPattern: Regex = Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE)
|
val failedEpisodeNumberPattern: Regex =
|
||||||
|
Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE)
|
||||||
failedEpisodeNumberPattern.replace(removedNumber) { mr ->
|
failedEpisodeNumberPattern.replace(removedNumber) { mr ->
|
||||||
mr.value.replaceFirst(mr.groupValues[1], "")
|
mr.value.replaceFirst(mr.groupValues[1], "")
|
||||||
}.ifEmpty { removedNumber }
|
}.ifEmpty { removedNumber }
|
||||||
|
@ -58,7 +59,8 @@ class AnimeNameAdapter {
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
return if (removedNumber.equals(text, true)) {
|
return if (removedNumber.equals(text, true)) {
|
||||||
val failedEpisodeNumberPattern: Regex = Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE)
|
val failedEpisodeNumberPattern: Regex =
|
||||||
|
Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE)
|
||||||
failedEpisodeNumberPattern.replace(removedNumber) { mr ->
|
failedEpisodeNumberPattern.replace(removedNumber) { mr ->
|
||||||
mr.value.replaceFirst(mr.groupValues[1], "")
|
mr.value.replaceFirst(mr.groupValues[1], "")
|
||||||
}.ifEmpty { removedNumber }
|
}.ifEmpty { removedNumber }
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
|
@ -22,12 +23,14 @@ import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
import ani.dantotsu.others.LanguageMapper
|
import ani.dantotsu.others.LanguageMapper
|
||||||
|
import ani.dantotsu.others.webview.CookieCatcher
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.DynamicAnimeParser
|
import ani.dantotsu.parsers.DynamicAnimeParser
|
||||||
import ani.dantotsu.parsers.WatchSources
|
import ani.dantotsu.parsers.WatchSources
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -86,8 +89,12 @@ class AnimeWatchAdapter(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences(
|
||||||
?.getBoolean("offlineMode", false) == true) View.GONE else View.VISIBLE
|
"Dantotsu",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
?.getBoolean("offlineMode", false) == true
|
||||||
|
) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
binding.animeSourceNameContainer.visibility = offline
|
binding.animeSourceNameContainer.visibility = offline
|
||||||
binding.animeSourceSettings.visibility = offline
|
binding.animeSourceSettings.visibility = offline
|
||||||
|
@ -188,7 +195,7 @@ class AnimeWatchAdapter(
|
||||||
val dialogView =
|
val dialogView =
|
||||||
LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
|
LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
|
||||||
val dialogBinding = DialogLayoutBinding.bind(dialogView)
|
val dialogBinding = DialogLayoutBinding.bind(dialogView)
|
||||||
|
var refresh = false
|
||||||
var run = false
|
var run = false
|
||||||
var reversed = media.selected!!.recyclerReversed
|
var reversed = media.selected!!.recyclerReversed
|
||||||
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
|
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
|
||||||
|
@ -237,6 +244,21 @@ class AnimeWatchAdapter(
|
||||||
dialogBinding.layoutText.text = "Compact"
|
dialogBinding.layoutText.text = "Compact"
|
||||||
run = true
|
run = true
|
||||||
}
|
}
|
||||||
|
dialogBinding.animeWebviewContainer.setOnClickListener {
|
||||||
|
//start CookieCatcher activity
|
||||||
|
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
||||||
|
val sourceAHH = watchSources[source] as? DynamicAnimeParser
|
||||||
|
val sourceHttp =
|
||||||
|
sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource
|
||||||
|
val url = sourceHttp?.baseUrl
|
||||||
|
url?.let {
|
||||||
|
refresh = true
|
||||||
|
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
|
||||||
|
.putExtra("url", url)
|
||||||
|
startActivity(fragment.requireContext(), intent, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//hidden
|
//hidden
|
||||||
dialogBinding.animeScanlatorContainer.visibility = View.GONE
|
dialogBinding.animeScanlatorContainer.visibility = View.GONE
|
||||||
|
@ -247,8 +269,13 @@ class AnimeWatchAdapter(
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton("OK") { _, _ ->
|
.setPositiveButton("OK") { _, _ ->
|
||||||
if (run) fragment.onIconPressed(style, reversed)
|
if (run) fragment.onIconPressed(style, reversed)
|
||||||
|
if (refresh) fragment.loadEpisodes(source, true)
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { _, _ ->
|
.setNegativeButton("Cancel") { _, _ ->
|
||||||
|
if (refresh) fragment.loadEpisodes(source, true)
|
||||||
|
}
|
||||||
|
.setOnCancelListener {
|
||||||
|
if (refresh) fragment.loadEpisodes(source, true)
|
||||||
}
|
}
|
||||||
.create()
|
.create()
|
||||||
nestedDialog?.show()
|
nestedDialog?.show()
|
||||||
|
@ -410,7 +437,8 @@ class AnimeWatchAdapter(
|
||||||
)
|
)
|
||||||
val items = adapter.count
|
val items = adapter.count
|
||||||
|
|
||||||
binding?.animeSourceLanguageContainer?.visibility = if (items > 1) View.VISIBLE else View.GONE
|
binding?.animeSourceLanguageContainer?.visibility =
|
||||||
|
if (items > 1) View.VISIBLE else View.GONE
|
||||||
binding?.animeSourceLanguage?.setAdapter(adapter)
|
binding?.animeSourceLanguage?.setAdapter(adapter)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import androidx.annotation.OptIn
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.offline.Download
|
|
||||||
import androidx.media3.exoplayer.offline.DownloadIndex
|
import androidx.media3.exoplayer.offline.DownloadIndex
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
|
@ -63,6 +62,7 @@ class EpisodeAdapter(
|
||||||
index = Helper.downloadManager(fragment.requireContext()).downloadIndex
|
index = Helper.downloadManager(fragment.requireContext()).downloadIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return (when (viewType) {
|
return (when (viewType) {
|
||||||
0 -> EpisodeListViewHolder(
|
0 -> EpisodeListViewHolder(
|
||||||
|
@ -248,7 +248,10 @@ class EpisodeAdapter(
|
||||||
// Find the position of the chapter and notify only that item
|
// Find the position of the chapter and notify only that item
|
||||||
val position = arr.indexOfFirst { it.number == episodeNumber }
|
val position = arr.indexOfFirst { it.number == episodeNumber }
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), episodeNumber)
|
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(
|
||||||
|
media.mainName(),
|
||||||
|
episodeNumber
|
||||||
|
)
|
||||||
val id = fragment.requireContext().getSharedPreferences(
|
val id = fragment.requireContext().getSharedPreferences(
|
||||||
ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads),
|
ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads),
|
||||||
Context.MODE_PRIVATE
|
Context.MODE_PRIVATE
|
||||||
|
@ -323,6 +326,7 @@ class EpisodeAdapter(
|
||||||
inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) :
|
inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
private val activeCoroutines = mutableSetOf<String>()
|
private val activeCoroutines = mutableSetOf<String>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ani.dantotsu.media.manga
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -23,12 +24,14 @@ import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
import ani.dantotsu.media.anime.handleProgress
|
import ani.dantotsu.media.anime.handleProgress
|
||||||
import ani.dantotsu.others.LanguageMapper
|
import ani.dantotsu.others.LanguageMapper
|
||||||
|
import ani.dantotsu.others.webview.CookieCatcher
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -65,8 +68,12 @@ class MangaReadAdapter(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences(
|
||||||
?.getBoolean("offlineMode", false) == true) View.GONE else View.VISIBLE
|
"Dantotsu",
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
?.getBoolean("offlineMode", false) == true
|
||||||
|
) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
binding.animeSourceNameContainer.visibility = offline
|
binding.animeSourceNameContainer.visibility = offline
|
||||||
binding.animeSourceSettings.visibility = offline
|
binding.animeSourceSettings.visibility = offline
|
||||||
|
@ -149,9 +156,10 @@ class MangaReadAdapter(
|
||||||
|
|
||||||
binding.animeNestedButton.setOnClickListener {
|
binding.animeNestedButton.setOnClickListener {
|
||||||
|
|
||||||
val dialogView = LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
|
val dialogView =
|
||||||
|
LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null)
|
||||||
val dialogBinding = DialogLayoutBinding.bind(dialogView)
|
val dialogBinding = DialogLayoutBinding.bind(dialogView)
|
||||||
|
var refresh = false
|
||||||
var run = false
|
var run = false
|
||||||
var reversed = media.selected!!.recyclerReversed
|
var reversed = media.selected!!.recyclerReversed
|
||||||
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
|
var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView
|
||||||
|
@ -194,6 +202,20 @@ class MangaReadAdapter(
|
||||||
dialogBinding.layoutText.text = "Compact"
|
dialogBinding.layoutText.text = "Compact"
|
||||||
run = true
|
run = true
|
||||||
}
|
}
|
||||||
|
dialogBinding.animeWebviewContainer.setOnClickListener {
|
||||||
|
//start CookieCatcher activity
|
||||||
|
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
||||||
|
val sourceAHH = mangaReadSources[source] as? DynamicMangaParser
|
||||||
|
val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource
|
||||||
|
val url = sourceHttp?.baseUrl
|
||||||
|
url?.let {
|
||||||
|
refresh = true
|
||||||
|
val intent = Intent(fragment.requireContext(), CookieCatcher::class.java)
|
||||||
|
.putExtra("url", url)
|
||||||
|
ContextCompat.startActivity(fragment.requireContext(), intent, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Multi download
|
//Multi download
|
||||||
dialogBinding.downloadNo.text = "0"
|
dialogBinding.downloadNo.text = "0"
|
||||||
|
@ -216,7 +238,8 @@ class MangaReadAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
//Scanlator
|
//Scanlator
|
||||||
dialogBinding.animeScanlatorContainer.visibility = if (options.count() > 1) View.VISIBLE else View.GONE
|
dialogBinding.animeScanlatorContainer.visibility =
|
||||||
|
if (options.count() > 1) View.VISIBLE else View.GONE
|
||||||
dialogBinding.scanlatorNo.text = "${options.count()}"
|
dialogBinding.scanlatorNo.text = "${options.count()}"
|
||||||
dialogBinding.animeScanlatorTop.setOnClickListener {
|
dialogBinding.animeScanlatorTop.setOnClickListener {
|
||||||
val dialogView2 =
|
val dialogView2 =
|
||||||
|
@ -267,8 +290,13 @@ class MangaReadAdapter(
|
||||||
if (dialogBinding.downloadNo.text != "0") {
|
if (dialogBinding.downloadNo.text != "0") {
|
||||||
fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt())
|
fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt())
|
||||||
}
|
}
|
||||||
|
if (refresh) fragment.loadChapters(source, true)
|
||||||
}
|
}
|
||||||
.setNegativeButton("Cancel") { _, _ ->
|
.setNegativeButton("Cancel") { _, _ ->
|
||||||
|
if (refresh) fragment.loadChapters(source, true)
|
||||||
|
}
|
||||||
|
.setOnCancelListener {
|
||||||
|
if (refresh) fragment.loadChapters(source, true)
|
||||||
}
|
}
|
||||||
.create()
|
.create()
|
||||||
nestedDialog?.show()
|
nestedDialog?.show()
|
||||||
|
@ -437,7 +465,8 @@ class MangaReadAdapter(
|
||||||
parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) }
|
parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) }
|
||||||
)
|
)
|
||||||
val items = adapter.count
|
val items = adapter.count
|
||||||
binding?.animeSourceLanguageContainer?.visibility = if (items > 1) View.VISIBLE else View.GONE
|
binding?.animeSourceLanguageContainer?.visibility =
|
||||||
|
if (items > 1) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
binding?.animeSourceLanguage?.setAdapter(adapter)
|
binding?.animeSourceLanguage?.setAdapter(adapter)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ani.dantotsu.others.webview
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class CookieCatcher : AppCompatActivity() {
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
|
//get url from intent
|
||||||
|
val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
val process = Application.getProcessName()
|
||||||
|
if (packageName != process) WebView.setDataDirectorySuffix(process)
|
||||||
|
}
|
||||||
|
setContentView(R.layout.activity_discord)
|
||||||
|
|
||||||
|
val webView = findViewById<WebView>(R.id.discordWebview)
|
||||||
|
|
||||||
|
val cookies: CookieManager = Injekt.get<NetworkHelper>().cookieJar.manager
|
||||||
|
cookies.setAcceptThirdPartyCookies(webView, true)
|
||||||
|
|
||||||
|
webView.apply {
|
||||||
|
settings.javaScriptEnabled = true
|
||||||
|
settings.databaseEnabled = true
|
||||||
|
settings.domStorageEnabled = true
|
||||||
|
}
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
|
webView.webViewClient = object : WebViewClient() {
|
||||||
|
override fun shouldOverrideUrlLoading(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?
|
||||||
|
): Boolean {
|
||||||
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webView.loadUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,6 +11,9 @@ import ani.dantotsu.BottomSheetDialogFragment
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.databinding.BottomSheetWebviewBinding
|
import ani.dantotsu.databinding.BottomSheetWebviewBinding
|
||||||
import ani.dantotsu.defaultHeaders
|
import ani.dantotsu.defaultHeaders
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
abstract class WebViewBottomDialog : BottomSheetDialogFragment() {
|
abstract class WebViewBottomDialog : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
@ -30,7 +33,8 @@ abstract class WebViewBottomDialog : BottomSheetDialogFragment() {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
val cookies: CookieManager = CookieManager.getInstance()
|
val cookies: CookieManager = Injekt.get<NetworkHelper>().cookieJar.manager
|
||||||
|
//CookieManager.getInstance()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
|
@ -11,15 +11,23 @@ class SourcePreferences(
|
||||||
|
|
||||||
// Common options
|
// Common options
|
||||||
|
|
||||||
fun sourceDisplayMode() = preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
fun sourceDisplayMode() = preferenceStore.getObject(
|
||||||
|
"pref_display_mode_catalogue",
|
||||||
|
LibraryDisplayMode.default,
|
||||||
|
LibraryDisplayMode.Serializer::serialize,
|
||||||
|
LibraryDisplayMode.Serializer::deserialize
|
||||||
|
)
|
||||||
|
|
||||||
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
fun enabledLanguages() =
|
||||||
|
preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
||||||
|
|
||||||
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||||
|
|
||||||
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
fun migrationSortingMode() =
|
||||||
|
preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
|
|
||||||
fun migrationSortingDirection() = preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
fun migrationSortingDirection() =
|
||||||
|
preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
||||||
|
|
||||||
|
@ -37,12 +45,17 @@ class SourcePreferences(
|
||||||
fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
||||||
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false)
|
fun searchPinnedAnimeSourcesOnly() =
|
||||||
fun searchPinnedMangaSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
preferenceStore.getBoolean("search_pinned_anime_sources_only", false)
|
||||||
|
|
||||||
fun hideInAnimeLibraryItems() = preferenceStore.getBoolean("browse_hide_in_anime_library_items", false)
|
fun searchPinnedMangaSourcesOnly() =
|
||||||
|
preferenceStore.getBoolean("search_pinned_sources_only", false)
|
||||||
|
|
||||||
fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
fun hideInAnimeLibraryItems() =
|
||||||
|
preferenceStore.getBoolean("browse_hide_in_anime_library_items", false)
|
||||||
|
|
||||||
|
fun hideInMangaLibraryItems() =
|
||||||
|
preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
|
@ -62,7 +75,8 @@ class SourcePreferences(
|
||||||
|
|
||||||
fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80)
|
fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80)
|
||||||
|
|
||||||
fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean("data_saver_image_format_jpeg", false)
|
fun dataSaverImageFormatJpeg() =
|
||||||
|
preferenceStore.getBoolean("data_saver_image_format_jpeg", false)
|
||||||
|
|
||||||
fun dataSaverServer() = preferenceStore.getString("data_saver_server", "")
|
fun dataSaverServer() = preferenceStore.getString("data_saver_server", "")
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,15 @@ package eu.kanade.tachiyomi.animesource.model
|
||||||
sealed class AnimeFilter<T>(val name: String, var state: T) {
|
sealed class AnimeFilter<T>(val name: String, var state: T) {
|
||||||
open class Header(name: String) : AnimeFilter<Any>(name, 0)
|
open class Header(name: String) : AnimeFilter<Any>(name, 0)
|
||||||
open class Separator(name: String = "") : AnimeFilter<Any>(name, 0)
|
open class Separator(name: String = "") : AnimeFilter<Any>(name, 0)
|
||||||
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : AnimeFilter<Int>(name, state)
|
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) :
|
||||||
|
AnimeFilter<Int>(name, state)
|
||||||
|
|
||||||
abstract class Text(name: String, state: String = "") : AnimeFilter<String>(name, state)
|
abstract class Text(name: String, state: String = "") : AnimeFilter<String>(name, state)
|
||||||
abstract class CheckBox(name: String, state: Boolean = false) : AnimeFilter<Boolean>(name, state)
|
abstract class CheckBox(name: String, state: Boolean = false) :
|
||||||
abstract class TriState(name: String, state: Int = STATE_IGNORE) : AnimeFilter<Int>(name, state) {
|
AnimeFilter<Boolean>(name, state)
|
||||||
|
|
||||||
|
abstract class TriState(name: String, state: Int = STATE_IGNORE) :
|
||||||
|
AnimeFilter<Int>(name, state) {
|
||||||
fun isIgnored() = state == STATE_IGNORE
|
fun isIgnored() = state == STATE_IGNORE
|
||||||
fun isIncluded() = state == STATE_INCLUDE
|
fun isIncluded() = state == STATE_INCLUDE
|
||||||
fun isExcluded() = state == STATE_EXCLUDE
|
fun isExcluded() = state == STATE_EXCLUDE
|
||||||
|
|
|
@ -50,7 +50,8 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
override val id by lazy {
|
override val id by lazy {
|
||||||
val key = "${name.lowercase()}/$lang/$versionId"
|
val key = "${name.lowercase()}/$lang/$versionId"
|
||||||
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
||||||
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
|
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
|
||||||
|
.reduce(Long::or) and Long.MAX_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +113,11 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
|
override fun fetchSearchAnime(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: AnimeFilterList
|
||||||
|
): Observable<AnimesPage> {
|
||||||
return Observable.defer {
|
return Observable.defer {
|
||||||
try {
|
try {
|
||||||
client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess()
|
client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess()
|
||||||
|
@ -134,7 +139,11 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request
|
protected abstract fun searchAnimeRequest(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: AnimeFilterList
|
||||||
|
): Request
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [AnimesPage] object.
|
* Parses the response from the site and returns a [AnimesPage] object.
|
||||||
|
@ -311,7 +320,12 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
||||||
val animeDownloadClient = client.newBuilder()
|
val animeDownloadClient = client.newBuilder()
|
||||||
.callTimeout(30, TimeUnit.MINUTES)
|
.callTimeout(30, TimeUnit.MINUTES)
|
||||||
.build()
|
.build()
|
||||||
return animeDownloadClient.newCachelessCallWithProgress(videoRequest(video, video.totalBytesDownloaded), video)
|
return animeDownloadClient.newCachelessCallWithProgress(
|
||||||
|
videoRequest(
|
||||||
|
video,
|
||||||
|
video.totalBytesDownloaded
|
||||||
|
), video
|
||||||
|
)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,10 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
||||||
|
|
||||||
private val packageActionReceiver = object : BroadcastReceiver() {
|
private val packageActionReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) {
|
when (intent.getIntExtra(
|
||||||
|
PackageInstaller.EXTRA_STATUS,
|
||||||
|
PackageInstaller.STATUS_FAILURE
|
||||||
|
)) {
|
||||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
val userAction = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
|
val userAction = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
|
||||||
if (userAction == null) {
|
if (userAction == null) {
|
||||||
|
@ -34,9 +37,11 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
||||||
userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
service.startActivity(userAction)
|
service.startActivity(userAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||||
continueQueue(InstallStep.Idle)
|
continueQueue(InstallStep.Idle)
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed)
|
PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed)
|
||||||
else -> continueQueue(InstallStep.Error)
|
else -> continueQueue(InstallStep.Error)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +57,8 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
||||||
super.processEntry(entry)
|
super.processEntry(entry)
|
||||||
activeSession = null
|
activeSession = null
|
||||||
try {
|
try {
|
||||||
val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
val installParams =
|
||||||
|
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +66,8 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
||||||
val fileSize = service.getUriSize(entry.uri) ?: throw IllegalStateException()
|
val fileSize = service.getUriSize(entry.uri) ?: throw IllegalStateException()
|
||||||
installParams.setSize(fileSize)
|
installParams.setSize(fileSize)
|
||||||
|
|
||||||
val inputStream = service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException()
|
val inputStream =
|
||||||
|
service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException()
|
||||||
val session = packageInstaller.openSession(activeSession!!.second)
|
val session = packageInstaller.openSession(activeSession!!.second)
|
||||||
val outputStream = session.openWrite(entry.downloadId.toString(), 0, fileSize)
|
val outputStream = session.openWrite(entry.downloadId.toString(), 0, fileSize)
|
||||||
session.use {
|
session.use {
|
||||||
|
@ -82,7 +89,10 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
||||||
session.commit(intentSender)
|
session.commit(intentSender)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
|
logcat(
|
||||||
|
LogPriority.ERROR,
|
||||||
|
e
|
||||||
|
) { "Failed to install extension ${entry.downloadId} ${entry.uri}" }
|
||||||
logcat(LogPriority.ERROR) { "Exception: $e" }
|
logcat(LogPriority.ERROR) { "Exception: $e" }
|
||||||
snackString("Failed to install extension ${entry.downloadId} ${entry.uri}")
|
snackString("Failed to install extension ${entry.downloadId} ${entry.uri}")
|
||||||
activeSession?.let { (_, sessionId) ->
|
activeSession?.let { (_, sessionId) ->
|
||||||
|
@ -108,7 +118,12 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_EXPORTED)
|
ContextCompat.registerReceiver(
|
||||||
|
service,
|
||||||
|
packageActionReceiver,
|
||||||
|
IntentFilter(INSTALL_ACTION),
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ import android.content.pm.ServiceInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime
|
import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime
|
||||||
import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAnime
|
import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAnime
|
||||||
|
@ -32,8 +32,12 @@ class AnimeExtensionInstallService : Service() {
|
||||||
setProgress(100, 100, true)
|
setProgress(100, 100, true)
|
||||||
}.build()
|
}.build()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
startForeground(
|
||||||
}else{
|
Notifications.ID_EXTENSION_INSTALLER,
|
||||||
|
notification,
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
)
|
||||||
|
} else {
|
||||||
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification)
|
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +55,10 @@ class AnimeExtensionInstallService : Service() {
|
||||||
|
|
||||||
if (installer == null) {
|
if (installer == null) {
|
||||||
installer = when (installerUsed) {
|
installer = when (installerUsed) {
|
||||||
BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime(this)
|
BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime(
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" }
|
logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" }
|
||||||
stopSelf()
|
stopSelf()
|
||||||
|
|
|
@ -41,15 +41,18 @@ internal object AnimeExtensionLoader {
|
||||||
const val LIB_VERSION_MIN = 12
|
const val LIB_VERSION_MIN = 12
|
||||||
const val LIB_VERSION_MAX = 15
|
const val LIB_VERSION_MAX = 15
|
||||||
|
|
||||||
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
private const val PACKAGE_FLAGS =
|
||||||
|
PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
||||||
|
|
||||||
// jmir1's key
|
// jmir1's key
|
||||||
private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c"
|
private const val officialSignature =
|
||||||
|
"50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of the trusted signatures.
|
* List of the trusted signatures.
|
||||||
*/
|
*/
|
||||||
var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
|
var trustedSignatures =
|
||||||
|
mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of all the installed extensions initialized concurrently.
|
* Return a list of all the installed extensions initialized concurrently.
|
||||||
|
@ -105,7 +108,11 @@ internal object AnimeExtensionLoader {
|
||||||
* @param pkgName The package name of the extension to load.
|
* @param pkgName The package name of the extension to load.
|
||||||
* @param pkgInfo The package info of the extension.
|
* @param pkgInfo The package info of the extension.
|
||||||
*/
|
*/
|
||||||
private fun loadExtension(context: Context, pkgName: String, pkgInfo: PackageInfo): AnimeLoadResult {
|
private fun loadExtension(
|
||||||
|
context: Context,
|
||||||
|
pkgName: String,
|
||||||
|
pkgInfo: PackageInfo
|
||||||
|
): AnimeLoadResult {
|
||||||
val pkgManager = context.packageManager
|
val pkgManager = context.packageManager
|
||||||
|
|
||||||
val appInfo = try {
|
val appInfo = try {
|
||||||
|
@ -141,7 +148,14 @@ internal object AnimeExtensionLoader {
|
||||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||||
return AnimeLoadResult.Error
|
return AnimeLoadResult.Error
|
||||||
} else if (signatureHash !in trustedSignatures) {
|
} else if (signatureHash !in trustedSignatures) {
|
||||||
val extension = AnimeExtension.Untrusted(extName, pkgName, versionName, versionCode, libVersion, signatureHash)
|
val extension = AnimeExtension.Untrusted(
|
||||||
|
extName,
|
||||||
|
pkgName,
|
||||||
|
versionName,
|
||||||
|
versionCode,
|
||||||
|
libVersion,
|
||||||
|
signatureHash
|
||||||
|
)
|
||||||
logcat(LogPriority.WARN, message = { "Extension $pkgName isn't trusted" })
|
logcat(LogPriority.WARN, message = { "Extension $pkgName isn't trusted" })
|
||||||
return AnimeLoadResult.Untrusted(extension)
|
return AnimeLoadResult.Untrusted(extension)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver
|
||||||
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller
|
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller
|
||||||
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader
|
import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader
|
||||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
@ -63,9 +62,11 @@ class MangaExtensionManager(
|
||||||
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
|
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
|
||||||
|
|
||||||
fun getAppIconForSource(sourceId: Long): Drawable? {
|
fun getAppIconForSource(sourceId: Long): Drawable? {
|
||||||
val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName
|
val pkgName =
|
||||||
|
_installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName
|
||||||
if (pkgName != null) {
|
if (pkgName != null) {
|
||||||
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) }
|
return iconMap[pkgName]
|
||||||
|
?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -257,14 +258,20 @@ class MangaExtensionManager(
|
||||||
MangaExtensionLoader.trustedSignatures += signature
|
MangaExtensionLoader.trustedSignatures += signature
|
||||||
preferences.trustedSignatures() += signature
|
preferences.trustedSignatures() += signature
|
||||||
|
|
||||||
val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
|
val nowTrustedExtensions =
|
||||||
|
_untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
|
||||||
_untrustedExtensionsFlow.value -= nowTrustedExtensions
|
_untrustedExtensionsFlow.value -= nowTrustedExtensions
|
||||||
|
|
||||||
val ctx = context
|
val ctx = context
|
||||||
launchNow {
|
launchNow {
|
||||||
nowTrustedExtensions
|
nowTrustedExtensions
|
||||||
.map { extension ->
|
.map { extension ->
|
||||||
async { MangaExtensionLoader.loadMangaExtensionFromPkgName(ctx, extension.pkgName) }
|
async {
|
||||||
|
MangaExtensionLoader.loadMangaExtensionFromPkgName(
|
||||||
|
ctx,
|
||||||
|
extension.pkgName
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map { it.await() }
|
.map { it.await() }
|
||||||
.forEach { result ->
|
.forEach { result ->
|
||||||
|
@ -354,13 +361,15 @@ class MangaExtensionManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MangaExtension.Installed.updateExists(availableExtension: MangaExtension.Available? = null): Boolean {
|
private fun MangaExtension.Installed.updateExists(availableExtension: MangaExtension.Available? = null): Boolean {
|
||||||
val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
val availableExt =
|
||||||
|
availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||||
if (isUnofficial || availableExt == null) return false
|
if (isUnofficial || availableExt == null) return false
|
||||||
|
|
||||||
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePendingUpdatesCount() {
|
private fun updatePendingUpdatesCount() {
|
||||||
preferences.mangaExtensionUpdatesCount().set(_installedExtensionsFlow.value.count { it.hasUpdate })
|
preferences.mangaExtensionUpdatesCount()
|
||||||
|
.set(_installedExtensionsFlow.value.count { it.hasUpdate })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,11 @@ internal class MangaExtensionInstaller(private val context: Context) {
|
||||||
val request = DownloadManager.Request(downloadUri)
|
val request = DownloadManager.Request(downloadUri)
|
||||||
.setTitle(extension.name)
|
.setTitle(extension.name)
|
||||||
.setMimeType(APK_MIME)
|
.setMimeType(APK_MIME)
|
||||||
.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment)
|
.setDestinationInExternalFilesDir(
|
||||||
|
context,
|
||||||
|
Environment.DIRECTORY_DOWNLOADS,
|
||||||
|
downloadUri.lastPathSegment
|
||||||
|
)
|
||||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
|
||||||
val id = downloadManager.enqueue(request)
|
val id = downloadManager.enqueue(request)
|
||||||
|
@ -141,6 +145,7 @@ internal class MangaExtensionInstaller(private val context: Context) {
|
||||||
|
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val intent =
|
val intent =
|
||||||
MangaExtensionInstallService.getIntent(context, downloadId, uri, installer)
|
MangaExtensionInstallService.getIntent(context, downloadId, uri, installer)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import okhttp3.HttpUrl
|
||||||
|
|
||||||
class AndroidCookieJar : CookieJar {
|
class AndroidCookieJar : CookieJar {
|
||||||
|
|
||||||
private val manager = CookieManager.getInstance()
|
val manager = CookieManager.getInstance()
|
||||||
|
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
val urlString = url.toString()
|
val urlString = url.toString()
|
||||||
|
|
|
@ -17,6 +17,9 @@ class NetworkPreferences(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defaultUserAgent(): Preference<String> {
|
fun defaultUserAgent(): Preference<String> {
|
||||||
return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0")
|
return preferenceStore.getString(
|
||||||
|
"default_user_agent",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,11 @@ class CloudflareInterceptor(
|
||||||
return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
|
return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
|
override fun intercept(
|
||||||
|
chain: Interceptor.Chain,
|
||||||
|
request: Request,
|
||||||
|
response: Response
|
||||||
|
): Response {
|
||||||
try {
|
try {
|
||||||
response.close()
|
response.close()
|
||||||
cookieManager.remove(request.url, COOKIE_NAMES, 0)
|
cookieManager.remove(request.url, COOKIE_NAMES, 0)
|
||||||
|
@ -125,7 +129,10 @@ class CloudflareInterceptor(
|
||||||
if (!cloudflareBypassed) {
|
if (!cloudflareBypassed) {
|
||||||
// Prompt user to update WebView if it seems too outdated
|
// Prompt user to update WebView if it seems too outdated
|
||||||
if (isWebViewOutdated) {
|
if (isWebViewOutdated) {
|
||||||
context.toast("Please update the webview app for better compatibility", Toast.LENGTH_LONG)
|
context.toast(
|
||||||
|
"Please update the webview app for better compatibility",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw CloudflareBypassException()
|
throw CloudflareBypassException()
|
||||||
|
|
|
@ -50,7 +50,8 @@ abstract class HttpSource : CatalogueSource {
|
||||||
override val id by lazy {
|
override val id by lazy {
|
||||||
val key = "${name.lowercase()}/$lang/$versionId"
|
val key = "${name.lowercase()}/$lang/$versionId"
|
||||||
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
||||||
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
|
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
|
||||||
|
.reduce(Long::or) and Long.MAX_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +113,11 @@ abstract class HttpSource : CatalogueSource {
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
override fun fetchSearchManga(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: FilterList
|
||||||
|
): Observable<MangasPage> {
|
||||||
return Observable.defer {
|
return Observable.defer {
|
||||||
try {
|
try {
|
||||||
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
|
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
|
||||||
|
@ -134,7 +139,11 @@ abstract class HttpSource : CatalogueSource {
|
||||||
* @param query the search query.
|
* @param query the search query.
|
||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
|
protected abstract fun searchMangaRequest(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: FilterList
|
||||||
|
): Request
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
|
|
|
@ -16,13 +16,21 @@ import androidx.core.content.getSystemService
|
||||||
val Context.notificationManager: NotificationManager
|
val Context.notificationManager: NotificationManager
|
||||||
get() = getSystemService()!!
|
get() = getSystemService()!!
|
||||||
|
|
||||||
fun Context.notify(id: Int, channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null) {
|
fun Context.notify(
|
||||||
|
id: Int,
|
||||||
|
channelId: String,
|
||||||
|
block: (NotificationCompat.Builder.() -> Unit)? = null
|
||||||
|
) {
|
||||||
val notification = notificationBuilder(channelId, block).build()
|
val notification = notificationBuilder(channelId, block).build()
|
||||||
this.notify(id, notification)
|
this.notify(id, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.notify(id: Int, notification: Notification) {
|
fun Context.notify(id: Int, notification: Notification) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PermissionChecker.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +38,11 @@ fun Context.notify(id: Int, notification: Notification) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.notify(notificationWithIdAndTags: List<NotificationWithIdAndTag>) {
|
fun Context.notify(notificationWithIdAndTags: List<NotificationWithIdAndTag>) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PermissionChecker.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +60,10 @@ fun Context.cancelNotification(id: Int) {
|
||||||
* @param block the function that will execute inside the builder.
|
* @param block the function that will execute inside the builder.
|
||||||
* @return a notification to be displayed or updated.
|
* @return a notification to be displayed or updated.
|
||||||
*/
|
*/
|
||||||
fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder {
|
fun Context.notificationBuilder(
|
||||||
|
channelId: String,
|
||||||
|
block: (NotificationCompat.Builder.() -> Unit)? = null
|
||||||
|
): NotificationCompat.Builder {
|
||||||
val builder = NotificationCompat.Builder(this, channelId)
|
val builder = NotificationCompat.Builder(this, channelId)
|
||||||
.setColor(getColor(android.R.color.holo_blue_dark))
|
.setColor(getColor(android.R.color.holo_blue_dark))
|
||||||
if (block != null) {
|
if (block != null) {
|
||||||
|
|
|
@ -23,4 +23,5 @@ interface Preference<T> {
|
||||||
fun stateIn(scope: CoroutineScope): StateFlow<T>
|
fun stateIn(scope: CoroutineScope): StateFlow<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T, R : T> Preference<T>.getAndSet(crossinline block: (T) -> R) = set(block(get()))
|
inline fun <reified T, R : T> Preference<T>.getAndSet(crossinline block: (T) -> R) =
|
||||||
|
set(block(get()))
|
||||||
|
|
|
@ -52,9 +52,11 @@ fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job =
|
||||||
fun CoroutineScope.launchNonCancellable(block: suspend CoroutineScope.() -> Unit): Job =
|
fun CoroutineScope.launchNonCancellable(block: suspend CoroutineScope.() -> Unit): Job =
|
||||||
launchIO { withContext(NonCancellable, block) }
|
launchIO { withContext(NonCancellable, block) }
|
||||||
|
|
||||||
suspend fun <T> withUIContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block)
|
suspend fun <T> withUIContext(block: suspend CoroutineScope.() -> T) =
|
||||||
|
withContext(Dispatchers.Main, block)
|
||||||
|
|
||||||
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block)
|
suspend fun <T> withIOContext(block: suspend CoroutineScope.() -> T) =
|
||||||
|
withContext(Dispatchers.IO, block)
|
||||||
|
|
||||||
suspend fun <T> withNonCancellableContext(block: suspend CoroutineScope.() -> T) =
|
suspend fun <T> withNonCancellableContext(block: suspend CoroutineScope.() -> T) =
|
||||||
withContext(NonCancellable, block)
|
withContext(NonCancellable, block)
|
||||||
|
|
|
@ -30,4 +30,5 @@ class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource {
|
||||||
return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString()
|
return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnimeSourceNotInstalledException : Exception()
|
class AnimeSourceNotInstalledException : Exception()
|
||||||
|
|
|
@ -19,7 +19,11 @@ interface AnimeSourceRepository {
|
||||||
|
|
||||||
fun getSourcesWithNonLibraryAnime(): Flow<List<AnimeSourceWithCount>>
|
fun getSourcesWithNonLibraryAnime(): Flow<List<AnimeSourceWithCount>>
|
||||||
|
|
||||||
fun searchAnime(sourceId: Long, query: String, filterList: AnimeFilterList): AnimeSourcePagingSourceType
|
fun searchAnime(
|
||||||
|
sourceId: Long,
|
||||||
|
query: String,
|
||||||
|
filterList: AnimeFilterList
|
||||||
|
): AnimeSourcePagingSourceType
|
||||||
|
|
||||||
fun getPopularAnime(sourceId: Long): AnimeSourcePagingSourceType
|
fun getPopularAnime(sourceId: Long): AnimeSourcePagingSourceType
|
||||||
|
|
||||||
|
|
10
app/src/main/res/drawable/ic_internet.xml
Normal file
10
app/src/main/res/drawable/ic_internet.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M480,880q-82,0 -155,-31.5t-127.5,-86Q143,708 111.5,635T80,480q0,-83 31.5,-155.5t86,-127Q252,143 325,111.5T480,80q83,0 155.5,31.5t127,86q54.5,54.5 86,127T880,480q0,82 -31.5,155t-86,127.5q-54.5,54.5 -127,86T480,880ZM480,798q26,-36 45,-75t31,-83L404,640q12,44 31,83t45,75ZM376,782q-18,-33 -31.5,-68.5T322,640L204,640q29,50 72.5,87t99.5,55ZM584,782q56,-18 99.5,-55t72.5,-87L638,640q-9,38 -22.5,73.5T584,782ZM170,560h136q-3,-20 -4.5,-39.5T300,480q0,-21 1.5,-40.5T306,400L170,400q-5,20 -7.5,39.5T160,480q0,21 2.5,40.5T170,560ZM386,560h188q3,-20 4.5,-39.5T580,480q0,-21 -1.5,-40.5T574,400L386,400q-3,20 -4.5,39.5T380,480q0,21 1.5,40.5T386,560ZM654,560h136q5,-20 7.5,-39.5T800,480q0,-21 -2.5,-40.5T790,400L654,400q3,20 4.5,39.5T660,480q0,21 -1.5,40.5T654,560ZM638,320h118q-29,-50 -72.5,-87T584,178q18,33 31.5,68.5T638,320ZM404,320h152q-12,-44 -31,-83t-45,-75q-26,36 -45,75t-31,83ZM204,320h118q9,-38 22.5,-73.5T376,178q-56,18 -99.5,55T204,320Z"/>
|
||||||
|
</vector>
|
|
@ -6,10 +6,12 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -38,10 +40,12 @@
|
||||||
tools:ignore="TextContrastCheck"
|
tools:ignore="TextContrastCheck"
|
||||||
tools:text="Continuous" />
|
tools:text="Continuous" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -139,20 +143,22 @@
|
||||||
android:id="@+id/animeSourceTop"
|
android:id="@+id/animeSourceTop"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:rotation="90"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:rotation="90"
|
||||||
app:srcCompat="@drawable/ic_round_arrow_back_ios_new_24"
|
app:srcCompat="@drawable/ic_round_arrow_back_ios_new_24"
|
||||||
app:tint="?attr/colorOnBackground"
|
app:tint="?attr/colorOnBackground"
|
||||||
tools:ignore="ContentDescription,ImageContrastCheck" />
|
tools:ignore="ContentDescription,ImageContrastCheck" />
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/animeDownloadContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:id="@+id/animeDownloadContainer"
|
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="265dp"
|
android:layout_width="265dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -161,8 +167,8 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="@font/poppins_bold"
|
|
||||||
android:alpha="0.58"
|
android:alpha="0.58"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
android:text="Download" />
|
android:text="Download" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -194,10 +200,11 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/animeScanlatorContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical">
|
||||||
android:id="@+id/animeScanlatorContainer">
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="265dp"
|
android:layout_width="265dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -206,8 +213,8 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="@font/poppins_bold"
|
|
||||||
android:alpha="0.58"
|
android:alpha="0.58"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
android:text="Scanlator" />
|
android:text="Scanlator" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -219,12 +226,14 @@
|
||||||
tools:ignore="TextContrastCheck"
|
tools:ignore="TextContrastCheck"
|
||||||
tools:text="number" />
|
tools:text="number" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:cardBackgroundColor="@color/nav_bg_inv"
|
app:cardBackgroundColor="@color/nav_bg_inv"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:cardElevation="0dp">
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/animeScanlatorTop"
|
android:id="@+id/animeScanlatorTop"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
|
@ -235,5 +244,54 @@
|
||||||
tools:ignore="ContentDescription,ImageContrastCheck" />
|
tools:ignore="ContentDescription,ImageContrastCheck" />
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/animeWebviewContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="265dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.58"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:text="Set Cookies" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:textColor="?attr/colorSecondary"
|
||||||
|
tools:ignore="TextContrastCheck"
|
||||||
|
android:text="Open Website" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardBackgroundColor="@color/nav_bg_inv"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/animeWebViewTop"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
app:srcCompat="@drawable/ic_internet"
|
||||||
|
app:tint="?attr/colorOnBackground"
|
||||||
|
tools:ignore="ContentDescription,ImageContrastCheck" />
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
Loading…
Add table
Add a link
Reference in a new issue