feat: allow partial urls
This commit is contained in:
parent
116de6324e
commit
38d68a7976
13 changed files with 360 additions and 463 deletions
|
@ -29,7 +29,6 @@ import ani.dantotsu.util.Logger
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.time.delay
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
|
|
|
@ -394,11 +394,10 @@
|
|||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<!-- Support both schemes -->
|
||||
<data android:host="add-repo"/>
|
||||
<data android:scheme="tachiyomi"/>
|
||||
<data android:host="add-repo"/>
|
||||
<data android:scheme="aniyomi"/>
|
||||
<data android:host="add-repo"/>
|
||||
<data android:scheme="novelyomi"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
|
|
|
@ -449,19 +449,20 @@ class MainActivity : AppCompatActivity() {
|
|||
if (uri == null) {
|
||||
throw Exception("Uri is null")
|
||||
}
|
||||
if ((uri.scheme == "tachiyomi" || uri.scheme == "aniyomi") && uri.host == "add-repo") {
|
||||
if ((uri.scheme == "tachiyomi" || uri.scheme == "aniyomi" || uri.scheme == "novelyomi") && uri.host == "add-repo") {
|
||||
val url = uri.getQueryParameter("url") ?: throw Exception("No url for repo import")
|
||||
val prefName = if (uri.scheme == "tachiyomi") {
|
||||
PrefName.MangaExtensionRepos
|
||||
} else {
|
||||
PrefName.AnimeExtensionRepos
|
||||
val (prefName, name) = when (uri.scheme) {
|
||||
"tachiyomi" -> PrefName.MangaExtensionRepos to "Manga"
|
||||
"aniyomi" -> PrefName.AnimeExtensionRepos to "Anime"
|
||||
"novelyomi" -> PrefName.NovelExtensionRepos to "Novel"
|
||||
else -> throw Exception("Invalid scheme")
|
||||
}
|
||||
val savedRepos: Set<String> = PrefManager.getVal(prefName)
|
||||
val newRepos = savedRepos.toMutableSet()
|
||||
AddRepositoryBottomSheet.addRepoWarning(this) {
|
||||
newRepos.add(url)
|
||||
PrefManager.setVal(prefName, newRepos)
|
||||
toast("${if (uri.scheme == "tachiyomi") "Manga" else "Anime"} Extension Repo added")
|
||||
toast("$name Extension Repo added")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -488,9 +489,9 @@ class MainActivity : AppCompatActivity() {
|
|||
return@passwordAlertDialog
|
||||
}
|
||||
if (PreferencePackager.unpack(decryptedJson)) {
|
||||
val intent = Intent(this, this.javaClass)
|
||||
val newIntent = Intent(this, this.javaClass)
|
||||
this.finish()
|
||||
startActivity(intent)
|
||||
startActivity(newIntent)
|
||||
}
|
||||
} else {
|
||||
toast("Password cannot be empty")
|
||||
|
@ -499,9 +500,9 @@ class MainActivity : AppCompatActivity() {
|
|||
} else if (name.endsWith(".ani")) {
|
||||
val decryptedJson = jsonString.toString(Charsets.UTF_8)
|
||||
if (PreferencePackager.unpack(decryptedJson)) {
|
||||
val intent = Intent(this, this.javaClass)
|
||||
val newIntent = Intent(this, this.javaClass)
|
||||
this.finish()
|
||||
startActivity(intent)
|
||||
startActivity(newIntent)
|
||||
}
|
||||
} else {
|
||||
toast("Invalid file type")
|
||||
|
|
|
@ -26,6 +26,7 @@ sealed class NovelExtension {
|
|||
override val pkgName: String,
|
||||
override val versionName: String,
|
||||
override val versionCode: Long,
|
||||
var repository: String,
|
||||
val sources: List<AvailableNovelSources>,
|
||||
val iconUrl: String,
|
||||
) : NovelExtension()
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
package ani.dantotsu.parsers.novel
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Date
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class NovelExtensionGithubApi {
|
||||
|
||||
private val networkService: NetworkHelper by injectLazy()
|
||||
private val novelExtensionManager: NovelExtensionManager by injectLazy()
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val lastExtCheck: Long = PrefManager.getVal(PrefName.NovelLastExtCheck)
|
||||
|
||||
private var requiresFallbackSource = false
|
||||
|
||||
suspend fun findExtensions(): List<NovelExtension.Available> {
|
||||
return withIOContext {
|
||||
val githubResponse = if (requiresFallbackSource) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
networkService.client
|
||||
.newCall(GET("${REPO_URL_PREFIX}index.min.json"))
|
||||
.awaitSuccess()
|
||||
} catch (e: Throwable) {
|
||||
Logger.log("Failed to get extensions from GitHub")
|
||||
requiresFallbackSource = true
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val response = githubResponse ?: run {
|
||||
Logger.log("using fallback source")
|
||||
networkService.client
|
||||
.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json"))
|
||||
.awaitSuccess()
|
||||
}
|
||||
|
||||
Logger.log("response: $response")
|
||||
|
||||
val extensions = with(json) {
|
||||
response
|
||||
.parseAs<List<NovelExtensionJsonObject>>()
|
||||
.toExtensions()
|
||||
}
|
||||
|
||||
// Sanity check - a small number of extensions probably means something broke
|
||||
// with the repo generator
|
||||
/*if (extensions.size < 10) { //TODO: uncomment when more extensions are added
|
||||
throw Exception()
|
||||
}*/
|
||||
Logger.log("extensions: $extensions")
|
||||
extensions
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkForUpdates(
|
||||
context: Context,
|
||||
fromAvailableExtensionList: Boolean = false
|
||||
): List<AnimeExtension.Installed>? {
|
||||
// Limit checks to once a day at most
|
||||
if (fromAvailableExtensionList && Date().time < lastExtCheck + 1.days.inWholeMilliseconds) {
|
||||
return null
|
||||
}
|
||||
|
||||
val extensions = if (fromAvailableExtensionList) {
|
||||
novelExtensionManager.availableExtensionsFlow.value
|
||||
} else {
|
||||
findExtensions().also {
|
||||
PrefManager.setVal(PrefName.NovelLastExtCheck, Date().time)
|
||||
}
|
||||
}
|
||||
|
||||
val installedExtensions = ExtensionLoader.loadNovelExtensions(context)
|
||||
.filterIsInstance<AnimeLoadResult.Success>()
|
||||
.map { it.extension }
|
||||
|
||||
val extensionsWithUpdate = mutableListOf<AnimeExtension.Installed>()
|
||||
for (installedExt in installedExtensions) {
|
||||
val pkgName = installedExt.pkgName
|
||||
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
|
||||
|
||||
val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
|
||||
val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer)
|
||||
if (hasUpdate) {
|
||||
extensionsWithUpdate.add(installedExt)
|
||||
}
|
||||
}
|
||||
|
||||
if (extensionsWithUpdate.isNotEmpty()) {
|
||||
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
|
||||
}
|
||||
|
||||
return extensionsWithUpdate
|
||||
}
|
||||
|
||||
private fun List<NovelExtensionJsonObject>.toExtensions(): List<NovelExtension.Available> {
|
||||
return mapNotNull { extension ->
|
||||
val sources = extension.sources?.map { source ->
|
||||
NovelExtensionSourceJsonObject(
|
||||
source.id,
|
||||
source.lang,
|
||||
source.name,
|
||||
source.baseUrl,
|
||||
)
|
||||
}
|
||||
val iconUrl = "${REPO_URL_PREFIX}icon/${extension.pkg}.png"
|
||||
NovelExtension.Available(
|
||||
extension.name,
|
||||
extension.pkg,
|
||||
extension.apk,
|
||||
extension.code,
|
||||
sources?.toSources() ?: emptyList(),
|
||||
iconUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<NovelExtensionSourceJsonObject>.toSources(): List<AvailableNovelSources> {
|
||||
return map { source ->
|
||||
AvailableNovelSources(
|
||||
source.id,
|
||||
source.lang,
|
||||
source.name,
|
||||
source.baseUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getApkUrl(extension: NovelExtension.Available): String {
|
||||
return "${getUrlPrefix()}apk/${extension.pkgName}.apk"
|
||||
}
|
||||
|
||||
private fun getUrlPrefix(): String {
|
||||
return if (requiresFallbackSource) {
|
||||
FALLBACK_REPO_URL_PREFIX
|
||||
} else {
|
||||
REPO_URL_PREFIX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val REPO_URL_PREFIX =
|
||||
"https://raw.githubusercontent.com/dannovels/novel-extensions/main/"
|
||||
private const val FALLBACK_REPO_URL_PREFIX =
|
||||
"https://gcore.jsdelivr.net/gh/dannovels/novel-extensions@latest/"
|
||||
|
||||
@Serializable
|
||||
private data class NovelExtensionJsonObject(
|
||||
val name: String,
|
||||
val pkg: String,
|
||||
val apk: String,
|
||||
val lang: String,
|
||||
val code: Long,
|
||||
val version: String,
|
||||
val nsfw: Int,
|
||||
val hasReadme: Int = 0,
|
||||
val hasChangelog: Int = 0,
|
||||
val sources: List<NovelExtensionSourceJsonObject>?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class NovelExtensionSourceJsonObject(
|
||||
val id: Long,
|
||||
val lang: String,
|
||||
val name: String,
|
||||
val baseUrl: String,
|
||||
)
|
||||
|
|
@ -6,6 +6,7 @@ import ani.dantotsu.media.MediaType
|
|||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
|
@ -22,7 +23,7 @@ class NovelExtensionManager(private val context: Context) {
|
|||
/**
|
||||
* API where all the available Novel extensions can be found.
|
||||
*/
|
||||
private val api = NovelExtensionGithubApi()
|
||||
private val api = ExtensionGithubApi()
|
||||
|
||||
/**
|
||||
* The installer which installs, updates and uninstalls the Novel extensions.
|
||||
|
@ -70,7 +71,7 @@ class NovelExtensionManager(private val context: Context) {
|
|||
*/
|
||||
suspend fun findAvailableExtensions() {
|
||||
val extensions: List<NovelExtension.Available> = try {
|
||||
api.findExtensions()
|
||||
api.findNovelExtensions()
|
||||
} catch (e: Exception) {
|
||||
Logger.log("Error finding extensions: ${e.message}")
|
||||
withUIContext { snackString("Failed to get Novel extensions list") }
|
||||
|
@ -119,7 +120,7 @@ class NovelExtensionManager(private val context: Context) {
|
|||
* @param extension The anime extension to be installed.
|
||||
*/
|
||||
fun installExtension(extension: NovelExtension.Available): Observable<InstallStep> {
|
||||
return installer.downloadAndInstall(api.getApkUrl(extension), extension.pkgName,
|
||||
return installer.downloadAndInstall(api.getNovelApkUrl(extension), extension.pkgName,
|
||||
extension.name, MediaType.NOVEL)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.settings
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -10,29 +11,52 @@ import android.view.inputmethod.EditorInfo
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import ani.dantotsu.BottomSheetDialogFragment
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.copyToClipboard
|
||||
import ani.dantotsu.databinding.BottomSheetAddRepositoryBinding
|
||||
import ani.dantotsu.databinding.ItemRepoBinding
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.util.customAlertDialog
|
||||
import com.xwray.groupie.GroupieAdapter
|
||||
import com.xwray.groupie.viewbinding.BindableItem
|
||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class RepoItem(
|
||||
val url: String,
|
||||
val onRemove: (String) -> Unit
|
||||
private val mediaType: MediaType,
|
||||
val onRemove: (String, MediaType) -> Unit
|
||||
) :BindableItem<ItemRepoBinding>() {
|
||||
override fun getLayout() = R.layout.item_repo
|
||||
|
||||
override fun bind(viewBinding: ItemRepoBinding, position: Int) {
|
||||
viewBinding.repoNameTextView.text = url
|
||||
viewBinding.repoNameTextView.text = url.cleanShownUrl()
|
||||
viewBinding.repoDeleteImageView.setOnClickListener {
|
||||
onRemove(url)
|
||||
onRemove(url, mediaType)
|
||||
}
|
||||
viewBinding.repoCopyImageView.setOnClickListener {
|
||||
viewBinding.repoCopyImageView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
copyToClipboard(url, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initializeViewBinding(view: View): ItemRepoBinding {
|
||||
return ItemRepoBinding.bind(view)
|
||||
}
|
||||
|
||||
private fun String.cleanShownUrl(): String {
|
||||
return this
|
||||
.removePrefix("https://raw.githubusercontent.com/")
|
||||
.replace("index.min.json", "")
|
||||
.removeSuffix("/")
|
||||
}
|
||||
}
|
||||
|
||||
class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
||||
|
@ -41,7 +65,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
|||
private var mediaType: MediaType = MediaType.ANIME
|
||||
private var onRepositoryAdded: ((String, MediaType) -> Unit)? = null
|
||||
private var repositories: MutableList<String> = mutableListOf()
|
||||
private var onRepositoryRemoved: ((String) -> Unit)? = null
|
||||
private var onRepositoryRemoved: ((String, MediaType) -> Unit)? = null
|
||||
private var adapter: GroupieAdapter = GroupieAdapter()
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -62,24 +86,19 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
|||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
adapter.addAll(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
|
||||
adapter.addAll(repositories.map { RepoItem(it, mediaType, ::onRepositoryRemoved) })
|
||||
|
||||
binding.repositoryInput.hint = when(mediaType) {
|
||||
MediaType.ANIME -> getString(R.string.anime_add_repository)
|
||||
MediaType.MANGA -> getString(R.string.manga_add_repository)
|
||||
else -> ""
|
||||
MediaType.NOVEL -> getString(R.string.novel_add_repository)
|
||||
}
|
||||
|
||||
binding.addButton.setOnClickListener {
|
||||
val input = binding.repositoryInput.text.toString()
|
||||
val error = isValidUrl(input)
|
||||
if (error == null) {
|
||||
context?.let { context ->
|
||||
addRepoWarning(context) {
|
||||
onRepositoryAdded?.invoke(input, mediaType)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
acceptUrl(input)
|
||||
} else {
|
||||
binding.repositoryInput.error = error
|
||||
}
|
||||
|
@ -96,12 +115,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
|||
if (url.isNotBlank()) {
|
||||
val error = isValidUrl(url)
|
||||
if (error == null) {
|
||||
context?.let { context ->
|
||||
addRepoWarning(context) {
|
||||
onRepositoryAdded?.invoke(url, mediaType)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
acceptUrl(url)
|
||||
return@setOnEditorActionListener true
|
||||
} else {
|
||||
binding.repositoryInput.error = error
|
||||
|
@ -112,20 +126,62 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun onRepositoryRemoved(url: String) {
|
||||
onRepositoryRemoved?.invoke(url)
|
||||
repositories.remove(url)
|
||||
adapter.update(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
|
||||
private fun acceptUrl(url: String) {
|
||||
val finalUrl = getRepoUrl(url)
|
||||
context?.let { context ->
|
||||
addRepoWarning(context) {
|
||||
onRepositoryAdded?.invoke(finalUrl, mediaType)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidUrl(url: String): String? {
|
||||
if (!url.startsWith("https://") && !url.startsWith("http://"))
|
||||
return "URL must start with http:// or https://"
|
||||
if (!url.removeSuffix("/").endsWith("index.min.json"))
|
||||
return "URL must end with index.min.json"
|
||||
private fun isValidUrl(input: String): String? {
|
||||
if (input.startsWith("http://") || input.startsWith("https://")) {
|
||||
if (!input.removeSuffix("/").endsWith("index.min.json")) {
|
||||
return "URL must end with index.min.json"
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val parts = input.split("/")
|
||||
if (parts.size !in 2..3) {
|
||||
return "Must be a full URL or in format: username/repo[/branch]"
|
||||
}
|
||||
|
||||
val username = parts[0]
|
||||
val repo = parts[1]
|
||||
val branch = if (parts.size == 3) parts[2] else "repo"
|
||||
|
||||
if (username.isBlank() || repo.isBlank()) {
|
||||
return "Username and repository name cannot be empty"
|
||||
}
|
||||
if (parts.size == 3 && branch.isBlank()) {
|
||||
return "Branch name cannot be empty"
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getRepoUrl(input: String): String {
|
||||
if (input.startsWith("http://") || input.startsWith("https://")) {
|
||||
return input
|
||||
}
|
||||
|
||||
val parts = input.split("/")
|
||||
val username = parts[0]
|
||||
val repo = parts[1]
|
||||
val branch = if (parts.size == 3) parts[2] else "repo"
|
||||
|
||||
return "https://raw.githubusercontent.com/$username/$repo/$branch/index.min.json"
|
||||
}
|
||||
|
||||
private fun onRepositoryRemoved(url: String, mediaType: MediaType) {
|
||||
onRepositoryRemoved?.invoke(url, mediaType)
|
||||
repositories.remove(url)
|
||||
adapter.update(repositories.map { RepoItem(it, mediaType, ::onRepositoryRemoved) })
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
|
@ -142,11 +198,81 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
|
|||
.setNegButton(R.string.cancel) { }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun addRepo(input: String, mediaType: MediaType) {
|
||||
val validLink = if (input.contains("github.com") && input.contains("blob")) {
|
||||
input.replace("github.com", "raw.githubusercontent.com")
|
||||
.replace("/blob/", "/")
|
||||
} else input
|
||||
|
||||
when (mediaType) {
|
||||
MediaType.ANIME -> {
|
||||
val anime =
|
||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||
.plus(validLink)
|
||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Injekt.get<AnimeExtensionManager>().findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
MediaType.MANGA -> {
|
||||
val manga =
|
||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||
.plus(validLink)
|
||||
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Injekt.get<MangaExtensionManager>().findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
MediaType.NOVEL -> {
|
||||
val novel =
|
||||
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
|
||||
.plus(validLink)
|
||||
PrefManager.setVal(PrefName.NovelExtensionRepos, novel)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Injekt.get<NovelExtensionManager>().findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeRepo(input: String, mediaType: MediaType) {
|
||||
when (mediaType) {
|
||||
MediaType.ANIME -> {
|
||||
val anime =
|
||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||
.minus(input)
|
||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Injekt.get<AnimeExtensionManager>().findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
MediaType.MANGA -> {
|
||||
val manga =
|
||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||
.minus(input)
|
||||
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Injekt.get<MangaExtensionManager>().findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
MediaType.NOVEL -> {
|
||||
val novel =
|
||||
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
|
||||
.minus(input)
|
||||
PrefManager.setVal(PrefName.NovelExtensionRepos, novel)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Injekt.get<NovelExtensionManager>().findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newInstance(
|
||||
mediaType: MediaType,
|
||||
repositories: List<String>,
|
||||
onRepositoryAdded: (String, MediaType) -> Unit,
|
||||
onRepositoryRemoved: (String) -> Unit
|
||||
onRepositoryRemoved: (String, MediaType) -> Unit
|
||||
): AddRepositoryBottomSheet {
|
||||
return AddRepositoryBottomSheet().apply {
|
||||
this.mediaType = mediaType
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
package ani.dantotsu.settings
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
|
@ -20,10 +14,7 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.copyToClipboard
|
||||
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
||||
import ani.dantotsu.databinding.DialogRepositoriesBinding
|
||||
import ani.dantotsu.databinding.ItemRepositoryBinding
|
||||
import ani.dantotsu.initActivity
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.navBarHeight
|
||||
|
@ -37,20 +28,11 @@ import ani.dantotsu.themes.ThemeManager
|
|||
import ani.dantotsu.util.customAlertDialog
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Locale
|
||||
|
||||
class ExtensionsActivity : AppCompatActivity() {
|
||||
lateinit var binding: ActivityExtensionsBinding
|
||||
|
||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
||||
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -124,6 +106,9 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||
if (tab.text?.contains("Manga") == true) {
|
||||
generateRepositoryButton(MediaType.MANGA)
|
||||
}
|
||||
if (tab.text?.contains("Novels") == true) {
|
||||
generateRepositoryButton(MediaType.NOVEL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||
|
@ -199,136 +184,28 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun processUserInput(input: String, mediaType: MediaType) {
|
||||
val entry = if (input.endsWith("/") || input.endsWith("index.min.json"))
|
||||
input.substring(0, input.lastIndexOf("/")) else input
|
||||
if (mediaType == MediaType.ANIME) {
|
||||
val anime =
|
||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).plus(entry)
|
||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
animeExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
if (mediaType == MediaType.MANGA) {
|
||||
val manga =
|
||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).plus(entry)
|
||||
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
mangaExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSavedRepositories(repoInventory: ViewGroup, type: MediaType) {
|
||||
repoInventory.removeAllViews()
|
||||
val prefName: PrefName? = when (type) {
|
||||
MediaType.ANIME -> {
|
||||
PrefName.AnimeExtensionRepos
|
||||
}
|
||||
|
||||
MediaType.MANGA -> {
|
||||
PrefName.MangaExtensionRepos
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
prefName?.let { repoList ->
|
||||
PrefManager.getVal<Set<String>>(repoList).forEach { item ->
|
||||
val view = ItemRepositoryBinding.inflate(
|
||||
LayoutInflater.from(repoInventory.context), repoInventory, true
|
||||
)
|
||||
view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com")
|
||||
view.repositoryItem.setOnClickListener {
|
||||
customAlertDialog().apply {
|
||||
setTitle(R.string.rem_repository)
|
||||
setMessage(item)
|
||||
setPosButton(R.string.ok) {
|
||||
val repos = PrefManager.getVal<Set<String>>(prefName).minus(item)
|
||||
PrefManager.setVal(prefName, repos)
|
||||
repoInventory.removeView(view.root)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
when (type) {
|
||||
MediaType.ANIME -> {
|
||||
animeExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
|
||||
MediaType.MANGA -> {
|
||||
mangaExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
setNegButton(R.string.cancel)
|
||||
show()
|
||||
}
|
||||
}
|
||||
view.repositoryItem.setOnLongClickListener {
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
copyToClipboard(item, true)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processEditorAction(editText: EditText, mediaType: MediaType) {
|
||||
editText.setOnEditorActionListener { textView, action, keyEvent ->
|
||||
if (action == EditorInfo.IME_ACTION_SEARCH || action == EditorInfo.IME_ACTION_DONE ||
|
||||
(keyEvent?.action == KeyEvent.ACTION_UP
|
||||
&& keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)
|
||||
) {
|
||||
return@setOnEditorActionListener if (textView.text.isNullOrBlank()) {
|
||||
false
|
||||
} else {
|
||||
processUserInput(textView.text.toString(), mediaType)
|
||||
true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRepositoryButton(type: MediaType) {
|
||||
val hintResource: Int? = when (type) {
|
||||
MediaType.ANIME -> {
|
||||
R.string.anime_add_repository
|
||||
}
|
||||
|
||||
MediaType.MANGA -> {
|
||||
R.string.manga_add_repository
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
hintResource?.let { res ->
|
||||
binding.openSettingsButton.setOnClickListener {
|
||||
val dialogView = DialogRepositoriesBinding.inflate(
|
||||
LayoutInflater.from(binding.openSettingsButton.context), null, false
|
||||
)
|
||||
dialogView.repositoryTextBox.hint = getString(res)
|
||||
dialogView.repoInventory.apply {
|
||||
getSavedRepositories(this, type)
|
||||
binding.openSettingsButton.setOnClickListener {
|
||||
val repos: Set<String> = when (type) {
|
||||
MediaType.ANIME -> {
|
||||
PrefManager.getVal(PrefName.AnimeExtensionRepos)
|
||||
}
|
||||
processEditorAction(dialogView.repositoryTextBox, type)
|
||||
customAlertDialog().apply {
|
||||
setTitle(R.string.edit_repositories)
|
||||
setCustomView(dialogView.root)
|
||||
setPosButton(R.string.add_list) {
|
||||
if (!dialogView.repositoryTextBox.text.isNullOrBlank()) {
|
||||
processUserInput(dialogView.repositoryTextBox.text.toString(), type)
|
||||
}
|
||||
}
|
||||
setNegButton(R.string.close)
|
||||
show()
|
||||
|
||||
MediaType.MANGA -> {
|
||||
PrefManager.getVal(PrefName.MangaExtensionRepos)
|
||||
}
|
||||
|
||||
MediaType.NOVEL -> {
|
||||
PrefManager.getVal(PrefName.NovelExtensionRepos)
|
||||
}
|
||||
}
|
||||
AddRepositoryBottomSheet.newInstance(
|
||||
type,
|
||||
repos.toList(),
|
||||
AddRepositoryBottomSheet::addRepo,
|
||||
AddRepositoryBottomSheet::removeRepo
|
||||
|
||||
).show(supportFragmentManager, "add_repo")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package ani.dantotsu.settings
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -32,9 +28,6 @@ import ani.dantotsu.util.customAlertDialog
|
|||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
@ -42,8 +35,7 @@ import uy.kohesive.injekt.injectLazy
|
|||
class SettingsExtensionsActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivitySettingsExtensionsBinding
|
||||
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
||||
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ThemeManager(this).applyTheme()
|
||||
|
@ -61,7 +53,7 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
}
|
||||
fun setExtensionOutput(repoInventory: ViewGroup, type: MediaType) {
|
||||
repoInventory.removeAllViews()
|
||||
val prefName: PrefName? = when (type) {
|
||||
val prefName: PrefName = when (type) {
|
||||
MediaType.ANIME -> {
|
||||
PrefName.AnimeExtensionRepos
|
||||
}
|
||||
|
@ -70,74 +62,24 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
PrefName.MangaExtensionRepos
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
MediaType.NOVEL -> {
|
||||
PrefName.NovelExtensionRepos
|
||||
}
|
||||
}
|
||||
prefName?.let { repoList ->
|
||||
PrefManager.getVal<Set<String>>(repoList).forEach { item ->
|
||||
val view = ItemRepositoryBinding.inflate(
|
||||
LayoutInflater.from(repoInventory.context), repoInventory, true
|
||||
)
|
||||
view.repositoryItem.text =
|
||||
item.removePrefix("https://raw.githubusercontent.com/")
|
||||
view.repositoryItem.setOnClickListener {
|
||||
context.customAlertDialog().apply {
|
||||
setTitle(R.string.rem_repository)
|
||||
setMessage(item)
|
||||
setPosButton(R.string.ok) {
|
||||
val repos = PrefManager.getVal<Set<String>>(repoList).minus(item)
|
||||
PrefManager.setVal(repoList, repos)
|
||||
setExtensionOutput(repoInventory, type)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
when (type) {
|
||||
MediaType.ANIME -> {
|
||||
animeExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
MediaType.MANGA -> {
|
||||
mangaExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
setNegButton(R.string.cancel)
|
||||
show()
|
||||
}
|
||||
}
|
||||
view.repositoryItem.setOnLongClickListener {
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
copyToClipboard(item, true)
|
||||
true
|
||||
}
|
||||
}
|
||||
repoInventory.isVisible = repoInventory.childCount > 0
|
||||
}
|
||||
}
|
||||
PrefManager.getVal<Set<String>>(prefName).forEach { item ->
|
||||
val view = ItemRepositoryBinding.inflate(
|
||||
LayoutInflater.from(repoInventory.context), repoInventory, true
|
||||
)
|
||||
view.repositoryItem.text =
|
||||
item.removePrefix("https://raw.githubusercontent.com/")
|
||||
|
||||
fun processUserInput(input: String, mediaType: MediaType, view: ViewGroup) {
|
||||
val validLink = if (input.contains("github.com") && input.contains("blob")) {
|
||||
input.replace("github.com", "raw.githubusercontent.com")
|
||||
.replace("/blob/", "/")
|
||||
} else input
|
||||
if (mediaType == MediaType.ANIME) {
|
||||
val anime =
|
||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).plus(validLink)
|
||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
animeExtensionManager.findAvailableExtensions()
|
||||
view.repositoryItem.setOnLongClickListener {
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
copyToClipboard(item, true)
|
||||
true
|
||||
}
|
||||
setExtensionOutput(view, MediaType.ANIME)
|
||||
}
|
||||
if (mediaType == MediaType.MANGA) {
|
||||
val manga =
|
||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).plus(validLink)
|
||||
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
mangaExtensionManager.findAvailableExtensions()
|
||||
}
|
||||
setExtensionOutput(view, MediaType.MANGA)
|
||||
}
|
||||
repoInventory.isVisible = repoInventory.childCount > 0
|
||||
}
|
||||
|
||||
settingsRecyclerView.adapter = SettingsAdapter(
|
||||
|
@ -148,17 +90,18 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
desc = getString(R.string.anime_add_repository_desc),
|
||||
icon = R.drawable.ic_github,
|
||||
onClick = {
|
||||
val animeRepos = PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||
val animeRepos =
|
||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos)
|
||||
AddRepositoryBottomSheet.newInstance(
|
||||
MediaType.ANIME,
|
||||
animeRepos.toList(),
|
||||
onRepositoryAdded = { input, mediaType ->
|
||||
processUserInput(input, mediaType, it.attachView)
|
||||
AddRepositoryBottomSheet.addRepo(input, mediaType)
|
||||
setExtensionOutput(it.attachView, mediaType)
|
||||
},
|
||||
onRepositoryRemoved = { item ->
|
||||
val repos = PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).minus(item)
|
||||
PrefManager.setVal(PrefName.AnimeExtensionRepos, repos)
|
||||
setExtensionOutput(it.attachView, MediaType.ANIME)
|
||||
onRepositoryRemoved = { item, mediaType ->
|
||||
AddRepositoryBottomSheet.removeRepo(item, mediaType)
|
||||
setExtensionOutput(it.attachView, mediaType)
|
||||
}
|
||||
).show(supportFragmentManager, "add_repo")
|
||||
},
|
||||
|
@ -172,17 +115,18 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
desc = getString(R.string.manga_add_repository_desc),
|
||||
icon = R.drawable.ic_github,
|
||||
onClick = {
|
||||
val mangaRepos = PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||
val mangaRepos =
|
||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
|
||||
AddRepositoryBottomSheet.newInstance(
|
||||
MediaType.MANGA,
|
||||
mangaRepos.toList(),
|
||||
onRepositoryAdded = { input, mediaType ->
|
||||
processUserInput(input, mediaType, it.attachView)
|
||||
AddRepositoryBottomSheet.addRepo(input, mediaType)
|
||||
setExtensionOutput(it.attachView, mediaType)
|
||||
},
|
||||
onRepositoryRemoved = { item ->
|
||||
val repos = PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).minus(item)
|
||||
PrefManager.setVal(PrefName.MangaExtensionRepos, repos)
|
||||
setExtensionOutput(it.attachView, MediaType.MANGA)
|
||||
onRepositoryRemoved = { item, mediaType ->
|
||||
AddRepositoryBottomSheet.removeRepo(item, mediaType)
|
||||
setExtensionOutput(it.attachView, mediaType)
|
||||
}
|
||||
).show(supportFragmentManager, "add_repo")
|
||||
},
|
||||
|
@ -190,6 +134,31 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
setExtensionOutput(it.attachView, MediaType.MANGA)
|
||||
}
|
||||
),
|
||||
Settings(
|
||||
type = 1,
|
||||
name = getString(R.string.novel_add_repository),
|
||||
desc = getString(R.string.novel_add_repository_desc),
|
||||
icon = R.drawable.ic_github,
|
||||
onClick = {
|
||||
val novelRepos =
|
||||
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
|
||||
AddRepositoryBottomSheet.newInstance(
|
||||
MediaType.NOVEL,
|
||||
novelRepos.toList(),
|
||||
onRepositoryAdded = { input, mediaType ->
|
||||
AddRepositoryBottomSheet.addRepo(input, mediaType)
|
||||
setExtensionOutput(it.attachView, mediaType)
|
||||
},
|
||||
onRepositoryRemoved = { item, mediaType ->
|
||||
AddRepositoryBottomSheet.removeRepo(item, mediaType)
|
||||
setExtensionOutput(it.attachView, mediaType)
|
||||
}
|
||||
).show(supportFragmentManager, "add_repo")
|
||||
},
|
||||
attach = {
|
||||
setExtensionOutput(it.attachView, MediaType.NOVEL)
|
||||
}
|
||||
),
|
||||
Settings(
|
||||
type = 1,
|
||||
name = getString(R.string.extension_test),
|
||||
|
@ -217,7 +186,10 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
setTitle(R.string.user_agent)
|
||||
setCustomView(dialogView.root)
|
||||
setPosButton(R.string.ok) {
|
||||
PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString())
|
||||
PrefManager.setVal(
|
||||
PrefName.DefaultUserAgent,
|
||||
editText.text.toString()
|
||||
)
|
||||
}
|
||||
setNeutralButton(R.string.reset) {
|
||||
PrefManager.removeVal(PrefName.DefaultUserAgent)
|
||||
|
@ -247,7 +219,7 @@ class SettingsExtensionsActivity : AppCompatActivity() {
|
|||
ProxyDialogFragment().show(supportFragmentManager, "dialog")
|
||||
}
|
||||
),
|
||||
Settings(
|
||||
Settings(
|
||||
type = 2,
|
||||
name = getString(R.string.force_legacy_installer),
|
||||
desc = getString(R.string.force_legacy_installer_desc),
|
||||
|
|
|
@ -32,6 +32,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
|
|||
),
|
||||
AnimeExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
||||
MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
||||
NovelExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
|
||||
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
||||
AnimeSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
|
||||
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import ani.dantotsu.parsers.novel.AvailableNovelSources
|
||||
import ani.dantotsu.parsers.novel.NovelExtension
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.util.Logger
|
||||
|
@ -192,6 +194,92 @@ internal class ExtensionGithubApi {
|
|||
return "${extension.repository}/apk/${extension.apkName}"
|
||||
}
|
||||
|
||||
suspend fun findNovelExtensions(): List<NovelExtension.Available> {
|
||||
return withIOContext {
|
||||
|
||||
val extensions: ArrayList<NovelExtension.Available> = arrayListOf()
|
||||
|
||||
val repos =
|
||||
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos).toMutableList()
|
||||
|
||||
repos.forEach {
|
||||
val repoUrl = if (it.contains("index.min.json")) {
|
||||
it
|
||||
} else {
|
||||
"$it${if (it.endsWith('/')) "" else "/"}index.min.json"
|
||||
}
|
||||
try {
|
||||
val githubResponse = try {
|
||||
networkService.client
|
||||
.newCall(GET(repoUrl))
|
||||
.awaitSuccess()
|
||||
} catch (e: Throwable) {
|
||||
Logger.log("Failed to get repo: $repoUrl")
|
||||
Logger.log(e)
|
||||
null
|
||||
}
|
||||
|
||||
val response = githubResponse ?: run {
|
||||
networkService.client
|
||||
.newCall(GET(fallbackRepoUrl(it) + "/index.min.json"))
|
||||
.awaitSuccess()
|
||||
}
|
||||
|
||||
val repoExtensions = with(json) {
|
||||
response
|
||||
.parseAs<List<ExtensionJsonObject>>()
|
||||
.toNovelExtensions(it)
|
||||
}
|
||||
|
||||
extensions.addAll(repoExtensions)
|
||||
} catch (e: Throwable) {
|
||||
Logger.log("Failed to get extensions from GitHub")
|
||||
Logger.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
extensions
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<ExtensionJsonObject>.toNovelExtensions(repository: String): List<NovelExtension.Available> {
|
||||
return mapNotNull { extension ->
|
||||
val sources = extension.sources?.map { source ->
|
||||
ExtensionSourceJsonObject(
|
||||
source.id,
|
||||
source.lang,
|
||||
source.name,
|
||||
source.baseUrl,
|
||||
)
|
||||
}
|
||||
val iconUrl = "${repository.removeSuffix("/index.min.json")}/icon/${extension.pkg}.png"
|
||||
NovelExtension.Available(
|
||||
extension.name,
|
||||
extension.pkg,
|
||||
extension.apk,
|
||||
extension.code,
|
||||
repository,
|
||||
sources?.toNovelSources() ?: emptyList(),
|
||||
iconUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<ExtensionSourceJsonObject>.toNovelSources(): List<AvailableNovelSources> {
|
||||
return map { source ->
|
||||
AvailableNovelSources(
|
||||
source.id,
|
||||
source.lang,
|
||||
source.name,
|
||||
source.baseUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getNovelApkUrl(extension: NovelExtension.Available): String {
|
||||
return "${extension.repository}/apk/${extension.pkgName}.apk"
|
||||
}
|
||||
|
||||
private fun fallbackRepoUrl(repoUrl: String): String? {
|
||||
var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/"
|
||||
val strippedRepoUrl = repoUrl
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="3dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
|
@ -32,4 +34,18 @@
|
|||
app:tint="?attr/colorOnBackground"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/repoCopyImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="3dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:srcCompat="@drawable/format_link_24"
|
||||
app:tint="?attr/colorOnBackground"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</LinearLayout>
|
|
@ -896,6 +896,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
|||
|
||||
<string name="anime_add_repository">Add Anime Repo</string>
|
||||
<string name="manga_add_repository">Add Manga Repo</string>
|
||||
<string name="novel_add_repository">Add Novel Repo</string>
|
||||
<string name="edit_repositories">Edit repositories</string>
|
||||
<string name="rem_repository">Remove repository?</string>
|
||||
|
||||
|
@ -963,6 +964,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
|||
<string name="adult_only_content_desc">Show only adult content in the explore page</string>
|
||||
<string name="anime_add_repository_desc">Add Anime Extensions from various sources</string>
|
||||
<string name="manga_add_repository_desc">Add Manga Extensions from various sources</string>
|
||||
<string name="novel_add_repository_desc">Add Novel Extensions from various sources</string>
|
||||
<string name="user_agent_desc">Change your default user agent</string>
|
||||
<string name="force_legacy_installer_desc">Use the legacy installer to install extensions (For older android phones)</string>
|
||||
<string name="skip_loading_extension_icons_desc">Don\'t load icons of extensions on the extension page</string>
|
||||
|
@ -1089,7 +1091,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
|||
<string name="textview_sub_stroke">Subtitle Stroke</string>
|
||||
<string name="textview_sub_bottom_margin">Bottom Margin</string>
|
||||
<string name="add_repository">Add Repository</string>
|
||||
<string name="add_repository_desc">A repository link should look like this: https://raw.githubusercontent.com/username/repo/branch/index.min.json</string>
|
||||
<string name="add_repository_desc">A repository link should look like this: https://raw.githubusercontent.com/username/repo/branch/index.min.json\nOr: username/repo/branch</string>
|
||||
<string name="current_repositories">Current Repositories</string>
|
||||
<string name="add_repository_warning">Warning: Extensions from the repository can run arbitrary code on your device. Only use repositories you trust. \n\nBy adding a repository, you agree to: \n\n1. Not use the app for viewing or distributing copyrighted content. \n2. Not use the app for any illegal activities. \n3. Not use the app for any activities that violate the terms of service of the content providers. \n\nThe app or it\'s maintainer are not affiliated in any way with extension providers. The developers are not responsible for any damages caused by the app. \n\nBy adding a repository, you agree to these terms.</string>
|
||||
<string name="privacy_policy">Privacy Policy</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue