webview for extensions

This commit is contained in:
rebelonion 2024-01-18 01:09:11 -06:00
parent 26b6564825
commit ff02280239
29 changed files with 447 additions and 100 deletions

View file

@ -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"

View file

@ -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")

View file

@ -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"))
) )
} }
} }

View file

@ -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

View file

@ -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 }

View file

@ -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)
} }

View file

@ -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)

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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", "")

View file

@ -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

View file

@ -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()
} }

View file

@ -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
)
} }
} }

View file

@ -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()

View file

@ -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)
} }

View file

@ -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 })
} }
} }

View file

@ -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)

View file

@ -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()

View file

@ -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"
)
} }
} }

View file

@ -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()

View file

@ -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.

View file

@ -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) {

View file

@ -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()))

View file

@ -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)

View file

@ -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()

View file

@ -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

View 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>

View file

@ -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>