mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
Detect identical mangas when adding to library
Changes from upstream: Option to migrate/copy from dialog (requires manga to be initialized first) Dialog shows up on all places you can add manga (browse source and global) Closes #1207 Co-Authored-By: Felix Kaiser <30923667+foxscore@users.noreply.github.com>
This commit is contained in:
parent
54b7288acd
commit
0f50c30ad1
13 changed files with 286 additions and 59 deletions
|
@ -44,6 +44,21 @@ interface MangaQueries : DbProvider {
|
||||||
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun getDuplicateLibraryManga(manga: Manga) = db.get()
|
||||||
|
.`object`(Manga::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_FAVORITE} = 1 AND LOWER(${MangaTable.COL_TITLE}) = ? AND ${MangaTable.COL_SOURCE} != ?")
|
||||||
|
.whereArgs(
|
||||||
|
manga.title.lowercase(),
|
||||||
|
manga.source,
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun getFavoriteMangas() = db.get()
|
fun getFavoriteMangas() = db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
|
|
|
@ -1367,6 +1367,8 @@ class MangaDetailsController :
|
||||||
presenter.preferences,
|
presenter.preferences,
|
||||||
view,
|
view,
|
||||||
activity,
|
activity,
|
||||||
|
presenter.sourceManager,
|
||||||
|
this,
|
||||||
onMangaAdded = {
|
onMangaAdded = {
|
||||||
updateHeader()
|
updateHeader()
|
||||||
showAddedSnack()
|
showAddedSnack()
|
||||||
|
|
|
@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.SourceNotFoundException
|
import eu.kanade.tachiyomi.source.SourceNotFoundException
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||||
|
@ -82,6 +83,7 @@ class MangaDetailsPresenter(
|
||||||
|
|
||||||
private val customMangaManager: CustomMangaManager by injectLazy()
|
private val customMangaManager: CustomMangaManager by injectLazy()
|
||||||
private val mangaShortcutManager: MangaShortcutManager by injectLazy()
|
private val mangaShortcutManager: MangaShortcutManager by injectLazy()
|
||||||
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
private val chapterSort = ChapterSort(manga, chapterFilter, preferences)
|
private val chapterSort = ChapterSort(manga, chapterFilter, preferences)
|
||||||
val extension by lazy { (source as? HttpSource)?.getExtension() }
|
val extension by lazy { (source as? HttpSource)?.getExtension() }
|
||||||
|
|
|
@ -61,7 +61,8 @@ class MigrationProcessAdapter(
|
||||||
} && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND }
|
} && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun mangasSkipped() = (items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND })
|
fun mangasSkipped() =
|
||||||
|
(items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND })
|
||||||
|
|
||||||
suspend fun performMigrations(copy: Boolean) {
|
suspend fun performMigrations(copy: Boolean) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -70,7 +71,8 @@ class MigrationProcessAdapter(
|
||||||
val manga = migratingManga.manga
|
val manga = migratingManga.manga
|
||||||
if (manga.searchResult.initialized) {
|
if (manga.searchResult.initialized) {
|
||||||
val toMangaObj =
|
val toMangaObj =
|
||||||
db.getManga(manga.searchResult.get() ?: return@forEach).executeAsBlocking()
|
db.getManga(manga.searchResult.get() ?: return@forEach)
|
||||||
|
.executeAsBlocking()
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val prevManga = manga.manga() ?: return@forEach
|
val prevManga = manga.manga() ?: return@forEach
|
||||||
val source = sourceManager.get(toMangaObj.source) ?: return@forEach
|
val source = sourceManager.get(toMangaObj.source) ?: return@forEach
|
||||||
|
@ -128,6 +130,21 @@ class MigrationProcessAdapter(
|
||||||
) {
|
) {
|
||||||
if (controller.config == null) return
|
if (controller.config == null) return
|
||||||
val flags = preferences.migrateFlags().get()
|
val flags = preferences.migrateFlags().get()
|
||||||
|
migrateMangaInternal(flags, db, enhancedServices, prevSource, source, prevManga, manga, replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun migrateMangaInternal(
|
||||||
|
flags: Int,
|
||||||
|
db: DatabaseHelper,
|
||||||
|
enhancedServices: List<EnhancedTrackService>,
|
||||||
|
prevSource: Source?,
|
||||||
|
source: Source,
|
||||||
|
prevManga: Manga,
|
||||||
|
manga: Manga,
|
||||||
|
replace: Boolean
|
||||||
|
) {
|
||||||
// Update chapters read
|
// Update chapters read
|
||||||
if (MigrationFlags.hasChapters(flags)) {
|
if (MigrationFlags.hasChapters(flags)) {
|
||||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||||
|
@ -144,8 +161,10 @@ class MigrationProcessAdapter(
|
||||||
chapter.bookmark = prevChapter.bookmark
|
chapter.bookmark = prevChapter.bookmark
|
||||||
chapter.read = prevChapter.read
|
chapter.read = prevChapter.read
|
||||||
chapter.date_fetch = prevChapter.date_fetch
|
chapter.date_fetch = prevChapter.date_fetch
|
||||||
prevHistoryList.find { it.chapter_id == prevChapter.id }?.let { prevHistory ->
|
prevHistoryList.find { it.chapter_id == prevChapter.id }
|
||||||
val history = History.create(chapter).apply { last_read = prevHistory.last_read }
|
?.let { prevHistory ->
|
||||||
|
val history = History.create(chapter)
|
||||||
|
.apply { last_read = prevHistory.last_read }
|
||||||
historyList.add(history)
|
historyList.add(history)
|
||||||
}
|
}
|
||||||
} else if (chapter.chapter_number <= maxChapterRead) {
|
} else if (chapter.chapter_number <= maxChapterRead) {
|
||||||
|
@ -164,7 +183,8 @@ class MigrationProcessAdapter(
|
||||||
}
|
}
|
||||||
// Update track
|
// Update track
|
||||||
if (MigrationFlags.hasTracks(flags)) {
|
if (MigrationFlags.hasTracks(flags)) {
|
||||||
val tracksToUpdate = db.getTracks(prevManga).executeAsBlocking().mapNotNull { track ->
|
val tracksToUpdate =
|
||||||
|
db.getTracks(prevManga).executeAsBlocking().mapNotNull { track ->
|
||||||
track.id = null
|
track.id = null
|
||||||
track.manga_id = manga.id!!
|
track.manga_id = manga.id!!
|
||||||
|
|
||||||
|
@ -187,4 +207,5 @@ class MigrationProcessAdapter(
|
||||||
db.updateMangaAdded(manga).executeAsBlocking()
|
db.updateMangaAdded(manga).executeAsBlocking()
|
||||||
db.updateMangaTitle(manga).executeAsBlocking()
|
db.updateMangaTitle(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,6 +700,8 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||||
preferences,
|
preferences,
|
||||||
view,
|
view,
|
||||||
activity,
|
activity,
|
||||||
|
presenter.sourceManager,
|
||||||
|
this,
|
||||||
onMangaAdded = {
|
onMangaAdded = {
|
||||||
adapter?.notifyItemChanged(position)
|
adapter?.notifyItemChanged(position)
|
||||||
snack = view.snack(R.string.added_to_library)
|
snack = view.snack(R.string.added_to_library)
|
||||||
|
|
|
@ -53,7 +53,7 @@ import uy.kohesive.injekt.api.get
|
||||||
open class BrowseSourcePresenter(
|
open class BrowseSourcePresenter(
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val db: DatabaseHelper = Injekt.get(),
|
val db: DatabaseHelper = Injekt.get(),
|
||||||
val prefs: PreferencesHelper = Injekt.get(),
|
val prefs: PreferencesHelper = Injekt.get(),
|
||||||
private val coverCache: CoverCache = Injekt.get(),
|
private val coverCache: CoverCache = Injekt.get(),
|
||||||
|
|
|
@ -123,7 +123,21 @@ open class GlobalSearchController(
|
||||||
preferences,
|
preferences,
|
||||||
view,
|
view,
|
||||||
activity,
|
activity,
|
||||||
onMangaAdded = {
|
presenter.sourceManager,
|
||||||
|
this,
|
||||||
|
onMangaAdded = { migrationInfo ->
|
||||||
|
migrationInfo?.let { (source, stillFaved) ->
|
||||||
|
val index = this.adapter
|
||||||
|
?.currentItems?.indexOfFirst { it.source.id == source } ?: return@let
|
||||||
|
val item = this.adapter?.getItem(index) ?: return@let
|
||||||
|
val oldMangaIndex = item.results?.indexOfFirst {
|
||||||
|
it.manga.title.lowercase() == manga.title.lowercase()
|
||||||
|
} ?: return@let
|
||||||
|
val oldMangaItem = item.results.getOrNull(oldMangaIndex)
|
||||||
|
oldMangaItem?.manga?.favorite = stillFaved
|
||||||
|
val holder = binding.recycler.findViewHolderForAdapterPosition(index) as? GlobalSearchHolder
|
||||||
|
holder?.updateManga(oldMangaIndex)
|
||||||
|
}
|
||||||
adapter.notifyItemChanged(position)
|
adapter.notifyItemChanged(position)
|
||||||
snack = view.snack(R.string.added_to_library)
|
snack = view.snack(R.string.added_to_library)
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,6 +81,8 @@ class GlobalSearchHolder(view: View, val adapter: GlobalSearchAdapter) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateManga(position: Int) = mangaAdapter.notifyItemChanged(position)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from the presenter when a manga is initialized.
|
* Called from the presenter when a manga is initialized.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package eu.kanade.tachiyomi.util
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.bluelinelabs.conductor.Controller
|
||||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
@ -16,10 +20,18 @@ import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.category.addtolibrary.SetCategoriesSheet
|
import eu.kanade.tachiyomi.ui.category.addtolibrary.SetCategoriesSheet
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||||
|
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
|
||||||
|
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcessAdapter
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
|
||||||
|
import eu.kanade.tachiyomi.util.lang.asButton
|
||||||
import eu.kanade.tachiyomi.util.system.launchIO
|
import eu.kanade.tachiyomi.util.system.launchIO
|
||||||
|
import eu.kanade.tachiyomi.util.system.materialAlertDialog
|
||||||
|
import eu.kanade.tachiyomi.util.system.setCustomTitleAndMessage
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||||
import eu.kanade.tachiyomi.util.view.snack
|
import eu.kanade.tachiyomi.util.view.snack
|
||||||
|
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.widget.TriStateCheckBox
|
import eu.kanade.tachiyomi.widget.TriStateCheckBox
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -104,11 +116,46 @@ fun Manga.addOrRemoveToFavorites(
|
||||||
preferences: PreferencesHelper,
|
preferences: PreferencesHelper,
|
||||||
view: View,
|
view: View,
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
onMangaAdded: () -> Unit,
|
sourceManager: SourceManager,
|
||||||
|
controller: Controller,
|
||||||
|
checkForDupes: Boolean = true,
|
||||||
|
onMangaAdded: (Pair<Long, Boolean>?) -> Unit,
|
||||||
onMangaMoved: () -> Unit,
|
onMangaMoved: () -> Unit,
|
||||||
onMangaDeleted: () -> Unit
|
onMangaDeleted: () -> Unit,
|
||||||
): Snackbar? {
|
): Snackbar? {
|
||||||
if (!favorite) {
|
if (!favorite) {
|
||||||
|
if (checkForDupes) {
|
||||||
|
val duplicateManga = db.getDuplicateLibraryManga(this).executeAsBlocking()
|
||||||
|
if (duplicateManga != null) {
|
||||||
|
showAddDuplicateDialog(
|
||||||
|
this,
|
||||||
|
duplicateManga,
|
||||||
|
activity,
|
||||||
|
db,
|
||||||
|
sourceManager,
|
||||||
|
controller,
|
||||||
|
addManga = {
|
||||||
|
addOrRemoveToFavorites(
|
||||||
|
db,
|
||||||
|
preferences,
|
||||||
|
view,
|
||||||
|
activity,
|
||||||
|
sourceManager,
|
||||||
|
controller,
|
||||||
|
false,
|
||||||
|
onMangaAdded,
|
||||||
|
onMangaMoved,
|
||||||
|
onMangaDeleted
|
||||||
|
)
|
||||||
|
},
|
||||||
|
migrateManga = { source, faved ->
|
||||||
|
onMangaAdded(source to faved)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val categories = db.getCategories().executeAsBlocking()
|
val categories = db.getCategories().executeAsBlocking()
|
||||||
val defaultCategoryId = preferences.defaultCategory()
|
val defaultCategoryId = preferences.defaultCategory()
|
||||||
val defaultCategory = categories.find { it.id == defaultCategoryId }
|
val defaultCategory = categories.find { it.id == defaultCategoryId }
|
||||||
|
@ -155,7 +202,7 @@ fun Manga.addOrRemoveToFavorites(
|
||||||
ids,
|
ids,
|
||||||
true
|
true
|
||||||
) {
|
) {
|
||||||
onMangaAdded()
|
onMangaAdded(null)
|
||||||
autoAddTrack(db, onMangaMoved)
|
autoAddTrack(db, onMangaMoved)
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
@ -188,6 +235,102 @@ fun Manga.addOrRemoveToFavorites(
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showAddDuplicateDialog(
|
||||||
|
newManga: Manga,
|
||||||
|
libraryManga: Manga,
|
||||||
|
activity: Activity,
|
||||||
|
db: DatabaseHelper,
|
||||||
|
sourceManager: SourceManager,
|
||||||
|
controller: Controller,
|
||||||
|
addManga: () -> Unit,
|
||||||
|
migrateManga: (Long, Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
val source = sourceManager.getOrStub(libraryManga.source)
|
||||||
|
|
||||||
|
fun migrateManga(mDialog: DialogInterface, replace: Boolean) {
|
||||||
|
val listView = (mDialog as AlertDialog).listView
|
||||||
|
var flags = 0
|
||||||
|
if (listView.isItemChecked(0)) flags = flags or MigrationFlags.CHAPTERS
|
||||||
|
if (listView.isItemChecked(1)) flags = flags or MigrationFlags.CATEGORIES
|
||||||
|
if (listView.isItemChecked(2)) flags = flags or MigrationFlags.TRACK
|
||||||
|
val enhancedServices by lazy { Injekt.get<TrackManager>().services.filterIsInstance<EnhancedTrackService>() }
|
||||||
|
MigrationProcessAdapter.migrateMangaInternal(
|
||||||
|
flags,
|
||||||
|
db,
|
||||||
|
enhancedServices,
|
||||||
|
source,
|
||||||
|
sourceManager.getOrStub(newManga.source),
|
||||||
|
libraryManga,
|
||||||
|
newManga,
|
||||||
|
replace
|
||||||
|
)
|
||||||
|
migrateManga(libraryManga.source, !replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.materialAlertDialog().apply {
|
||||||
|
setCustomTitleAndMessage(0, activity.getString(R.string.confirm_manga_add_duplicate, source.name))
|
||||||
|
setItems(
|
||||||
|
arrayOf(
|
||||||
|
activity.getString(R.string.show_, libraryManga.seriesType(activity, sourceManager)).asButton(activity),
|
||||||
|
activity.getString(R.string.add_to_library).asButton(activity),
|
||||||
|
activity.getString(R.string.migrate).asButton(activity, !newManga.initialized),
|
||||||
|
)
|
||||||
|
) { dialog, i ->
|
||||||
|
when (i) {
|
||||||
|
0 -> controller.router.pushController(
|
||||||
|
MangaDetailsController(libraryManga)
|
||||||
|
.withFadeTransaction()
|
||||||
|
)
|
||||||
|
1 -> addManga()
|
||||||
|
2 -> {
|
||||||
|
if (!newManga.initialized) {
|
||||||
|
activity.toast(R.string.must_view_details_before_migration, Toast.LENGTH_LONG)
|
||||||
|
return@setItems
|
||||||
|
}
|
||||||
|
activity.materialAlertDialog().apply {
|
||||||
|
setTitle(R.string.migration)
|
||||||
|
setMultiChoiceItems(
|
||||||
|
arrayOf(
|
||||||
|
activity.getString(R.string.chapters),
|
||||||
|
activity.getString(R.string.categories),
|
||||||
|
activity.getString(R.string.tracking),
|
||||||
|
),
|
||||||
|
booleanArrayOf(true, true, true), null
|
||||||
|
)
|
||||||
|
setPositiveButton(R.string.migrate) { mDialog, _ ->
|
||||||
|
migrateManga(mDialog, true)
|
||||||
|
}
|
||||||
|
setNegativeButton(R.string.copy) { mDialog, _ ->
|
||||||
|
migrateManga(mDialog, false)
|
||||||
|
}
|
||||||
|
setNeutralButton(android.R.string.cancel, null)
|
||||||
|
setCancelable(true)
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
setNegativeButton(activity.getString(android.R.string.cancel)) { _, _ -> }
|
||||||
|
setCancelable(true)
|
||||||
|
}.create().apply {
|
||||||
|
setOnShowListener {
|
||||||
|
if (!newManga.initialized) {
|
||||||
|
val listView = (it as AlertDialog).listView
|
||||||
|
val view = listView.getChildAt(2)
|
||||||
|
view?.setOnClickListener {
|
||||||
|
if (!newManga.initialized) {
|
||||||
|
activity.toast(
|
||||||
|
R.string.must_view_details_before_migration,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
fun Manga.autoAddTrack(db: DatabaseHelper, onMangaMoved: () -> Unit) {
|
fun Manga.autoAddTrack(db: DatabaseHelper, onMangaMoved: () -> Unit) {
|
||||||
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLogged }
|
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLogged }
|
||||||
val source = Injekt.get<SourceManager>().getOrStub(this.source)
|
val source = Injekt.get<SourceManager>().getOrStub(this.source)
|
||||||
|
|
|
@ -6,13 +6,18 @@ import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
import android.text.SpannedString
|
||||||
import android.text.style.BackgroundColorSpan
|
import android.text.style.BackgroundColorSpan
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.RelativeSizeSpan
|
import android.text.style.RelativeSizeSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.text.style.SuperscriptSpan
|
import android.text.style.SuperscriptSpan
|
||||||
|
import android.text.style.TextAppearanceSpan
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.text.buildSpannedString
|
||||||
|
import androidx.core.text.color
|
||||||
|
import androidx.core.text.inSpans
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
|
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
|
||||||
|
@ -111,6 +116,19 @@ fun String.highlightText(highlight: String, @ColorInt color: Int): Spanned {
|
||||||
return wordToSpan
|
return wordToSpan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.asButton(context: Context, disabled: Boolean = false): SpannedString {
|
||||||
|
return buildSpannedString {
|
||||||
|
val buttonSpan: SpannableStringBuilder.() -> Unit = {
|
||||||
|
inSpans(
|
||||||
|
TextAppearanceSpan(context, R.style.TextAppearance_Tachiyomi_Button)
|
||||||
|
) { append(this@asButton) }
|
||||||
|
}
|
||||||
|
if (disabled) {
|
||||||
|
color(context.getColor(R.color.material_on_surface_disabled), buttonSpan)
|
||||||
|
} else buttonSpan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun String.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
|
fun String.indexesOf(substr: String, ignoreCase: Boolean = true): List<Int> {
|
||||||
val list = mutableListOf<Int>()
|
val list = mutableListOf<Int>()
|
||||||
if (substr.isBlank()) return list
|
if (substr.isBlank()) return list
|
||||||
|
|
|
@ -63,7 +63,11 @@ fun AlertDialog.disableItems(items: Array<String>) {
|
||||||
fun MaterialAlertDialogBuilder.setCustomTitleAndMessage(title: Int, message: String): MaterialAlertDialogBuilder {
|
fun MaterialAlertDialogBuilder.setCustomTitleAndMessage(title: Int, message: String): MaterialAlertDialogBuilder {
|
||||||
return setCustomTitle(
|
return setCustomTitle(
|
||||||
(CustomDialogTitleMessageBinding.inflate(LayoutInflater.from(context))).apply {
|
(CustomDialogTitleMessageBinding.inflate(LayoutInflater.from(context))).apply {
|
||||||
|
if (title != 0) {
|
||||||
alertTitle.text = context.getString(title)
|
alertTitle.text = context.getString(title)
|
||||||
|
} else {
|
||||||
|
alertTitle.isVisible = false
|
||||||
|
}
|
||||||
this.message.text = message
|
this.message.text = message
|
||||||
}.root
|
}.root
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@android:id/message"
|
||||||
android:paddingTop="@dimen/abc_dialog_padding_top_material"
|
android:paddingTop="@dimen/abc_dialog_padding_top_material"
|
||||||
android:paddingLeft="?attr/dialogPreferredPadding"
|
android:paddingLeft="?attr/dialogPreferredPadding"
|
||||||
android:paddingRight="?attr/dialogPreferredPadding" />
|
android:paddingRight="?attr/dialogPreferredPadding" />
|
||||||
|
|
|
@ -504,6 +504,9 @@
|
||||||
<string name="added_to_library">Added to library</string>
|
<string name="added_to_library">Added to library</string>
|
||||||
<string name="add_to_library">Add to Library</string>
|
<string name="add_to_library">Add to Library</string>
|
||||||
<string name="removed_from_library">Removed from library</string>
|
<string name="removed_from_library">Removed from library</string>
|
||||||
|
<string name="show_">Show %1$s</string>
|
||||||
|
<string name="must_view_details_before_migration">This entry\'s details page must be viewed before being able to migrate</string>
|
||||||
|
<string name="confirm_manga_add_duplicate">You have an entry in your library with the same name but from a different source (%1$s).\n\nDo you still wish to continue?</string>
|
||||||
<string name="_copied_to_clipboard">%1$s copied to clipboard</string>
|
<string name="_copied_to_clipboard">%1$s copied to clipboard</string>
|
||||||
<string name="source_not_installed_">Source not installed: %1$s</string>
|
<string name="source_not_installed_">Source not installed: %1$s</string>
|
||||||
<string name="no_description">No description</string>
|
<string name="no_description">No description</string>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue