From af214364ad89b229ac594bfb1404767a817dc116 Mon Sep 17 00:00:00 2001 From: LandWarderer2772 Date: Tue, 9 Jun 2026 12:45:06 +0530 Subject: [PATCH 1/3] fix: Sources are unknown. --- .../futon/backups/data/model/SourceBackup.kt | 3 ++ .../futon/core/db/MangaDatabase.kt | 4 +- .../futon/core/db/dao/MangaSourcesDao.kt | 23 +++++++- .../futon/core/db/entity/MangaSourceEntity.kt | 1 + .../core/db/migrations/Migration28To29.kt | 10 ++++ .../futon/core/model/MangaSource.kt | 21 ++++++-- .../landwarderer/futon/core/nav/AppRouter.kt | 15 +++--- .../futon/core/nav/MangaIntent.kt | 7 ++- .../futon/details/ui/DetailsActivity.kt | 9 +++- .../futon/details/ui/DetailsViewModel.kt | 1 + .../explore/data/MangaSourcesRepository.kt | 53 ++++++++++++++----- .../futon/explore/ui/ExploreFragment.kt | 3 +- .../futon/list/ui/MangaListFragment.kt | 2 +- .../futon/mihon/MihonExtensionManager.kt | 14 ++++- .../runtime/ExternalExtensionManagerFacade.kt | 1 + .../ExternalExtensionManagerRuntime.kt | 4 ++ .../futon/mihon/model/MihonMangaSource.kt | 5 +- .../futon/remotelist/ui/RemoteListFragment.kt | 2 +- .../futon/search/ui/MangaListActivity.kt | 7 ++- .../futon/search/ui/multi/SearchActivity.kt | 6 ++- .../sources/catalog/SourcesCatalogActivity.kt | 3 +- .../res/layout/item_explore_source_grid.xml | 4 +- .../res/layout/item_recommendation_manga.xml | 2 +- .../item_search_suggestion_manga_grid.xml | 2 +- 24 files changed, 160 insertions(+), 42 deletions(-) create mode 100644 app/src/main/kotlin/io/github/landwarderer/futon/core/db/migrations/Migration28To29.kt diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/backups/data/model/SourceBackup.kt b/app/src/main/kotlin/io/github/landwarderer/futon/backups/data/model/SourceBackup.kt index ef676dca2b..2867c5c0fc 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/backups/data/model/SourceBackup.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/backups/data/model/SourceBackup.kt @@ -12,6 +12,7 @@ class SourceBackup( @SerialName("added_in") val addedIn: Int, @SerialName("pinned") val isPinned: Boolean = false, @SerialName("enabled") val isEnabled: Boolean = true, // for compatibility purposes, should be only true + @SerialName("title") val title: String? = null, ) { constructor(entity: MangaSourceEntity) : this( @@ -21,6 +22,7 @@ class SourceBackup( addedIn = entity.addedIn, isPinned = entity.isPinned, isEnabled = entity.isEnabled, + title = entity.title, ) fun toEntity() = MangaSourceEntity( @@ -31,5 +33,6 @@ class SourceBackup( lastUsedAt = lastUsedAt, isPinned = isPinned, cfState = 0, + title = title, ) } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/MangaDatabase.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/MangaDatabase.kt index 284deabb4d..9d91c93631 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/MangaDatabase.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/MangaDatabase.kt @@ -42,6 +42,7 @@ import io.github.landwarderer.futon.core.db.migrations.Migration24To25 import io.github.landwarderer.futon.core.db.migrations.Migration25To26 import io.github.landwarderer.futon.core.db.migrations.Migration26To27 import io.github.landwarderer.futon.core.db.migrations.Migration27To28 +import io.github.landwarderer.futon.core.db.migrations.Migration28To29 import io.github.landwarderer.futon.core.db.migrations.Migration2To3 import io.github.landwarderer.futon.core.db.migrations.Migration3To4 import io.github.landwarderer.futon.core.db.migrations.Migration4To5 @@ -73,7 +74,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -const val DATABASE_VERSION = 28 +const val DATABASE_VERSION = 29 @Database( entities = [ @@ -148,6 +149,7 @@ fun getDatabaseMigrations(context: Context): Array = arrayOf( Migration25To26(), Migration26To27(), Migration27To28(), + Migration28To29(), ) fun MangaDatabase(context: Context): MangaDatabase = Room diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/dao/MangaSourcesDao.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/dao/MangaSourcesDao.kt index 703043b655..5ebaa63696 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/dao/MangaSourcesDao.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/dao/MangaSourcesDao.kt @@ -52,8 +52,28 @@ abstract class MangaSourcesDao { @Query("UPDATE sources SET used_at = :value WHERE source = :source") abstract suspend fun setLastUsed(source: String, value: Long) + @Query("UPDATE sources SET title = :title WHERE source = :source") + abstract suspend fun setTitle(source: String, title: String?) + + @Transaction + open suspend fun setPinned(source: String, isPinned: Boolean) { + if (updateIsPinned(source, isPinned) == 0) { + val entity = MangaSourceEntity( + source = source, + isEnabled = false, + sortKey = getMaxSortKey() + 1, + addedIn = BuildConfig.VERSION_CODE, + lastUsedAt = 0, + isPinned = isPinned, + cfState = CloudFlareHelper.PROTECTION_NOT_DETECTED, + title = null, + ) + upsert(entity) + } + } + @Query("UPDATE sources SET pinned = :isPinned WHERE source = :source") - abstract suspend fun setPinned(source: String, isPinned: Boolean) + protected abstract suspend fun updateIsPinned(source: String, isPinned: Boolean): Int @Query("UPDATE sources SET cf_state = :state WHERE source = :source") abstract suspend fun setCfState(source: String, state: Int) @@ -88,6 +108,7 @@ abstract class MangaSourcesDao { lastUsedAt = 0, isPinned = false, cfState = CloudFlareHelper.PROTECTION_NOT_DETECTED, + title = null, ) upsert(entity) } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/entity/MangaSourceEntity.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/entity/MangaSourceEntity.kt index 2bb8e7385e..59c6f23f9e 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/entity/MangaSourceEntity.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/entity/MangaSourceEntity.kt @@ -18,4 +18,5 @@ data class MangaSourceEntity( @ColumnInfo(name = "used_at") val lastUsedAt: Long, @ColumnInfo(name = "pinned") val isPinned: Boolean, @ColumnInfo(name = "cf_state") val cfState: Int, + @ColumnInfo(name = "title") val title: String?, ) diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/db/migrations/Migration28To29.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/migrations/Migration28To29.kt new file mode 100644 index 0000000000..b0985fcba1 --- /dev/null +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/db/migrations/Migration28To29.kt @@ -0,0 +1,10 @@ +package io.github.landwarderer.futon.core.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration28To29 : Migration(28, 29) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE sources ADD COLUMN title TEXT") + } +} diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/model/MangaSource.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/model/MangaSource.kt index 48a2e1b8ef..3d92db8a7d 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/core/model/MangaSource.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/model/MangaSource.kt @@ -21,6 +21,13 @@ import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.splitTwoParts import java.util.Locale +import java.util.concurrent.ConcurrentHashMap + +private val MIHON_TITLES = ConcurrentHashMap() + +fun updateMihonTitle(name: String, title: String) { + MIHON_TITLES[name] = title +} data object LocalMangaSource : MangaSource { override val name = "LOCAL" @@ -34,7 +41,7 @@ data object TestMangaSource : MangaSource { override val name = "TEST" } -fun MangaSource(name: String?): MangaSource { +fun MangaSource(name: String?, title: String? = null): MangaSource { when (name ?: return UnknownMangaSource) { UnknownMangaSource.name -> return UnknownMangaSource LocalMangaSource.name -> return LocalMangaSource @@ -45,7 +52,7 @@ fun MangaSource(name: String?): MangaSource { return ExternalMangaSource(packageName = parts.first, authority = parts.second) } if (name.startsWith("mihon:") || name.startsWith("MIHON_")) { - return AnonymousMangaSource(name) + return AnonymousMangaSource(name, title) } MangaParserSource.entries.forEach { if (it.name == name) return it @@ -53,9 +60,12 @@ fun MangaSource(name: String?): MangaSource { return UnknownMangaSource } -private data class AnonymousMangaSource(override val name: String) : MangaSource +data class AnonymousMangaSource( + override val name: String, + val title: String? = null +) : MangaSource -fun Collection.toMangaSources() = map(::MangaSource) +fun Collection.toMangaSources() = map { MangaSource(it) } fun MangaSource.isNsfw(): Boolean = when (val source = unwrap()) { is MangaSourceInfo -> source.mangaSource.isNsfw() @@ -127,7 +137,8 @@ fun MangaSource.getTitle(context: Context): String = when (val source = unwrap() LocalMangaSource -> context.getString(R.string.local_storage) TestMangaSource -> context.getString(R.string.test_parser) is ExternalMangaSource -> source.resolveName(context) - is MihonMangaSource -> source.displayName + is MihonMangaSource -> source.displayName.also { updateMihonTitle(source.name, it) } + is AnonymousMangaSource -> MIHON_TITLES[source.name] ?: source.title ?: context.getString(R.string.unknown) else -> context.getString(R.string.unknown) } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt index 6c1fbd8f5a..f5961ddf8c 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt @@ -126,8 +126,8 @@ class AppRouter private constructor( /** Activities **/ - fun openList(source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?) { - startActivity(listIntent(contextOrNull() ?: return, source, filter, sortOrder)) + fun openList(source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?, sourceTitle: String? = null) { + startActivity(listIntent(contextOrNull() ?: return, source, filter, sortOrder, sourceTitle)) } fun openList(tag: MangaTag) = openList(tag.source, MangaListFilter(tags = setOf(tag)), null) @@ -142,8 +142,8 @@ class AppRouter private constructor( fun openSearch(source: MangaSource, query: String) = openList(source, MangaListFilter(query = query), null) - fun openDetails(manga: Manga) { - startActivity(detailsIntent(contextOrNull() ?: return, manga)) + fun openDetails(manga: Manga, sourceTitle: String? = null) { + startActivity(detailsIntent(contextOrNull() ?: return, manga, sourceTitle)) } fun openDetails(mangaId: Long) { @@ -696,18 +696,20 @@ class AppRouter private constructor( (view.context.findActivity() as? FragmentActivity)?.let(::AppRouter) } - fun detailsIntent(context: Context, manga: Manga) = Intent(context, DetailsActivity::class.java) + fun detailsIntent(context: Context, manga: Manga, sourceTitle: String? = null) = Intent(context, DetailsActivity::class.java) .putExtra(KEY_MANGA, ParcelableManga(manga)) + .putExtra(KEY_SOURCE_TITLE, sourceTitle) .setData(shortMangaUrl(manga.id)) fun detailsIntent(context: Context, mangaId: Long) = Intent(context, DetailsActivity::class.java) .putExtra(KEY_ID, mangaId) .setData(shortMangaUrl(mangaId)) - fun listIntent(context: Context, source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?): Intent = + fun listIntent(context: Context, source: MangaSource, filter: MangaListFilter?, sortOrder: SortOrder?, sourceTitle: String? = null): Intent = Intent(context, MangaListActivity::class.java) .setAction(ACTION_MANGA_EXPLORE) .putExtra(KEY_SOURCE, source.name) + .putExtra(KEY_SOURCE_TITLE, sourceTitle) .apply { if (!filter.isNullOrEmpty()) { putExtra(KEY_FILTER, ParcelableMangaListFilter(filter)) @@ -832,6 +834,7 @@ class AppRouter private constructor( const val KEY_READER_MODE = "reader_mode" const val KEY_SORT_ORDER = "sort_order" const val KEY_SOURCE = "source" + const val KEY_SOURCE_TITLE = "source_title" const val KEY_TAB = "tab" const val KEY_TITLE = "title" const val KEY_URL = "url" diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/MangaIntent.kt b/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/MangaIntent.kt index 3fee21c9b1..e8b096f60f 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/MangaIntent.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/core/nav/MangaIntent.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.SavedStateHandle import io.github.landwarderer.futon.core.model.parcelable.ParcelableManga import io.github.landwarderer.futon.core.nav.AppRouter.Companion.KEY_ID import io.github.landwarderer.futon.core.nav.AppRouter.Companion.KEY_MANGA +import io.github.landwarderer.futon.core.nav.AppRouter.Companion.KEY_SOURCE_TITLE import io.github.landwarderer.futon.core.util.ext.getParcelableCompat import io.github.landwarderer.futon.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.parsers.model.Manga @@ -15,24 +16,28 @@ class MangaIntent private constructor( @JvmField val manga: Manga?, @JvmField val id: Long, @JvmField val uri: Uri?, + @JvmField val sourceTitle: String?, ) { constructor(intent: Intent?) : this( manga = intent?.getParcelableExtraCompat(KEY_MANGA)?.manga, id = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE, uri = intent?.data, + sourceTitle = intent?.getStringExtra(KEY_SOURCE_TITLE), ) constructor(savedStateHandle: SavedStateHandle) : this( manga = savedStateHandle.get(KEY_MANGA)?.manga, id = savedStateHandle[KEY_ID] ?: ID_NONE, uri = savedStateHandle[AppRouter.KEY_DATA], + sourceTitle = savedStateHandle[KEY_SOURCE_TITLE], ) constructor(args: Bundle?) : this( manga = args?.getParcelableCompat(KEY_MANGA)?.manga, id = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE, uri = null, + sourceTitle = args?.getString(KEY_SOURCE_TITLE), ) val mangaId: Long @@ -42,6 +47,6 @@ class MangaIntent private constructor( const val ID_NONE = 0L - fun of(manga: Manga) = MangaIntent(manga, manga.id, null) + fun of(manga: Manga) = MangaIntent(manga, manga.id, null, null) } } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsActivity.kt b/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsActivity.kt index 8435d0bd6c..45d1f2a781 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsActivity.kt @@ -394,7 +394,7 @@ class DetailsActivity : ListItemType.MANGA_GRID, mangaGridItemAD( sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)), - ) { item, view -> + ) { item, _ -> router.openDetails(item.toMangaWithOverride()) }, ).also { rv.adapter = it } @@ -460,7 +460,12 @@ class DetailsActivity : textViewSource.isVisible = false textViewSourceLabel.isVisible = false } else { - textViewSource.textAndVisible = manga.source.getTitle(this@DetailsActivity) + val sourceTitle = manga.source.getTitle(this@DetailsActivity) + textViewSource.textAndVisible = if (sourceTitle == getString(R.string.unknown)) { + viewModel.sourceTitle ?: sourceTitle + } else { + sourceTitle + } textViewSource.setTooltipCompat(manga.source.getSummary(this@DetailsActivity)) textViewSourceLabel.isVisible = textViewSource.isVisible == true } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsViewModel.kt b/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsViewModel.kt index 6619d40315..750ee4a208 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsViewModel.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsViewModel.kt @@ -87,6 +87,7 @@ class DetailsViewModel @Inject constructor( private val intent = MangaIntent(savedStateHandle) private var loadingJob: Job val mangaId = intent.mangaId + val sourceTitle = intent.sourceTitle private val scrobblers: Set<@JvmSuppressWildcards Scrobbler> by lazy { scrobblersProvider.get() } init { diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/explore/data/MangaSourcesRepository.kt b/app/src/main/kotlin/io/github/landwarderer/futon/explore/data/MangaSourcesRepository.kt index 9d141ffe28..516905a990 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/explore/data/MangaSourcesRepository.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/explore/data/MangaSourcesRepository.kt @@ -11,8 +11,10 @@ import io.github.landwarderer.futon.core.LocalizedAppContext import io.github.landwarderer.futon.core.db.MangaDatabase import io.github.landwarderer.futon.core.db.dao.MangaSourcesDao import io.github.landwarderer.futon.core.db.entity.MangaSourceEntity +import io.github.landwarderer.futon.core.model.AnonymousMangaSource import io.github.landwarderer.futon.core.model.MangaSourceInfo import io.github.landwarderer.futon.core.model.getTitle +import io.github.landwarderer.futon.core.model.updateMihonTitle import io.github.landwarderer.futon.core.model.isBroken import io.github.landwarderer.futon.core.model.isNsfw import io.github.landwarderer.futon.core.parser.external.ExternalMangaSource @@ -21,6 +23,7 @@ import io.github.landwarderer.futon.core.prefs.observeAsFlow import io.github.landwarderer.futon.core.ui.util.ReversibleHandle import io.github.landwarderer.futon.core.util.ext.flattenLatest import io.github.landwarderer.futon.mihon.MihonExtensionManager +import io.github.landwarderer.futon.mihon.model.MihonMangaSource import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow @@ -78,7 +81,7 @@ class MangaSourcesRepository @Inject constructor( assimilateNewSources() val skipNsfw = settings.isNsfwContentDisabled return dao.findAllPinned().mapNotNullToSet { - it.source.toMangaSourceOrNull()?.takeUnless { x -> skipNsfw && x.isNsfw() } + it.toMangaSource()?.takeUnless { x -> skipNsfw && x.isNsfw() } } } @@ -192,7 +195,7 @@ class MangaSourcesRepository @Inject constructor( }, ) { skipNsfw, sources -> sources.count { - it.source.toMangaSourceOrNull()?.let { s -> !skipNsfw || !s.isNsfw() } == true + it.toMangaSource()?.let { s -> !skipNsfw || !s.isNsfw() } == true } }.distinctUntilChanged().onStart { assimilateNewSources() } } @@ -231,8 +234,8 @@ class MangaSourcesRepository @Inject constructor( fun observeAll(): Flow>> = dao.observeAll().map { entities -> val result = ArrayList>(entities.size) for (entity in entities) { - val source = entity.source.toMangaSourceOrNull() ?: continue - if (source in allMangaSources) { + val source = entity.toMangaSource() ?: continue + if (source in allMangaSources || source is AnonymousMangaSource || source is MihonMangaSource) { result.add(source to entity.isEnabled) } } @@ -293,12 +296,18 @@ class MangaSourcesRepository @Inject constructor( private suspend fun assimilateNewSources(): Boolean { if (isNewSourcesAssimilated.getAndSet(true)) { + updateMihonTitles() return false } - val new = getNewSources() - if (new.isEmpty()) { - return false + + // Initial cache population from database + dao.findAll().forEach { entity -> + if ((entity.source.startsWith("mihon:") || entity.source.startsWith("MIHON_")) && entity.title != null) { + updateMihonTitle(entity.source, entity.title) + } } + + val new = getNewSources() var maxSortKey = dao.getMaxSortKey() val isAllEnabled = settings.isAllSourcesEnabled val entities = new.map { x -> @@ -310,10 +319,19 @@ class MangaSourcesRepository @Inject constructor( lastUsedAt = 0, isPinned = false, cfState = CloudFlareHelper.PROTECTION_NOT_DETECTED, + title = x.getTitle(context), ) } dao.insertIfAbsent(entities) - return true + updateMihonTitles() + return new.isNotEmpty() + } + + private suspend fun updateMihonTitles() { + val mihonSources = mihonExtensionManager.getMihonMangaSources() + for (source in mihonSources) { + dao.setTitle(source.name, source.displayName) + } } suspend fun isSetupRequired(): Boolean { @@ -323,7 +341,7 @@ class MangaSourcesRepository @Inject constructor( suspend fun setIsPinned(sources: Collection, isPinned: Boolean): ReversibleHandle { setSourcesPinnedImpl(sources, isPinned) return ReversibleHandle { - setSourcesEnabledImpl(sources, !isPinned) + setSourcesPinnedImpl(sources, !isPinned) } } @@ -351,7 +369,7 @@ class MangaSourcesRepository @Inject constructor( result.addAll(MangaParserSource.entries) result.addAll(mihonExtensionManager.getMihonMangaSources()) for (e in entities) { - result.remove(e.source.toMangaSourceOrNull() ?: continue) + result.remove(e.toMangaSource() ?: continue) } return result } @@ -423,7 +441,7 @@ class MangaSourcesRepository @Inject constructor( val isAllEnabled = settings.isAllSourcesEnabled val result = ArrayList(size) for (entity in this) { - val source = entity.source.toMangaSourceOrNull() ?: continue + val source = entity.toMangaSource() ?: continue if (skipNsfwSources && source.isNsfw()) { continue } @@ -458,10 +476,19 @@ class MangaSourcesRepository @Inject constructor( isAllSourcesEnabled } + private fun MangaSourceEntity.toMangaSource(): MangaSource? { + if (source.startsWith("mihon:") || source.startsWith("MIHON_")) { + return mihonExtensionManager.getMihonMangaSourceByName(source) + ?: io.github.landwarderer.futon.core.model.MangaSource(source, title) + } + return MangaParserSource.entries.find { it.name == source } + } + private fun String.toMangaSourceOrNull(): MangaSource? { if (startsWith("mihon:") || startsWith("MIHON_")) { - return mihonExtensionManager.getMihonMangaSourceByName(this) ?: io.github.landwarderer.futon.core.model.MangaSource(this) - } + return mihonExtensionManager.getMihonMangaSourceByName(this) + ?: io.github.landwarderer.futon.core.model.MangaSource(this) + } return MangaParserSource.entries.find { it.name == this } } } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/io/github/landwarderer/futon/explore/ui/ExploreFragment.kt index 2139eb8f84..32a1f5d018 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/explore/ui/ExploreFragment.kt @@ -21,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.github.landwarderer.futon.R import io.github.landwarderer.futon.core.exceptions.resolve.SnackbarErrorObserver import io.github.landwarderer.futon.core.model.LocalMangaSource +import io.github.landwarderer.futon.core.model.getTitle import io.github.landwarderer.futon.core.nav.router import io.github.landwarderer.futon.core.parser.external.ExternalMangaSource import io.github.landwarderer.futon.core.ui.BaseFragment @@ -134,7 +135,7 @@ class ExploreFragment : if (sourceSelectionController?.onItemClick(item.id) == true) { return } - router.openList(item.source, null, null) + router.openList(item.source, null, null, item.source.getTitle(view.context)) } override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean { diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/list/ui/MangaListFragment.kt b/app/src/main/kotlin/io/github/landwarderer/futon/list/ui/MangaListFragment.kt index 1649c05af2..081cf468c6 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/list/ui/MangaListFragment.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/list/ui/MangaListFragment.kt @@ -157,7 +157,7 @@ abstract class MangaListFragment : if (selectionController?.onItemClick(item.id) != true) { val manga = item.toMangaWithOverride() if ((activity as? MangaListActivity)?.showPreview(manga) != true) { - router.openDetails(manga) + router.openDetails(manga, (activity as? MangaListActivity)?.title?.toString()) } } } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/MihonExtensionManager.kt b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/MihonExtensionManager.kt index 983d7b2bba..3d27f015d3 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/MihonExtensionManager.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/MihonExtensionManager.kt @@ -7,10 +7,13 @@ import eu.kanade.tachiyomi.source.Source import io.github.landwarderer.futon.mihon.extensions.runtime.ExternalExtensionManagerFacade import io.github.landwarderer.futon.mihon.model.MihonLoadResult import io.github.landwarderer.futon.mihon.model.MihonMangaSource +import io.github.landwarderer.futon.core.model.updateMihonTitle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject import javax.inject.Singleton @@ -61,7 +64,9 @@ class MihonExtensionManager @Inject constructor( pkgName = pkgName, isNsfw = isNsfw, hasLanguageSuffix = hasLanguageSuffix, - ) + ).also { + updateMihonTitle(it.name, it.displayName) + } }, errorPackageName = { it.pkgName }, errorMessage = { it.message }, @@ -72,6 +77,13 @@ class MihonExtensionManager @Inject constructor( val isLoading: StateFlow = facade.isLoading init { + facade.wrappedSources + .onEach { sources -> + for (source in sources) { + updateMihonTitle(source.name, source.displayName) + } + } + .launchIn(scope) initialize() } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerFacade.kt b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerFacade.kt index 0cf483596d..99409517ea 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerFacade.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerFacade.kt @@ -41,6 +41,7 @@ class ExternalExtensionManagerFacade> = runtime.installedExtensions val failedExtensions: StateFlow> = runtime.failedExtensions + val wrappedSources: StateFlow> = runtime.wrappedSources val isLoading: StateFlow = runtime.isLoading fun initialize() { diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerRuntime.kt b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerRuntime.kt index c7478b7427..8241909bdc 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerRuntime.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/extensions/runtime/ExternalExtensionManagerRuntime.kt @@ -18,6 +18,9 @@ class ExternalExtensionManagerRuntime>(emptyList()) val failedExtensions: StateFlow> = _failedExtensions.asStateFlow() + private val _wrappedSources = MutableStateFlow>(emptyList()) + val wrappedSources: StateFlow> = _wrappedSources.asStateFlow() + private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading.asStateFlow() @@ -47,6 +50,7 @@ class ExternalExtensionManagerRuntime() + .find { it.source == manga.source } + ?.getTitle(this) + router.openDetails(manga, sourceTitle) } } diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/catalog/SourcesCatalogActivity.kt b/app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/catalog/SourcesCatalogActivity.kt index a956e5ba3c..8e5ae2ddd9 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/catalog/SourcesCatalogActivity.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/catalog/SourcesCatalogActivity.kt @@ -15,6 +15,7 @@ import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.combine import io.github.landwarderer.futon.R +import io.github.landwarderer.futon.core.model.getTitle import io.github.landwarderer.futon.core.model.titleResId import io.github.landwarderer.futon.core.nav.router import io.github.landwarderer.futon.core.ui.BaseActivity @@ -94,7 +95,7 @@ class SourcesCatalogActivity : BaseActivity(), } override fun onItemClick(item: SourceCatalogItem.Source, view: View) { - router.openList(item.source, null, null) + router.openList(item.source, null, null, item.source.getTitle(this)) } override fun onItemLongClick(item: SourceCatalogItem.Source, view: View): Boolean { diff --git a/app/src/main/res/layout/item_explore_source_grid.xml b/app/src/main/res/layout/item_explore_source_grid.xml index 918b1e5513..cc92a61ac3 100644 --- a/app/src/main/res/layout/item_explore_source_grid.xml +++ b/app/src/main/res/layout/item_explore_source_grid.xml @@ -27,14 +27,14 @@ From fee44f6e86c883cad633ca9a893f40e782023759 Mon Sep 17 00:00:00 2001 From: Land Date: Sun, 21 Jun 2026 18:11:42 +0530 Subject: [PATCH 2/3] refactor: simplify ContentSource.toMangaSource mapping --- .../landwarderer/futon/mihon/model/ParserModelMappers.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt index dede4c0189..38bfac7299 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt @@ -174,9 +174,6 @@ fun MangaTag.toContentTag(): ContentTag { } fun ContentSource.toMangaSource(): MangaSource { - val src = this - return object : MangaSource { - override val name = src.name - } + return this } From d187f2fc72500d41235d1495701b2cd3b8b3beb7 Mon Sep 17 00:00:00 2001 From: Land Date: Sun, 21 Jun 2026 18:14:46 +0530 Subject: [PATCH 3/3] Add TODO to remove toContentPage mapper --- .../github/landwarderer/futon/mihon/model/ParserModelMappers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt index 38bfac7299..982aef8e39 100644 --- a/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt +++ b/app/src/main/kotlin/io/github/landwarderer/futon/mihon/model/ParserModelMappers.kt @@ -105,7 +105,7 @@ fun ContentPage.toMangaPage(): MangaPage { source = source.toMangaSource() ) } - +// TODO: Remove this function fun MangaPage.toContentPage(source: ContentSource): ContentPage { return ContentPage( id = id,