From 43dee6ee497f984a4ab5336de4c80173de3aa48b Mon Sep 17 00:00:00 2001
From: rebel onion <87634197+rebelonion@users.noreply.github.com>
Date: Mon, 30 Dec 2024 19:25:22 -0600
Subject: [PATCH] feat: make repo adding easier
---
.gitignore | 3 +
app/src/main/AndroidManifest.xml | 17 ++-
app/src/main/java/ani/dantotsu/Functions.kt | 41 +-----
.../main/java/ani/dantotsu/MainActivity.kt | 121 ++++++++-------
.../connections/bakaupdates/MangaUpdates.kt | 133 -----------------
.../settings/AddRepositoryBottomSheet.kt | 138 ++++++++++++++++++
.../settings/SettingsExtensionsActivity.kt | 105 ++++---------
.../java/ani/dantotsu/util/CountUpTimer.kt | 22 ---
.../extension/api/ExtensionGithubApi.kt | 30 ++--
.../layout/bottom_sheet_add_repository.xml | 79 ++++++++++
app/src/main/res/layout/item_repo.xml | 35 +++++
app/src/main/res/values/strings.xml | 3 +
build.gradle | 2 +-
13 files changed, 383 insertions(+), 346 deletions(-)
delete mode 100644 app/src/main/java/ani/dantotsu/connections/bakaupdates/MangaUpdates.kt
create mode 100644 app/src/main/java/ani/dantotsu/settings/AddRepositoryBottomSheet.kt
delete mode 100644 app/src/main/java/ani/dantotsu/util/CountUpTimer.kt
create mode 100644 app/src/main/res/layout/bottom_sheet_add_repository.xml
create mode 100644 app/src/main/res/layout/item_repo.xml
diff --git a/.gitignore b/.gitignore
index 9900c88788..0fc4c86f69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
.gradle/
build/
+#kotlin
+.kotlin/
+
# Local configuration file (sdk path, etc)
local.properties
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1d8d4d6a35..644d218a8b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -116,7 +116,8 @@
-
+
+
@@ -374,25 +375,31 @@
android:exported="true">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
countDown(media, view)
- media.format == "MANGA" || media.format == "ONE_SHOT" -> sinceWhen(media, view)
- else -> {} // No timer yet
+ else -> {}
}
}
diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt
index 2e87a7a5df..1020aeb324 100644
--- a/app/src/main/java/ani/dantotsu/MainActivity.kt
+++ b/app/src/main/java/ani/dantotsu/MainActivity.kt
@@ -116,58 +116,8 @@ class MainActivity : AppCompatActivity() {
}
}
- val action = intent.action
- val type = intent.type
- if (Intent.ACTION_VIEW == action && type != null) {
- val uri: Uri? = intent.data
- try {
- if (uri == null) {
- throw Exception("Uri is null")
- }
- val jsonString =
- contentResolver.openInputStream(uri)?.readBytes()
- ?: throw Exception("Error reading file")
- val name =
- DocumentFile.fromSingleUri(this, uri)?.name ?: "settings"
- //.sani is encrypted, .ani is not
- if (name.endsWith(".sani")) {
- passwordAlertDialog { password ->
- if (password != null) {
- val salt = jsonString.copyOfRange(0, 16)
- val encrypted = jsonString.copyOfRange(16, jsonString.size)
- val decryptedJson = try {
- PreferenceKeystore.decryptWithPassword(
- password,
- encrypted,
- salt
- )
- } catch (e: Exception) {
- toast("Incorrect password")
- return@passwordAlertDialog
- }
- if (PreferencePackager.unpack(decryptedJson)) {
- val intent = Intent(this, this.javaClass)
- this.finish()
- startActivity(intent)
- }
- } else {
- toast("Password cannot be empty")
- }
- }
- } else if (name.endsWith(".ani")) {
- val decryptedJson = jsonString.toString(Charsets.UTF_8)
- if (PreferencePackager.unpack(decryptedJson)) {
- val intent = Intent(this, this.javaClass)
- this.finish()
- startActivity(intent)
- }
- } else {
- toast("Invalid file type")
- }
- } catch (e: Exception) {
- e.printStackTrace()
- toast("Error importing settings")
- }
+ if (Intent.ACTION_VIEW == intent.action) {
+ handleViewIntent(intent)
}
val bottomNavBar = findViewById(R.id.navbar)
@@ -492,6 +442,73 @@ class MainActivity : AppCompatActivity() {
params.updateMargins(bottom = margin.toPx)
}
+ private fun handleViewIntent(intent: Intent) {
+ val uri: Uri? = intent.data
+ try {
+ if (uri == null) {
+ throw Exception("Uri is null")
+ }
+ if ((uri.scheme == "tachiyomi" || uri.scheme == "aniyomi") && 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 savedRepos: Set = PrefManager.getVal(prefName)
+ val newRepos = savedRepos.toMutableSet()
+ newRepos.add(url)
+ PrefManager.setVal(prefName, newRepos)
+ toast("${if (uri.scheme == "tachiyomi") "Manga" else "Anime"} Extension Repo added")
+ return
+ }
+ if (intent.type == null) return
+ val jsonString =
+ contentResolver.openInputStream(uri)?.readBytes()
+ ?: throw Exception("Error reading file")
+ val name =
+ DocumentFile.fromSingleUri(this, uri)?.name ?: "settings"
+ //.sani is encrypted, .ani is not
+ if (name.endsWith(".sani")) {
+ passwordAlertDialog { password ->
+ if (password != null) {
+ val salt = jsonString.copyOfRange(0, 16)
+ val encrypted = jsonString.copyOfRange(16, jsonString.size)
+ val decryptedJson = try {
+ PreferenceKeystore.decryptWithPassword(
+ password,
+ encrypted,
+ salt
+ )
+ } catch (e: Exception) {
+ toast("Incorrect password")
+ return@passwordAlertDialog
+ }
+ if (PreferencePackager.unpack(decryptedJson)) {
+ val intent = Intent(this, this.javaClass)
+ this.finish()
+ startActivity(intent)
+ }
+ } else {
+ toast("Password cannot be empty")
+ }
+ }
+ } else if (name.endsWith(".ani")) {
+ val decryptedJson = jsonString.toString(Charsets.UTF_8)
+ if (PreferencePackager.unpack(decryptedJson)) {
+ val intent = Intent(this, this.javaClass)
+ this.finish()
+ startActivity(intent)
+ }
+ } else {
+ toast("Invalid file type")
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ toast("Error importing settings")
+ }
+ }
+
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
val password = CharArray(16).apply { fill('0') }
diff --git a/app/src/main/java/ani/dantotsu/connections/bakaupdates/MangaUpdates.kt b/app/src/main/java/ani/dantotsu/connections/bakaupdates/MangaUpdates.kt
deleted file mode 100644
index 53a88815ac..0000000000
--- a/app/src/main/java/ani/dantotsu/connections/bakaupdates/MangaUpdates.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-package ani.dantotsu.connections.bakaupdates
-
-import android.content.Context
-import ani.dantotsu.R
-import ani.dantotsu.client
-import ani.dantotsu.connections.anilist.api.FuzzyDate
-import ani.dantotsu.tryWithSuspend
-import ani.dantotsu.util.Logger
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import okio.ByteString.Companion.encode
-import org.json.JSONException
-import org.json.JSONObject
-import java.nio.charset.Charset
-
-
-class MangaUpdates {
-
- private val Int?.dateFormat get() = String.format("%02d", this)
-
- private val apiUrl = "https://api.mangaupdates.com/v1/releases/search"
-
- suspend fun search(title: String, startDate: FuzzyDate?): MangaUpdatesResponse.Results? {
- return tryWithSuspend {
- val query = JSONObject().apply {
- try {
- put("search", title.encode(Charset.forName("UTF-8")))
- startDate?.let {
- put(
- "start_date",
- "${it.year}-${it.month.dateFormat}-${it.day.dateFormat}"
- )
- }
- put("include_metadata", true)
- } catch (e: JSONException) {
- e.printStackTrace()
- }
- }
- val res = try {
- client.post(apiUrl, json = query).parsed()
- } catch (e: Exception) {
- Logger.log(e.toString())
- return@tryWithSuspend null
- }
- coroutineScope {
- res.results?.map {
- async(Dispatchers.IO) {
- Logger.log(it.toString())
- }
- }
- }?.awaitAll()
- res.results?.first {
- it.metadata.series.lastUpdated?.timestamp != null
- && (it.metadata.series.latestChapter != null
- || (it.record.volume.isNullOrBlank() && it.record.chapter != null))
- }
- }
- }
-
- companion object {
- fun getLatestChapter(context: Context, results: MangaUpdatesResponse.Results): String {
- return results.metadata.series.latestChapter?.let {
- context.getString(R.string.chapter_number, it)
- } ?: results.record.chapter!!.substringAfterLast("-").trim().let { chapter ->
- chapter.takeIf {
- it.toIntOrNull() == null
- } ?: context.getString(R.string.chapter_number, chapter.toInt())
- }
- }
- }
-
- @Serializable
- data class MangaUpdatesResponse(
- @SerialName("total_hits")
- val totalHits: Int?,
- @SerialName("page")
- val page: Int?,
- @SerialName("per_page")
- val perPage: Int?,
- val results: List? = null
- ) {
- @Serializable
- data class Results(
- val record: Record,
- val metadata: MetaData
- ) {
- @Serializable
- data class Record(
- @SerialName("id")
- val id: Int,
- @SerialName("title")
- val title: String,
- @SerialName("volume")
- val volume: String?,
- @SerialName("chapter")
- val chapter: String?,
- @SerialName("release_date")
- val releaseDate: String
- )
-
- @Serializable
- data class MetaData(
- val series: Series
- ) {
- @Serializable
- data class Series(
- @SerialName("series_id")
- val seriesId: Long?,
- @SerialName("title")
- val title: String?,
- @SerialName("latest_chapter")
- val latestChapter: Int?,
- @SerialName("last_updated")
- val lastUpdated: LastUpdated?
- ) {
- @Serializable
- data class LastUpdated(
- @SerialName("timestamp")
- val timestamp: Long,
- @SerialName("as_rfc3339")
- val asRfc3339: String,
- @SerialName("as_string")
- val asString: String
- )
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/ani/dantotsu/settings/AddRepositoryBottomSheet.kt b/app/src/main/java/ani/dantotsu/settings/AddRepositoryBottomSheet.kt
new file mode 100644
index 0000000000..dc1cf5d1a6
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/settings/AddRepositoryBottomSheet.kt
@@ -0,0 +1,138 @@
+package ani.dantotsu.settings
+
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import androidx.recyclerview.widget.LinearLayoutManager
+import ani.dantotsu.BottomSheetDialogFragment
+import ani.dantotsu.R
+import ani.dantotsu.databinding.BottomSheetAddRepositoryBinding
+import ani.dantotsu.databinding.ItemRepoBinding
+import ani.dantotsu.media.MediaType
+import com.xwray.groupie.GroupieAdapter
+import com.xwray.groupie.viewbinding.BindableItem
+
+class RepoItem(
+ val url: String,
+ val onRemove: (String) -> Unit
+) :BindableItem() {
+ override fun getLayout() = R.layout.item_repo
+
+ override fun bind(viewBinding: ItemRepoBinding, position: Int) {
+ viewBinding.repoNameTextView.text = url
+ viewBinding.repoDeleteImageView.setOnClickListener {
+ onRemove(url)
+ }
+ }
+
+ override fun initializeViewBinding(view: View): ItemRepoBinding {
+ return ItemRepoBinding.bind(view)
+ }
+}
+
+class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
+ private var _binding: BottomSheetAddRepositoryBinding? = null
+ private val binding get() = _binding!!
+ private var mediaType: MediaType = MediaType.ANIME
+ private var onRepositoryAdded: ((String, MediaType) -> Unit)? = null
+ private var repositories: MutableList = mutableListOf()
+ private var onRepositoryRemoved: ((String) -> Unit)? = null
+ private var adapter: GroupieAdapter = GroupieAdapter()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = BottomSheetAddRepositoryBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.repositoriesRecyclerView.adapter = adapter
+ binding.repositoriesRecyclerView.layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ adapter.addAll(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
+
+ binding.repositoryInput.hint = when(mediaType) {
+ MediaType.ANIME -> getString(R.string.anime_add_repository)
+ MediaType.MANGA -> getString(R.string.manga_add_repository)
+ else -> ""
+ }
+
+ binding.addButton.setOnClickListener {
+ val input = binding.repositoryInput.text.toString()
+ val error = isValidUrl(input)
+ if (error == null) {
+ onRepositoryAdded?.invoke(input, mediaType)
+ dismiss()
+ } else {
+ binding.repositoryInput.error = error
+ }
+ }
+
+ binding.cancelButton.setOnClickListener {
+ dismiss()
+ }
+
+ binding.repositoryInput.setOnEditorActionListener { textView, action, keyEvent ->
+ if (action == EditorInfo.IME_ACTION_DONE ||
+ (keyEvent?.action == KeyEvent.ACTION_UP && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)) {
+ if (!textView.text.isNullOrBlank()) {
+ val error = isValidUrl(textView.text.toString())
+ if (error == null) {
+ onRepositoryAdded?.invoke(textView.text.toString(), mediaType)
+ dismiss()
+ return@setOnEditorActionListener true
+ } else {
+ binding.repositoryInput.error = error
+ }
+ }
+ }
+ false
+ }
+ }
+
+ private fun onRepositoryRemoved(url: String) {
+ onRepositoryRemoved?.invoke(url)
+ repositories.remove(url)
+ adapter.update(repositories.map { RepoItem(it, ::onRepositoryRemoved) })
+ }
+
+ 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"
+ return null
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ companion object {
+ fun newInstance(
+ mediaType: MediaType,
+ repositories: List,
+ onRepositoryAdded: (String, MediaType) -> Unit,
+ onRepositoryRemoved: (String) -> Unit
+ ): AddRepositoryBottomSheet {
+ return AddRepositoryBottomSheet().apply {
+ this.mediaType = mediaType
+ this.repositories.addAll(repositories)
+ this.onRepositoryAdded = onRepositoryAdded
+ this.onRepositoryRemoved = onRepositoryRemoved
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt
index 4388231a21..59add57808 100644
--- a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt
@@ -27,7 +27,6 @@ import ani.dantotsu.restartApp
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
-import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.customAlertDialog
import eu.kanade.domain.base.BasePreferences
@@ -117,14 +116,13 @@ class SettingsExtensionsActivity : AppCompatActivity() {
}
fun processUserInput(input: String, mediaType: MediaType, view: ViewGroup) {
- val entry =
- if (input.endsWith("/") || input.endsWith("index.min.json")) input.substring(
- 0,
- input.lastIndexOf("/")
- ) else input
+ 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>(PrefName.AnimeExtensionRepos).plus(entry)
+ PrefManager.getVal>(PrefName.AnimeExtensionRepos).plus(validLink)
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
CoroutineScope(Dispatchers.IO).launch {
animeExtensionManager.findAvailableExtensions()
@@ -133,7 +131,7 @@ class SettingsExtensionsActivity : AppCompatActivity() {
}
if (mediaType == MediaType.MANGA) {
val manga =
- PrefManager.getVal>(PrefName.MangaExtensionRepos).plus(entry)
+ PrefManager.getVal>(PrefName.MangaExtensionRepos).plus(validLink)
PrefManager.setVal(PrefName.MangaExtensionRepos, manga)
CoroutineScope(Dispatchers.IO).launch {
mangaExtensionManager.findAvailableExtensions()
@@ -142,25 +140,6 @@ class SettingsExtensionsActivity : AppCompatActivity() {
}
}
- fun processEditorAction(
- dialog: AlertDialog,
- editText: EditText,
- mediaType: MediaType,
- view: ViewGroup
- ) {
- 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, view)
- dialog.dismiss()
- true
- }
- }
- false
- }
- }
settingsRecyclerView.adapter = SettingsAdapter(
arrayListOf(
Settings(
@@ -169,31 +148,19 @@ class SettingsExtensionsActivity : AppCompatActivity() {
desc = getString(R.string.anime_add_repository_desc),
icon = R.drawable.ic_github,
onClick = {
- val dialogView = DialogUserAgentBinding.inflate(layoutInflater)
- val editText = dialogView.userAgentTextBox.apply {
- hint = getString(R.string.anime_add_repository)
- }
- context.customAlertDialog().apply {
- setTitle(R.string.anime_add_repository)
- setCustomView(dialogView.root)
- setPosButton(getString(R.string.ok)) {
- if (!editText.text.isNullOrBlank()) processUserInput(
- editText.text.toString(),
- MediaType.ANIME,
- it.attachView
- )
+ val animeRepos = PrefManager.getVal>(PrefName.AnimeExtensionRepos)
+ AddRepositoryBottomSheet.newInstance(
+ MediaType.ANIME,
+ animeRepos.toList(),
+ onRepositoryAdded = { input, mediaType ->
+ processUserInput(input, mediaType, it.attachView)
+ },
+ onRepositoryRemoved = { item ->
+ val repos = PrefManager.getVal>(PrefName.AnimeExtensionRepos).minus(item)
+ PrefManager.setVal(PrefName.AnimeExtensionRepos, repos)
+ setExtensionOutput(it.attachView, MediaType.ANIME)
}
- setNegButton(getString(R.string.cancel))
- attach { dialog ->
- processEditorAction(
- dialog,
- editText,
- MediaType.ANIME,
- it.attachView
- )
- }
- show()
- }
+ ).show(supportFragmentManager, "add_repo")
},
attach = {
setExtensionOutput(it.attachView, MediaType.ANIME)
@@ -205,31 +172,19 @@ class SettingsExtensionsActivity : AppCompatActivity() {
desc = getString(R.string.manga_add_repository_desc),
icon = R.drawable.ic_github,
onClick = {
- val dialogView = DialogUserAgentBinding.inflate(layoutInflater)
- val editText = dialogView.userAgentTextBox.apply {
- hint = getString(R.string.manga_add_repository)
- }
- context.customAlertDialog().apply {
- setTitle(R.string.manga_add_repository)
- setCustomView(dialogView.root)
- setPosButton(R.string.ok) {
- if (!editText.text.isNullOrBlank()) processUserInput(
- editText.text.toString(),
- MediaType.MANGA,
- it.attachView
- )
- }
- setNegButton(R.string.cancel)
- attach { dialog ->
- processEditorAction(
- dialog,
- editText,
- MediaType.MANGA,
- it.attachView
- )
+ val mangaRepos = PrefManager.getVal>(PrefName.MangaExtensionRepos)
+ AddRepositoryBottomSheet.newInstance(
+ MediaType.MANGA,
+ mangaRepos.toList(),
+ onRepositoryAdded = { input, mediaType ->
+ processUserInput(input, mediaType, it.attachView)
+ },
+ onRepositoryRemoved = { item ->
+ val repos = PrefManager.getVal>(PrefName.MangaExtensionRepos).minus(item)
+ PrefManager.setVal(PrefName.MangaExtensionRepos, repos)
+ setExtensionOutput(it.attachView, MediaType.MANGA)
}
- }.show()
-
+ ).show(supportFragmentManager, "add_repo")
},
attach = {
setExtensionOutput(it.attachView, MediaType.MANGA)
diff --git a/app/src/main/java/ani/dantotsu/util/CountUpTimer.kt b/app/src/main/java/ani/dantotsu/util/CountUpTimer.kt
deleted file mode 100644
index 725781ff95..0000000000
--- a/app/src/main/java/ani/dantotsu/util/CountUpTimer.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package ani.dantotsu.util
-
-import android.os.CountDownTimer
-
-// https://stackoverflow.com/a/40422151/461982
-abstract class CountUpTimer protected constructor(
- private val duration: Long
-) : CountDownTimer(duration, INTERVAL_MS) {
- abstract fun onTick(second: Int)
- override fun onTick(msUntilFinished: Long) {
- val second = ((duration - msUntilFinished) / 1000).toInt()
- onTick(second)
- }
-
- override fun onFinish() {
- onTick(duration / 1000)
- }
-
- companion object {
- private const val INTERVAL_MS: Long = 1000
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
index d42ece546d..8e230f4de2 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
@@ -89,13 +89,6 @@ internal class ExtensionGithubApi {
.toAnimeExtensions(it)
}
- // Sanity check - a small number of extensions probably means something broke
- // with the repo generator
- //if (repoExtensions.size < 10) {
- // throw Exception()
- //}
- // No official repo now so this won't be needed anymore. User-made repo can have less than 10 extensions
-
extensions.addAll(repoExtensions)
} catch (e: Throwable) {
Logger.log("Failed to get extensions from GitHub")
@@ -156,13 +149,18 @@ internal class ExtensionGithubApi {
PrefManager.getVal>(PrefName.MangaExtensionRepos).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("${it}/index.min.json"))
+ .newCall(GET(repoUrl))
.awaitSuccess()
} catch (e: Throwable) {
- Logger.log("Failed to get repo: $it")
+ Logger.log("Failed to get repo: $repoUrl")
Logger.log(e)
null
}
@@ -179,13 +177,6 @@ internal class ExtensionGithubApi {
.toMangaExtensions(it)
}
- // Sanity check - a small number of extensions probably means something broke
- // with the repo generator
- //if (repoExtensions.size < 10) {
- // throw Exception()
- //}
- // No official repo now so this won't be needed anymore. User made repo can have less than 10 extensions.
-
extensions.addAll(repoExtensions)
} catch (e: Throwable) {
Logger.log("Failed to get extensions from GitHub")
@@ -203,8 +194,11 @@ internal class ExtensionGithubApi {
private fun fallbackRepoUrl(repoUrl: String): String? {
var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/"
- val strippedRepoUrl =
- repoUrl.removePrefix("https://").removePrefix("http://").removeSuffix("/")
+ val strippedRepoUrl = repoUrl
+ .removePrefix("https://")
+ .removePrefix("http://")
+ .removeSuffix("/")
+ .removeSuffix("/index.min.json")
val repoUrlParts = strippedRepoUrl.split("/")
if (repoUrlParts.size < 3) {
return null
diff --git a/app/src/main/res/layout/bottom_sheet_add_repository.xml b/app/src/main/res/layout/bottom_sheet_add_repository.xml
new file mode 100644
index 0000000000..2ec072bcdb
--- /dev/null
+++ b/app/src/main/res/layout/bottom_sheet_add_repository.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_repo.xml b/app/src/main/res/layout/item_repo.xml
new file mode 100644
index 0000000000..658110b5be
--- /dev/null
+++ b/app/src/main/res/layout/item_repo.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3e5729094c..e644ccae03 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1088,5 +1088,8 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
Textview Subtitles (Experimental)
Subtitle Stroke
Bottom Margin
+ Add Repository
+ A repository link should look like this: https://raw.githubusercontent.com/username/repo/branch/index.min.json
+ Current Repositories
diff --git a/build.gradle b/build.gradle
index 0f7e17b4aa..25cddf4d3f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.7.0'
+ classpath 'com.android.tools.build:gradle:8.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.devtools.ksp:symbol-processing-api:$ksp_version"