feat: allow partial urls
This commit is contained in:
parent
116de6324e
commit
38d68a7976
13 changed files with 360 additions and 463 deletions
|
@ -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>())),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue