From 0f50c30ad13e79959931ae486548a50c67da9248 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Fri, 22 Apr 2022 04:30:27 -0400 Subject: [PATCH] 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> --- .../data/database/queries/MangaQueries.kt | 15 ++ .../ui/manga/MangaDetailsController.kt | 2 + .../ui/manga/MangaDetailsPresenter.kt | 2 + .../manga/process/MigrationProcessAdapter.kt | 127 ++++++++------- .../source/browse/BrowseSourceController.kt | 2 + .../ui/source/browse/BrowseSourcePresenter.kt | 2 +- .../globalsearch/GlobalSearchController.kt | 16 +- .../source/globalsearch/GlobalSearchHolder.kt | 2 + .../kanade/tachiyomi/util/MangaExtensions.kt | 149 +++++++++++++++++- .../tachiyomi/util/lang/StringExtensions.kt | 18 +++ .../system/MaterialAlertDialogExtensions.kt | 6 +- .../layout/custom_dialog_title_message.xml | 1 + app/src/main/res/values/strings.xml | 3 + 13 files changed, 286 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt index fa46f6976b..48a841bf95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt @@ -44,6 +44,21 @@ interface MangaQueries : DbProvider { .withGetResolver(LibraryMangaGetResolver.INSTANCE) .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() .listOfObjects(Manga::class.java) .withQuery( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index b0c69a6882..d6a89c01bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -1367,6 +1367,8 @@ class MangaDetailsController : presenter.preferences, view, activity, + presenter.sourceManager, + this, onMangaAdded = { updateHeader() showAddedSnack() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index 4962175ea4..9f97ff2fff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceNotFoundException import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.toSChapter @@ -82,6 +83,7 @@ class MangaDetailsPresenter( private val customMangaManager: CustomMangaManager by injectLazy() private val mangaShortcutManager: MangaShortcutManager by injectLazy() + val sourceManager: SourceManager by injectLazy() private val chapterSort = ChapterSort(manga, chapterFilter, preferences) val extension by lazy { (source as? HttpSource)?.getExtension() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt index 31314fb81c..f24075fc32 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt @@ -61,7 +61,8 @@ class MigrationProcessAdapter( } && 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) { withContext(Dispatchers.IO) { @@ -70,7 +71,8 @@ class MigrationProcessAdapter( val manga = migratingManga.manga if (manga.searchResult.initialized) { val toMangaObj = - db.getManga(manga.searchResult.get() ?: return@forEach).executeAsBlocking() + db.getManga(manga.searchResult.get() ?: return@forEach) + .executeAsBlocking() ?: return@forEach val prevManga = manga.manga() ?: return@forEach val source = sourceManager.get(toMangaObj.source) ?: return@forEach @@ -128,63 +130,82 @@ class MigrationProcessAdapter( ) { if (controller.config == null) return val flags = preferences.migrateFlags().get() - // Update chapters read - if (MigrationFlags.hasChapters(flags)) { - val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() - val maxChapterRead = - prevMangaChapters.filter { it.read }.maxOfOrNull { it.chapter_number } ?: 0f - val dbChapters = db.getChapters(manga).executeAsBlocking() - val prevHistoryList = db.getHistoryByMangaId(prevManga.id!!).executeAsBlocking() - val historyList = mutableListOf() - for (chapter in dbChapters) { - if (chapter.isRecognizedNumber) { - val prevChapter = - prevMangaChapters.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number } - if (prevChapter != null) { - chapter.bookmark = prevChapter.bookmark - chapter.read = prevChapter.read - chapter.date_fetch = prevChapter.date_fetch - prevHistoryList.find { it.chapter_id == prevChapter.id }?.let { prevHistory -> - val history = History.create(chapter).apply { last_read = prevHistory.last_read } - historyList.add(history) + migrateMangaInternal(flags, db, enhancedServices, prevSource, source, prevManga, manga, replace) + } + + companion object { + + fun migrateMangaInternal( + flags: Int, + db: DatabaseHelper, + enhancedServices: List, + prevSource: Source?, + source: Source, + prevManga: Manga, + manga: Manga, + replace: Boolean + ) { + // Update chapters read + if (MigrationFlags.hasChapters(flags)) { + val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() + val maxChapterRead = + prevMangaChapters.filter { it.read }.maxOfOrNull { it.chapter_number } ?: 0f + val dbChapters = db.getChapters(manga).executeAsBlocking() + val prevHistoryList = db.getHistoryByMangaId(prevManga.id!!).executeAsBlocking() + val historyList = mutableListOf() + for (chapter in dbChapters) { + if (chapter.isRecognizedNumber) { + val prevChapter = + prevMangaChapters.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number } + if (prevChapter != null) { + chapter.bookmark = prevChapter.bookmark + chapter.read = prevChapter.read + chapter.date_fetch = prevChapter.date_fetch + prevHistoryList.find { it.chapter_id == prevChapter.id } + ?.let { prevHistory -> + val history = History.create(chapter) + .apply { last_read = prevHistory.last_read } + historyList.add(history) + } + } else if (chapter.chapter_number <= maxChapterRead) { + chapter.read = true } - } else if (chapter.chapter_number <= maxChapterRead) { - chapter.read = true } } + db.insertChapters(dbChapters).executeAsBlocking() + db.updateHistoryLastRead(historyList).executeAsBlocking() } - db.insertChapters(dbChapters).executeAsBlocking() - db.updateHistoryLastRead(historyList).executeAsBlocking() - } - // Update categories - if (MigrationFlags.hasCategories(flags)) { - val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() - val mangaCategories = categories.map { MangaCategory.create(manga, it) } - db.setMangaCategories(mangaCategories, listOf(manga)) - } - // Update track - if (MigrationFlags.hasTracks(flags)) { - val tracksToUpdate = db.getTracks(prevManga).executeAsBlocking().mapNotNull { track -> - track.id = null - track.manga_id = manga.id!! + // Update categories + if (MigrationFlags.hasCategories(flags)) { + val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() + val mangaCategories = categories.map { MangaCategory.create(manga, it) } + db.setMangaCategories(mangaCategories, listOf(manga)) + } + // Update track + if (MigrationFlags.hasTracks(flags)) { + val tracksToUpdate = + db.getTracks(prevManga).executeAsBlocking().mapNotNull { track -> + track.id = null + track.manga_id = manga.id!! - val service = enhancedServices - .firstOrNull { it.isTrackFrom(track, prevManga, prevSource) } - if (service != null) service.migrateTrack(track, manga, source) - else track + val service = enhancedServices + .firstOrNull { it.isTrackFrom(track, prevManga, prevSource) } + if (service != null) service.migrateTrack(track, manga, source) + else track + } + db.insertTracks(tracksToUpdate).executeAsBlocking() } - db.insertTracks(tracksToUpdate).executeAsBlocking() + // Update favorite status + if (replace) { + prevManga.favorite = false + db.updateMangaFavorite(prevManga).executeAsBlocking() + } + manga.favorite = true + if (replace) manga.date_added = prevManga.date_added + else manga.date_added = Date().time + db.updateMangaFavorite(manga).executeAsBlocking() + db.updateMangaAdded(manga).executeAsBlocking() + db.updateMangaTitle(manga).executeAsBlocking() } - // Update favorite status - if (replace) { - prevManga.favorite = false - db.updateMangaFavorite(prevManga).executeAsBlocking() - } - manga.favorite = true - if (replace) manga.date_added = prevManga.date_added - else manga.date_added = Date().time - db.updateMangaFavorite(manga).executeAsBlocking() - db.updateMangaAdded(manga).executeAsBlocking() - db.updateMangaTitle(manga).executeAsBlocking() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 9447c35f08..d08adc7dbd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -700,6 +700,8 @@ open class BrowseSourceController(bundle: Bundle) : preferences, view, activity, + presenter.sourceManager, + this, onMangaAdded = { adapter?.notifyItemChanged(position) snack = view.snack(R.string.added_to_library) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt index 7711c1bcc0..4945908d89 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt @@ -53,7 +53,7 @@ import uy.kohesive.injekt.api.get open class BrowseSourcePresenter( private val sourceId: Long, searchQuery: String? = null, - private val sourceManager: SourceManager = Injekt.get(), + val sourceManager: SourceManager = Injekt.get(), val db: DatabaseHelper = Injekt.get(), val prefs: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt index 43972aae66..0b0b1d2720 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt @@ -123,7 +123,21 @@ open class GlobalSearchController( preferences, view, 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) snack = view.snack(R.string.added_to_library) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt index ec77444545..a8ae1daa47 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt @@ -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. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt index 4e94bb4389..17219283e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt @@ -1,7 +1,11 @@ package eu.kanade.tachiyomi.util import android.app.Activity +import android.content.DialogInterface 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.Snackbar 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.SourceManager 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.lang.asButton 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.view.snack +import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.widget.TriStateCheckBox import timber.log.Timber import uy.kohesive.injekt.Injekt @@ -104,11 +116,46 @@ fun Manga.addOrRemoveToFavorites( preferences: PreferencesHelper, view: View, activity: Activity, - onMangaAdded: () -> Unit, + sourceManager: SourceManager, + controller: Controller, + checkForDupes: Boolean = true, + onMangaAdded: (Pair?) -> Unit, onMangaMoved: () -> Unit, - onMangaDeleted: () -> Unit + onMangaDeleted: () -> Unit, ): Snackbar? { 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 defaultCategoryId = preferences.defaultCategory() val defaultCategory = categories.find { it.id == defaultCategoryId } @@ -155,7 +202,7 @@ fun Manga.addOrRemoveToFavorites( ids, true ) { - onMangaAdded() + onMangaAdded(null) autoAddTrack(db, onMangaMoved) }.show() } @@ -188,6 +235,102 @@ fun Manga.addOrRemoveToFavorites( 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().services.filterIsInstance() } + 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) { val loggedServices = Injekt.get().services.filter { it.isLogged } val source = Injekt.get().getOrStub(this.source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt index ad06c31e13..6f8ac1764f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt @@ -6,13 +6,18 @@ import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.Spanned +import android.text.SpannedString import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.text.style.RelativeSizeSpan import android.text.style.StyleSpan import android.text.style.SuperscriptSpan +import android.text.style.TextAppearanceSpan import androidx.annotation.ColorInt 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.util.system.getResourceColor import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator @@ -111,6 +116,19 @@ fun String.highlightText(highlight: String, @ColorInt color: Int): Spanned { 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 { val list = mutableListOf() if (substr.isBlank()) return list diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt index e984d3deac..ba2393e389 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt @@ -63,7 +63,11 @@ fun AlertDialog.disableItems(items: Array) { fun MaterialAlertDialogBuilder.setCustomTitleAndMessage(title: Int, message: String): MaterialAlertDialogBuilder { return setCustomTitle( (CustomDialogTitleMessageBinding.inflate(LayoutInflater.from(context))).apply { - alertTitle.text = context.getString(title) + if (title != 0) { + alertTitle.text = context.getString(title) + } else { + alertTitle.isVisible = false + } this.message.text = message }.root ) diff --git a/app/src/main/res/layout/custom_dialog_title_message.xml b/app/src/main/res/layout/custom_dialog_title_message.xml index 845824777f..4a5afe754f 100644 --- a/app/src/main/res/layout/custom_dialog_title_message.xml +++ b/app/src/main/res/layout/custom_dialog_title_message.xml @@ -14,6 +14,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@android:id/message" android:paddingTop="@dimen/abc_dialog_padding_top_material" android:paddingLeft="?attr/dialogPreferredPadding" android:paddingRight="?attr/dialogPreferredPadding" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 01ca137af2..be48f764af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -504,6 +504,9 @@ Added to library Add to Library Removed from library + Show %1$s + This entry\'s details page must be viewed before being able to migrate + 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? %1$s copied to clipboard Source not installed: %1$s No description