feat: extension testing

This commit is contained in:
rebelonion 2024-05-19 14:17:58 -05:00
parent 949bcc418a
commit ab360b3a75
15 changed files with 1103 additions and 4 deletions

View file

@ -200,6 +200,7 @@
android:parentActivityName=".MainActivity" /> android:parentActivityName=".MainActivity" />
<activity <activity
android:name=".util.MarkdownCreatorActivity"/> android:name=".util.MarkdownCreatorActivity"/>
<activity android:name=".parsers.ParserTestActivity" />
<activity <activity
android:name=".media.ReviewActivity" android:name=".media.ReviewActivity"
android:parentActivityName=".media.MediaDetailsActivity" /> android:parentActivityName=".media.MediaDetailsActivity" />

View file

@ -53,8 +53,11 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
override val name = extension.name override val name = extension.name
override val saveName = extension.name override val saveName = extension.name
override val hostUrl = extension.sources.first().name override val hostUrl =
(extension.sources.first() as? AnimeHttpSource)?.baseUrl ?: extension.sources.first().name
override val isNSFW = extension.isNsfw override val isNSFW = extension.isNsfw
override val icon = extension.icon
override var selectDub: Boolean override var selectDub: Boolean
get() = getDub() get() = getDub()
set(value) { set(value) {
@ -324,8 +327,10 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
override val name = extension.name override val name = extension.name
override val saveName = extension.name override val saveName = extension.name
override val hostUrl = extension.sources.first().name override val hostUrl =
(extension.sources.first() as? HttpSource)?.baseUrl ?: extension.sources.first().name
override val isNSFW = extension.isNsfw override val isNSFW = extension.isNsfw
override val icon = extension.icon
override suspend fun loadChapters( override suspend fun loadChapters(
mangaLink: String, mangaLink: String,

View file

@ -1,17 +1,21 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.graphics.drawable.Drawable
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.okHttpClient
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import me.xdrop.fuzzywuzzy.FuzzySearch import me.xdrop.fuzzywuzzy.FuzzySearch
import okhttp3.Request
import java.io.Serializable import java.io.Serializable
import java.net.URLDecoder import java.net.URLDecoder
import java.net.URLEncoder import java.net.URLEncoder
import kotlin.system.measureTimeMillis
abstract class BaseParser { abstract class BaseParser {
@ -41,6 +45,11 @@ abstract class BaseParser {
* **/ * **/
open val language = "English" open val language = "English"
/**
* Icon of the site, can be null
*/
open val icon: Drawable? = null
/** /**
* Search for Anime/Manga/Novel, returns a List of Responses * Search for Anime/Manga/Novel, returns a List of Responses
* *
@ -133,10 +142,39 @@ abstract class BaseParser {
return response return response
} }
/**
* ping the site to check if it's working or not.
* @return Triple<Int, Int?, String> : First Int is the status code, Second Int is the response time in milliseconds, Third String is the response message.
*/
fun ping(): Triple<Int, Int?, String> {
val client = okHttpClient
var statusCode = 0
var responseTime: Int? = null
var responseMessage = ""
try {
val request = Request.Builder()
.url(hostUrl)
.build()
responseTime = measureTimeMillis {
client.newCall(request).execute().use { response ->
statusCode = response.code
responseMessage = response.message
}
}.toInt()
} catch (e: Exception) {
Logger.log("Failed to ping $name")
statusCode = -1
responseMessage = if (e.message.isNullOrEmpty()) "None" else e.message!!
Logger.log(e)
}
return Triple(statusCode, responseTime, responseMessage)
}
/** /**
* Used to get an existing Search Response which was selected by the user. * Used to get an existing Search Response which was selected by the user.
* **/ * @param mediaId : The mediaId of the Media object.
* @return ShowResponse? : The ShowResponse object if found, else null.
*/
open suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? { open suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? {
checkIfVariablesAreEmpty() checkIfVariablesAreEmpty()
return PrefManager.getNullableCustomVal( return PrefManager.getNullableCustomVal(
@ -148,7 +186,10 @@ abstract class BaseParser {
/** /**
* Used to save Shows Response using `saveName`. * Used to save Shows Response using `saveName`.
* **/ * @param mediaId : The mediaId of the Media object.
* @param response : The ShowResponse object to save.
* @param selected : Boolean : If the ShowResponse was selected by the user or not.
*/
open fun saveShowResponse(mediaId: Int, response: ShowResponse?, selected: Boolean = false) { open fun saveShowResponse(mediaId: Int, response: ShowResponse?, selected: Boolean = false) {
if (response != null) { if (response != null) {
checkIfVariablesAreEmpty() checkIfVariablesAreEmpty()

View file

@ -0,0 +1,38 @@
package ani.dantotsu.parsers
import android.graphics.drawable.Drawable
import android.view.View
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemExtensionSelectBinding
import com.xwray.groupie.viewbinding.BindableItem
class ExtensionSelectItem(
private val name: String,
private val image: Drawable?,
private var isSelected: Boolean,
val selectCallback: (String, Boolean) -> Unit
) : BindableItem<ItemExtensionSelectBinding>() {
private lateinit var binding: ItemExtensionSelectBinding
override fun bind(viewBinding: ItemExtensionSelectBinding, position: Int) {
binding = viewBinding
binding.extensionNameTextView.text = name
image?.let {
binding.extensionIconImageView.setImageDrawable(it)
}
binding.extensionCheckBox.setOnCheckedChangeListener(null)
binding.extensionCheckBox.isChecked = isSelected
binding.extensionCheckBox.setOnCheckedChangeListener { _, isChecked ->
isSelected = isChecked
selectCallback(name, isChecked)
}
}
override fun getLayout(): Int {
return R.layout.item_extension_select
}
override fun initializeViewBinding(view: View): ItemExtensionSelectBinding {
return ItemExtensionSelectBinding.bind(view)
}
}

View file

@ -0,0 +1,367 @@
package ani.dantotsu.parsers
import android.content.Context
import android.view.View
import androidx.core.view.isVisible
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemExtensionTestBinding
import ani.dantotsu.getThemeColor
import com.xwray.groupie.viewbinding.BindableItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ExtensionTestItem(
private var extensionType: String,
private var testType: String,
private var extension: BaseParser,
private var searchString: String = "Chainsaw Man"
) : BindableItem<ItemExtensionTestBinding>() {
private lateinit var binding: ItemExtensionTestBinding
private lateinit var context: Context
private var job: Job? = null
private var isRunning = false
private var pingResult: Triple<Int, Int?, String>? = null
private var searchResultSize: Int? = null
private var episodeResultSize: Int? = null
private var serverResultSize: Int? = null
override fun bind(viewBinding: ItemExtensionTestBinding, position: Int) {
binding = viewBinding
context = binding.root.context
binding.extensionIconImageView.setImageDrawable(extension.icon)
binding.extensionNameTextView.text = extension.name
binding.extensionLoading.isVisible = isRunning
hideAllResults()
pingResult()
searchResult()
episodeResult()
serverResult()
}
override fun getLayout(): Int {
return R.layout.item_extension_test
}
override fun initializeViewBinding(view: View): ItemExtensionTestBinding {
return ItemExtensionTestBinding.bind(view)
}
private fun hideAllResults() {
if (::binding.isInitialized.not()) return
binding.searchResultText.isVisible = false
binding.episodeResultText.isVisible = false
binding.serverResultText.isVisible = false
}
fun cancelJob() {
job?.cancel()
job = null
binding.extensionLoading.isVisible = false
}
fun startTest() {
pingResult = null
searchResultSize = null
episodeResultSize = null
serverResultSize = null
isRunning = true
hideAllResults()
job?.cancel()
job = Job()
CoroutineScope(Dispatchers.IO + job!!).launch {
when (extensionType) {
"anime" -> {
val extension = extension as AnimeParser
runAnimeTest(extension)
}
"manga" -> {
val extension = extension as MangaParser
runMangaTest(extension)
}
"novel" -> {
val extension = extension as NovelParser
runNovelTest(extension)
}
}
}
}
private suspend fun runAnimeTest(extension: AnimeParser) {
pingResult = extension.ping()
withContext(Dispatchers.Main) {
pingResult()
}
if (testType == "ping") {
done()
return
}
val searchResult = extension.search(searchString)
searchResultSize = searchResult.size
withContext(Dispatchers.Main) {
searchResult()
}
if (searchResultSize == 0 || testType == "basic") {
done()
return
}
val episodeResult = extension.loadEpisodes("", null, searchResult.first().sAnime!!)
episodeResultSize = episodeResult.size
withContext(Dispatchers.Main) {
episodeResult()
}
if (episodeResultSize == 0) {
done()
return
}
val serverResult = extension.loadVideoServers("", null, episodeResult.first().sEpisode!!)
serverResultSize = serverResult.size
withContext(Dispatchers.Main) {
serverResult()
}
done()
}
private suspend fun runMangaTest(extension: MangaParser) {
pingResult = extension.ping()
withContext(Dispatchers.Main) {
pingResult()
}
if (testType == "ping") {
done()
return
}
val searchResult = extension.search(searchString)
searchResultSize = searchResult.size
withContext(Dispatchers.Main) {
searchResult()
}
if (searchResultSize == 0 || testType == "basic") {
done()
return
}
val chapterResult = extension.loadChapters("", null, searchResult.first().sManga!!)
episodeResultSize = chapterResult.size
withContext(Dispatchers.Main) {
episodeResult()
}
if (episodeResultSize == 0) {
done()
return
}
val serverResult = extension.loadImages("", chapterResult.first().sChapter)
serverResultSize = serverResult.size
withContext(Dispatchers.Main) {
serverResult()
}
withContext(Dispatchers.Main) {
if (::binding.isInitialized )
binding.extensionLoading.isVisible = false
isRunning = false
}
}
private suspend fun runNovelTest(extension: NovelParser) {
withContext(Dispatchers.Main) {
pingResult()
}
if (testType == "ping") {
done()
return
}
val searchResult = extension.search(searchString)
searchResultSize = searchResult.size
withContext(Dispatchers.Main) {
searchResult()
}
if (searchResultSize == 0 || testType == "basic") {
done()
return
}
val chapterResult = extension.loadBook(searchResult.first().link, null)
episodeResultSize = chapterResult.links.size
withContext(Dispatchers.Main) {
episodeResult()
serverResult()
}
withContext(Dispatchers.Main) {
if (::binding.isInitialized )
binding.extensionLoading.isVisible = false
isRunning = false
}
}
private fun done() {
if (::binding.isInitialized.not()) return
binding.extensionLoading.isVisible = false
isRunning = false
}
private fun pingResult() {
if (::binding.isInitialized.not()) return
if (extensionType == "novel") {
binding.pingResultText.isVisible = true
binding.pingResultText.text = context.getString(R.string.test_not_supported)
binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_round_info_24, 0, 0, 0
)
return
}
if (pingResult == null) {
binding.pingResultText.isVisible = false
return
} else {
binding.pingResultText.isVisible = true
}
binding.pingResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
)
val (code, time, message) = pingResult!!
if (code == 200) {
binding.pingResultText.text = context.getString(R.string.ping_success, time.toString())
binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_check, 0, 0, 0
)
return
}
binding.pingResultText.text =
context.getString(R.string.ping_error, code.toString(), message)
binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_cancel, 0, 0, 0
)
binding.pingResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorError)
)
}
private fun searchResult() {
if (::binding.isInitialized.not()) return
if (searchResultSize == null) {
binding.searchResultText.isVisible = false
return
}
binding.searchResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
)
binding.searchResultText.isVisible = true
if (searchResultSize == 0) {
val text = context.getString(R.string.title_search_test,
context.getString(R.string.no_results_found))
binding.searchResultText.text = text
binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_cancel, 0, 0, 0
)
binding.searchResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorError)
)
return
}
val text = context.getString(R.string.title_search_test,
context.getString(R.string.results_found, searchResultSize.toString()))
binding.searchResultText.text = text
binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_check, 0, 0, 0
)
}
private fun episodeResult() {
if (::binding.isInitialized.not()) return
if (episodeResultSize == null) {
binding.episodeResultText.isVisible = false
return
}
binding.episodeResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
)
binding.episodeResultText.isVisible = true
if (episodeResultSize == 0) {
val text = when(extensionType) {
"anime" -> context.getString(R.string.episode_search_test,
context.getString(R.string.no_results_found))
"manga" -> context.getString(R.string.chapter_search_test,
context.getString(R.string.no_results_found))
else -> context.getString(R.string.book_search_test,
context.getString(R.string.no_results_found))
}
binding.episodeResultText.text = text
binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_cancel, 0, 0, 0
)
binding.episodeResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorError)
)
return
}
val text = when(extensionType) {
"anime" -> context.getString(R.string.episode_search_test,
context.getString(R.string.results_found, episodeResultSize.toString()))
"manga" -> context.getString(R.string.chapter_search_test,
context.getString(R.string.results_found, episodeResultSize.toString()))
else -> context.getString(R.string.book_search_test,
context.getString(R.string.results_found, episodeResultSize.toString()))
}
binding.episodeResultText.text = text
binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_check, 0, 0, 0
)
}
private fun serverResult() {
if (::binding.isInitialized.not()) return
if (extensionType == "novel") {
binding.pingResultText.isVisible = true
binding.pingResultText.text = context.getString(R.string.test_not_supported)
binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_round_info_24, 0, 0, 0
)
return
}
if (serverResultSize == null) {
binding.serverResultText.isVisible = false
return
}
binding.serverResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
)
binding.serverResultText.isVisible = true
if (serverResultSize == 0) {
val text = when(extensionType) {
"anime" -> context.getString(R.string.video_search_test,
context.getString(R.string.no_results_found))
"manga" -> context.getString(R.string.image_search_test,
context.getString(R.string.no_results_found))
else -> context.getString(R.string.book_search_test,
context.getString(R.string.no_results_found))
}
binding.serverResultText.text = text
binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_cancel, 0, 0, 0
)
binding.serverResultText.setTextColor(
context.getThemeColor(com.google.android.material.R.attr.colorError)
)
return
}
val text = when(extensionType) {
"anime" -> context.getString(R.string.video_search_test,
context.getString(R.string.results_found, serverResultSize.toString()))
"manga" -> context.getString(R.string.image_search_test,
context.getString(R.string.results_found, serverResultSize.toString()))
else -> context.getString(R.string.book_search_test,
context.getString(R.string.results_found, serverResultSize.toString()))
}
binding.serverResultText.text = text
binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_check, 0, 0, 0
)
}
}

View file

@ -0,0 +1,117 @@
package ani.dantotsu.parsers
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.databinding.BottomSheetExtensionTestSettingsBinding
import ani.dantotsu.parsers.novel.NovelExtensionManager
import com.xwray.groupie.GroupieAdapter
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionTestSettingsBottomDialog : BottomSheetDialogFragment() {
private var _binding: BottomSheetExtensionTestSettingsBinding? = null
private val binding get() = _binding!!
private val adapter: GroupieAdapter = GroupieAdapter()
private val animeExtension: AnimeExtensionManager = Injekt.get()
private val mangaExtensions: MangaExtensionManager = Injekt.get()
private val novelExtensions: NovelExtensionManager = Injekt.get()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = BottomSheetExtensionTestSettingsBinding.inflate(inflater, container, false)
return _binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.extensionSelectionRecyclerView.adapter = adapter
binding.extensionSelectionRecyclerView.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.VERTICAL,
false
)
binding.animeRadioButton.setOnCheckedChangeListener { _, b ->
if (b) {
extensionType = "anime"
extensionsToTest.clear()
setupAdapter()
}
}
binding.mangaRadioButton.setOnCheckedChangeListener { _, b ->
if (b) {
extensionType = "manga"
extensionsToTest.clear()
setupAdapter()
}
}
binding.novelsRadioButton.setOnCheckedChangeListener { _, b ->
if (b) {
extensionType = "novel"
extensionsToTest.clear()
setupAdapter()
}
}
binding.pingRadioButton.setOnCheckedChangeListener { _, b ->
if (b) {
testType = "ping"
}
}
binding.basicRadioButton.setOnCheckedChangeListener { _, b ->
if (b) {
testType = "basic"
}
}
binding.fullRadioButton.setOnCheckedChangeListener { _, b ->
if (b) {
testType = "full"
}
}
setupAdapter()
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
private fun setupAdapter() {
val namesAndUrls: Map<String,Drawable?> = when (extensionType) {
"anime" -> animeExtension.installedExtensionsFlow.value.associate { it.name to it.icon }
"manga" -> mangaExtensions.installedExtensionsFlow.value.associate { it.name to it.icon }
"novel" -> novelExtensions.installedExtensionsFlow.value.associate { it.name to it.icon }
else -> emptyMap()
}
adapter.clear()
namesAndUrls.forEach { (name, icon) ->
val isSelected = extensionsToTest.contains(name)
adapter.add(ExtensionSelectItem(name, icon, isSelected, ::selectedCallback))
}
}
private fun selectedCallback(name: String, isSelected: Boolean) {
if (isSelected) {
extensionsToTest.add(name)
} else {
extensionsToTest.remove(name)
}
}
companion object {
fun newInstance(): ExtensionTestSettingsBottomDialog {
return ExtensionTestSettingsBottomDialog()
}
var extensionType = "anime"
var testType = "ping"
var extensionsToTest: MutableList<String> = mutableListOf()
}
}

View file

@ -0,0 +1,112 @@
package ani.dantotsu.parsers
import android.os.Bundle
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.databinding.ActivityParserTestBinding
import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toast
import com.xwray.groupie.GroupieAdapter
class ParserTestActivity : AppCompatActivity() {
private lateinit var binding: ActivityParserTestBinding
val adapter = GroupieAdapter()
val extensionsToTest: MutableList<ExtensionTestItem> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme()
initActivity(this)
binding = ActivityParserTestBinding.inflate(layoutInflater)
binding.toolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
}
binding.extensionResultsRecyclerView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
setContentView(binding.root)
binding.extensionResultsRecyclerView.adapter = adapter
binding.extensionResultsRecyclerView.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL,
false
)
binding.backButton.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
binding.optionsLayout.setOnClickListener {
ExtensionTestSettingsBottomDialog.newInstance()
.show(supportFragmentManager, "extension_test_settings")
}
binding.startButton.setOnClickListener {
if (ExtensionTestSettingsBottomDialog.extensionsToTest.isEmpty()) {
toast(R.string.no_extensions_selected)
return@setOnClickListener
}
extensionsToTest.forEach {
it.cancelJob()
}
extensionsToTest.clear()
adapter.clear()
when (ExtensionTestSettingsBottomDialog.extensionType) {
"anime" -> {
ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
val extension =
AnimeSources.list.find { source -> source.name == name }?.get?.value
extension?.let {
extensionsToTest.add(
ExtensionTestItem(
"anime",
ExtensionTestSettingsBottomDialog.testType,
it
)
)
}
}
}
"manga" -> {
ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
val extension =
MangaSources.list.find { source -> source.name == name }?.get?.value
extension?.let {
extensionsToTest.add(
ExtensionTestItem(
"manga",
ExtensionTestSettingsBottomDialog.testType,
it
)
)
}
}
}
"novel" -> {
ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
val extension =
NovelSources.list.find { source -> source.name == name }?.get?.value
extension?.let {
extensionsToTest.add(
ExtensionTestItem(
"novel",
ExtensionTestSettingsBottomDialog.testType,
it
)
)
}
}
}
}
extensionsToTest.forEach {
adapter.add(it)
it.startTest()
}
}
}
}

View file

@ -1,6 +1,7 @@
package ani.dantotsu.settings package ani.dantotsu.settings
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
@ -13,6 +14,7 @@ import android.view.inputmethod.EditorInfo
import android.widget.AutoCompleteTextView import android.widget.AutoCompleteTextView
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
@ -28,6 +30,7 @@ import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.others.AndroidBug5497Workaround import ani.dantotsu.others.AndroidBug5497Workaround
import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.ParserTestActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
@ -70,6 +73,14 @@ class ExtensionsActivity : AppCompatActivity() {
bottomMargin = statusBarHeight + navBarHeight bottomMargin = statusBarHeight + navBarHeight
} }
binding.testButton.setOnClickListener {
ContextCompat.startActivity(
this,
Intent(this, ParserTestActivity::class.java),
null
)
}
val tabLayout = findViewById<TabLayout>(R.id.tabLayout) val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
val viewPager = findViewById<ViewPager2>(R.id.viewPager) val viewPager = findViewById<ViewPager2>(R.id.viewPager)
viewPager.offscreenPageLimit = 1 viewPager.offscreenPageLimit = 1

View file

@ -1,6 +1,7 @@
package ani.dantotsu.settings package ani.dantotsu.settings
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.KeyEvent import android.view.KeyEvent
@ -9,6 +10,7 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -20,6 +22,7 @@ import ani.dantotsu.databinding.ItemRepositoryBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaType import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.parsers.ParserTestActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
@ -232,6 +235,20 @@ class SettingsExtensionsActivity : AppCompatActivity() {
setExtensionOutput(it.attachView, MediaType.MANGA) setExtensionOutput(it.attachView, MediaType.MANGA)
} }
), ),
Settings(
type = 1,
name = getString(R.string.extension_test),
desc = getString(R.string.extension_test_desc),
icon = R.drawable.ic_round_search_sources_24,
isActivity = true,
onClick = {
ContextCompat.startActivity(
context,
Intent(context, ParserTestActivity::class.java),
null
)
}
),
Settings( Settings(
type = 1, type = 1,
name = getString(R.string.user_agent), name = getString(R.string.user_agent),

View file

@ -41,6 +41,15 @@
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageButton
android:id="@+id/testButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/sort_by"
app:srcCompat="@drawable/ic_round_search_sources_24"
app:tint="?attr/colorOnBackground" />
<ImageButton <ImageButton
android:id="@+id/openSettingsButton" android:id="@+id/openSettingsButton"
android:layout_width="48dp" android:layout_width="48dp"

View file

@ -0,0 +1,109 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".settings.SettingsNotificationActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/backButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="12dp"
android:src="@drawable/ic_round_arrow_back_ios_new_24"
app:tint="?attr/colorOnBackground"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="44dp"
android:ellipsize="end"
android:fontFamily="@font/poppins_bold"
android:gravity="center|start"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:textColor="?attr/colorOnBackground"
android:textSize="18sp"
android:text="@string/extension_test" />
</FrameLayout>
<LinearLayout
android:id="@+id/optionsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:layout_marginVertical="16dp"
android:layout_marginEnd="24dp"
app:srcCompat="@drawable/ic_round_filter_24"
app:tint="?attr/colorPrimary"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="@font/poppins_bold"
android:text="@string/view_options"
android:textColor="?attr/colorOnSurface"
android:textSize="16sp" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginVertical="16dp"
android:rotation="180"
android:visibility="gone"
app:srcCompat="@drawable/ic_round_arrow_back_ios_new_24"
app:tint="?attr/colorPrimary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</LinearLayout>
<Button
android:id="@+id/startButton"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:layout_marginVertical="16dp"
android:backgroundTint="?attr/colorPrimaryContainer"
android:fontFamily="@font/poppins_bold"
android:text="@string/start_test"
android:textColor="?attr/colorOnPrimaryContainer"
app:cornerRadius="12dp"
app:iconTint="?attr/colorOnPrimaryContainer" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/extensionResultsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginVertical="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:fontFamily="@font/poppins_bold"
android:text="@string/extension_type"
android:textAlignment="center"
android:textSize="16sp" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/animeRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:checked="true"
android:text="@string/anime"
android:textSize="16sp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/mangaRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/manga"
android:textSize="16sp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/novelsRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/novels"
android:textSize="16sp" />
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_marginTop="16dp"
android:fontFamily="@font/poppins_bold"
android:text="@string/test_type"
android:textAlignment="center"
android:textSize="16sp" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/pingRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:checked="true"
android:text="@string/ping"
android:textSize="16sp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/basicRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/basic"
android:textSize="16sp" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/fullRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/full"
android:textSize="16sp" />
</RadioGroup>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/extensionSelectionRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
tools:listitem="@layout/item_extension_select"/>
</LinearLayout>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/extensionCardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<ImageView
android:id="@+id/extensionIconImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="10dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="3dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/extensionNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/extension_name"
android:textSize="15sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/extensionCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" />
</LinearLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/extensionCardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/extensionIconImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginEnd="3dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/extensionNameTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/extension_name"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/resultLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/pingResultText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/placeholder"
android:textSize="13sp"
android:gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:drawableStartCompat="@drawable/ic_round_help_24" />
<TextView
android:id="@+id/searchResultText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/placeholder"
android:textSize="13sp"
android:gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:drawableStartCompat="@drawable/ic_round_help_24" />
<TextView
android:id="@+id/episodeResultText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/placeholder"
android:textSize="13sp"
android:gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:drawableStartCompat="@drawable/ic_round_help_24" />
<TextView
android:id="@+id/serverResultText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="10dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/placeholder"
android:textSize="13sp"
android:gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:drawableStartCompat="@drawable/ic_round_help_24" />
</LinearLayout>
<ProgressBar
android:id="@+id/extensionLoading"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginVertical="10dp"
android:visibility="visible" />
</LinearLayout>

View file

@ -996,4 +996,25 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="subscriptions">Subscriptions</string> <string name="subscriptions">Subscriptions</string>
<string name="subscription_deleted">Subscription Deleted</string> <string name="subscription_deleted">Subscription Deleted</string>
<string name="foreground_service_not_allowed">Cannot install when app is in background</string> <string name="foreground_service_not_allowed">Cannot install when app is in background</string>
<string name="ping">Ping</string>
<string name="basic">Basic</string>
<string name="full">Full</string>
<string name="test_type">Test Type</string>
<string name="extension_type">Extension Type</string>
<string name="view_options">View Options</string>
<string name="extension_test">Extension Test</string>
<string name="extension_test_desc">Test if the extensions are working</string>
<string name="start_test">Start Test</string>
<string name="no_extensions_selected">No extensions selected</string>
<string name="ping_success">Ping successful: %1$s ms</string>
<string name="ping_error">Ping failed. Code: %1$s, Message: %2$s</string>
<string name="test_not_supported">Test not supported for this extension</string>
<string name="no_results_found">No results found. Extension may be broken</string>
<string name="results_found">%1$s results found</string>
<string name="title_search_test">Title Search Test: %1$s</string>
<string name="episode_search_test">Episode Search Test: %1$s</string>
<string name="chapter_search_test">Chapter Search Test: %1$s</string>
<string name="video_search_test">Video Search Test: %1$s</string>
<string name="image_search_test">Image Search Test: %1$s</string>
<string name="book_search_test">Book Search Test: %1$s</string>
</resources> </resources>