mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Convert extension details to full Compose
This commit is contained in:
		@@ -11,6 +11,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
@@ -20,9 +21,12 @@ import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.navigationBars
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.layout.statusBarsPadding
 | 
			
		||||
import androidx.compose.foundation.layout.width
 | 
			
		||||
import androidx.compose.foundation.lazy.items
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.outlined.HelpOutline
 | 
			
		||||
import androidx.compose.material.icons.outlined.History
 | 
			
		||||
import androidx.compose.material.icons.outlined.Settings
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.Button
 | 
			
		||||
@@ -41,22 +45,25 @@ import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import androidx.compose.ui.platform.LocalUriHandler
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.text.TextStyle
 | 
			
		||||
import androidx.compose.ui.text.font.FontWeight
 | 
			
		||||
import androidx.compose.ui.text.style.TextAlign
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.presentation.browse.components.ExtensionIcon
 | 
			
		||||
import eu.kanade.presentation.components.AppBar
 | 
			
		||||
import eu.kanade.presentation.components.AppBarActions
 | 
			
		||||
import eu.kanade.presentation.components.DIVIDER_ALPHA
 | 
			
		||||
import eu.kanade.presentation.components.Divider
 | 
			
		||||
import eu.kanade.presentation.components.EmptyScreen
 | 
			
		||||
import eu.kanade.presentation.components.LoadingScreen
 | 
			
		||||
import eu.kanade.presentation.components.PreferenceRow
 | 
			
		||||
import eu.kanade.presentation.components.Scaffold
 | 
			
		||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
 | 
			
		||||
import eu.kanade.presentation.util.horizontalPadding
 | 
			
		||||
import eu.kanade.presentation.util.plus
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.extension.model.Extension
 | 
			
		||||
import eu.kanade.tachiyomi.source.ConfigurableSource
 | 
			
		||||
@@ -66,11 +73,68 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun ExtensionDetailsScreen(
 | 
			
		||||
    nestedScrollInterop: NestedScrollConnection,
 | 
			
		||||
    navigateUp: () -> Unit,
 | 
			
		||||
    presenter: ExtensionDetailsPresenter,
 | 
			
		||||
    onClickSourcePreferences: (sourceId: Long) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val uriHandler = LocalUriHandler.current
 | 
			
		||||
 | 
			
		||||
    Scaffold(
 | 
			
		||||
        modifier = Modifier.statusBarsPadding(),
 | 
			
		||||
        topBar = {
 | 
			
		||||
            AppBar(
 | 
			
		||||
                title = stringResource(R.string.label_extension_info),
 | 
			
		||||
                navigateUp = navigateUp,
 | 
			
		||||
                actions = {
 | 
			
		||||
                    AppBarActions(
 | 
			
		||||
                        actions = buildList {
 | 
			
		||||
                            if (presenter.extension?.isUnofficial == false) {
 | 
			
		||||
                                add(
 | 
			
		||||
                                    AppBar.Action(
 | 
			
		||||
                                        title = stringResource(R.string.whats_new),
 | 
			
		||||
                                        icon = Icons.Outlined.History,
 | 
			
		||||
                                        onClick = { uriHandler.openUri(presenter.getChangelogUrl()) },
 | 
			
		||||
                                    ),
 | 
			
		||||
                                )
 | 
			
		||||
                                add(
 | 
			
		||||
                                    AppBar.Action(
 | 
			
		||||
                                        title = stringResource(R.string.action_faq_and_guides),
 | 
			
		||||
                                        icon = Icons.Outlined.HelpOutline,
 | 
			
		||||
                                        onClick = { uriHandler.openUri(presenter.getReadmeUrl()) },
 | 
			
		||||
                                    ),
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                            addAll(
 | 
			
		||||
                                listOf(
 | 
			
		||||
                                    AppBar.OverflowAction(
 | 
			
		||||
                                        title = stringResource(R.string.action_enable_all),
 | 
			
		||||
                                        onClick = { presenter.toggleSources(true) },
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    AppBar.OverflowAction(
 | 
			
		||||
                                        title = stringResource(R.string.action_disable_all),
 | 
			
		||||
                                        onClick = { presenter.toggleSources(false) },
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    AppBar.OverflowAction(
 | 
			
		||||
                                        title = stringResource(R.string.pref_clear_cookies),
 | 
			
		||||
                                        onClick = { presenter.clearCookies() },
 | 
			
		||||
                                    ),
 | 
			
		||||
                                ),
 | 
			
		||||
                            )
 | 
			
		||||
                        },
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        },
 | 
			
		||||
    ) { paddingValues ->
 | 
			
		||||
        ExtensionDetails(paddingValues, presenter, onClickSourcePreferences)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun ExtensionDetails(
 | 
			
		||||
    paddingValues: PaddingValues,
 | 
			
		||||
    presenter: ExtensionDetailsPresenter,
 | 
			
		||||
    onClickUninstall: () -> Unit,
 | 
			
		||||
    onClickSourcePreferences: (sourceId: Long) -> Unit,
 | 
			
		||||
    onClickSource: (sourceId: Long) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    when {
 | 
			
		||||
        presenter.isLoading -> LoadingScreen()
 | 
			
		||||
@@ -81,8 +145,7 @@ fun ExtensionDetailsScreen(
 | 
			
		||||
            var showNsfwWarning by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
            ScrollbarLazyColumn(
 | 
			
		||||
                modifier = Modifier.nestedScroll(nestedScrollInterop),
 | 
			
		||||
                contentPadding = WindowInsets.navigationBars.asPaddingValues(),
 | 
			
		||||
                contentPadding = paddingValues + WindowInsets.navigationBars.asPaddingValues(),
 | 
			
		||||
            ) {
 | 
			
		||||
                when {
 | 
			
		||||
                    extension.isUnofficial ->
 | 
			
		||||
@@ -98,7 +161,7 @@ fun ExtensionDetailsScreen(
 | 
			
		||||
                item {
 | 
			
		||||
                    DetailsHeader(
 | 
			
		||||
                        extension = extension,
 | 
			
		||||
                        onClickUninstall = onClickUninstall,
 | 
			
		||||
                        onClickUninstall = { presenter.uninstallExtension() },
 | 
			
		||||
                        onClickAppInfo = {
 | 
			
		||||
                            Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
 | 
			
		||||
                                data = Uri.fromParts("package", extension.pkgName, null)
 | 
			
		||||
@@ -119,7 +182,7 @@ fun ExtensionDetailsScreen(
 | 
			
		||||
                        modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                        source = source,
 | 
			
		||||
                        onClickSourcePreferences = onClickSourcePreferences,
 | 
			
		||||
                        onClickSource = onClickSource,
 | 
			
		||||
                        onClickSource = { presenter.toggleSource(it) },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,135 +2,35 @@ package eu.kanade.tachiyomi.ui.browse.extension.details
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.MenuInflater
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import androidx.core.os.bundleOf
 | 
			
		||||
import eu.kanade.presentation.browse.ExtensionDetailsScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrl
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
@SuppressLint("RestrictedApi")
 | 
			
		||||
class ExtensionDetailsController(bundle: Bundle? = null) :
 | 
			
		||||
    ComposeController<ExtensionDetailsPresenter>(bundle) {
 | 
			
		||||
 | 
			
		||||
    private val network: NetworkHelper by injectLazy()
 | 
			
		||||
class ExtensionDetailsController(
 | 
			
		||||
    bundle: Bundle? = null,
 | 
			
		||||
) : FullComposeController<ExtensionDetailsPresenter>(bundle) {
 | 
			
		||||
 | 
			
		||||
    constructor(pkgName: String) : this(
 | 
			
		||||
        bundleOf(PKGNAME_KEY to pkgName),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        setHasOptionsMenu(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getTitle() = resources?.getString(R.string.label_extension_info)
 | 
			
		||||
 | 
			
		||||
    override fun createPresenter() = ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
 | 
			
		||||
    override fun ComposeContent() {
 | 
			
		||||
        ExtensionDetailsScreen(
 | 
			
		||||
            nestedScrollInterop = nestedScrollInterop,
 | 
			
		||||
            navigateUp = router::popCurrentController,
 | 
			
		||||
            presenter = presenter,
 | 
			
		||||
            onClickUninstall = { presenter.uninstallExtension() },
 | 
			
		||||
            onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) },
 | 
			
		||||
            onClickSource = { presenter.toggleSource(it) },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
			
		||||
        inflater.inflate(R.menu.extension_details, menu)
 | 
			
		||||
 | 
			
		||||
        presenter.extension?.let { extension ->
 | 
			
		||||
            menu.findItem(R.id.action_history).isVisible = !extension.isUnofficial
 | 
			
		||||
            menu.findItem(R.id.action_faq_and_guides).isVisible = !extension.isUnofficial
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
        when (item.itemId) {
 | 
			
		||||
            R.id.action_history -> openChangelog()
 | 
			
		||||
            R.id.action_faq_and_guides -> openReadme()
 | 
			
		||||
            R.id.action_enable_all -> toggleAllSources(true)
 | 
			
		||||
            R.id.action_disable_all -> toggleAllSources(false)
 | 
			
		||||
            R.id.action_clear_cookies -> clearCookies()
 | 
			
		||||
        }
 | 
			
		||||
        return super.onOptionsItemSelected(item)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onExtensionUninstalled() {
 | 
			
		||||
        router.popCurrentController()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun toggleAllSources(enable: Boolean) {
 | 
			
		||||
        presenter.toggleSources(enable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun openChangelog() {
 | 
			
		||||
        val extension = presenter.extension!!
 | 
			
		||||
        val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
 | 
			
		||||
        val pkgFactory = extension.pkgFactory
 | 
			
		||||
        if (extension.hasChangelog) {
 | 
			
		||||
            val url = createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/CHANGELOG.md")
 | 
			
		||||
            openInBrowser(url)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Falling back on GitHub commit history because there is no explicit changelog in extension
 | 
			
		||||
        val url = createUrl(URL_EXTENSION_COMMITS, pkgName, pkgFactory)
 | 
			
		||||
        openInBrowser(url)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun openReadme() {
 | 
			
		||||
        val extension = presenter.extension!!
 | 
			
		||||
 | 
			
		||||
        if (!extension.hasReadme) {
 | 
			
		||||
            openInBrowser("https://tachiyomi.org/help/faq/#extensions")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
 | 
			
		||||
        val pkgFactory = extension.pkgFactory
 | 
			
		||||
        val url = createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/README.md")
 | 
			
		||||
        openInBrowser(url)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String {
 | 
			
		||||
        return if (!pkgFactory.isNullOrEmpty()) {
 | 
			
		||||
            when (path.isEmpty()) {
 | 
			
		||||
                true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
 | 
			
		||||
                else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            url + "/src/" + pkgName.replace(".", "/") + path
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun clearCookies() {
 | 
			
		||||
        val urls = presenter.extension?.sources
 | 
			
		||||
            ?.filterIsInstance<HttpSource>()
 | 
			
		||||
            ?.map { it.baseUrl }
 | 
			
		||||
            ?.distinct() ?: emptyList()
 | 
			
		||||
 | 
			
		||||
        val cleared = urls.sumOf {
 | 
			
		||||
            network.cookieManager.remove(it.toHttpUrl())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        logcat { "Cleared $cleared cookies for: ${urls.joinToString()}" }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private const val PKGNAME_KEY = "pkg_name"
 | 
			
		||||
private const val URL_EXTENSION_COMMITS = "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
 | 
			
		||||
private const val URL_EXTENSION_BLOB = "https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,14 +7,18 @@ import eu.kanade.domain.source.interactor.ToggleSource
 | 
			
		||||
import eu.kanade.presentation.browse.ExtensionDetailsState
 | 
			
		||||
import eu.kanade.presentation.browse.ExtensionDetailsStateImpl
 | 
			
		||||
import eu.kanade.tachiyomi.extension.ExtensionManager
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.launchIO
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.withUIContext
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrl
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
@@ -24,6 +28,7 @@ class ExtensionDetailsPresenter(
 | 
			
		||||
    private val context: Application = Injekt.get(),
 | 
			
		||||
    private val getExtensionSources: GetExtensionSources = Injekt.get(),
 | 
			
		||||
    private val toggleSource: ToggleSource = Injekt.get(),
 | 
			
		||||
    private val network: NetworkHelper = Injekt.get(),
 | 
			
		||||
    private val extensionManager: ExtensionManager = Injekt.get(),
 | 
			
		||||
) : BasePresenter<ExtensionDetailsController>(), ExtensionDetailsState by state {
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +37,7 @@ class ExtensionDetailsPresenter(
 | 
			
		||||
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            extensionManager.getInstalledExtensionsFlow()
 | 
			
		||||
                .map { it.firstOrNull { it.pkgName == pkgName } }
 | 
			
		||||
                .map { it.firstOrNull { pkg-> pkg.pkgName == pkgName } }
 | 
			
		||||
                .collectLatest { extension ->
 | 
			
		||||
                    // If extension is null it's most likely uninstalled
 | 
			
		||||
                    if (extension == null) {
 | 
			
		||||
@@ -65,6 +70,44 @@ class ExtensionDetailsPresenter(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getChangelogUrl(): String {
 | 
			
		||||
        extension ?: return ""
 | 
			
		||||
 | 
			
		||||
        val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
 | 
			
		||||
        val pkgFactory = extension.pkgFactory
 | 
			
		||||
        if (extension.hasChangelog) {
 | 
			
		||||
            return createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/CHANGELOG.md")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Falling back on GitHub commit history because there is no explicit changelog in extension
 | 
			
		||||
        return createUrl(URL_EXTENSION_COMMITS, pkgName, pkgFactory)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getReadmeUrl(): String {
 | 
			
		||||
        extension ?: return ""
 | 
			
		||||
 | 
			
		||||
        if (!extension.hasReadme) {
 | 
			
		||||
            return "https://tachiyomi.org/help/faq/#extensions"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
 | 
			
		||||
        val pkgFactory = extension.pkgFactory
 | 
			
		||||
        return createUrl(URL_EXTENSION_BLOB, pkgName, pkgFactory, "/README.md")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun clearCookies() {
 | 
			
		||||
        val urls = extension?.sources
 | 
			
		||||
            ?.filterIsInstance<HttpSource>()
 | 
			
		||||
            ?.map { it.baseUrl }
 | 
			
		||||
            ?.distinct() ?: emptyList()
 | 
			
		||||
 | 
			
		||||
        val cleared = urls.sumOf {
 | 
			
		||||
            network.cookieManager.remove(it.toHttpUrl())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        logcat { "Cleared $cleared cookies for: ${urls.joinToString()}" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun uninstallExtension() {
 | 
			
		||||
        val extension = extension ?: return
 | 
			
		||||
        extensionManager.uninstallExtension(extension.pkgName)
 | 
			
		||||
@@ -77,6 +120,17 @@ class ExtensionDetailsPresenter(
 | 
			
		||||
    fun toggleSources(enable: Boolean) {
 | 
			
		||||
        extension?.sources?.forEach { toggleSource.await(it.id, enable) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createUrl(url: String, pkgName: String, pkgFactory: String?, path: String = ""): String {
 | 
			
		||||
        return if (!pkgFactory.isNullOrEmpty()) {
 | 
			
		||||
            when (path.isEmpty()) {
 | 
			
		||||
                true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
 | 
			
		||||
                else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            url + "/src/" + pkgName.replace(".", "/") + path
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data class ExtensionSourceItem(
 | 
			
		||||
@@ -84,3 +138,6 @@ data class ExtensionSourceItem(
 | 
			
		||||
    val enabled: Boolean,
 | 
			
		||||
    val labelAsName: Boolean,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
private const val URL_EXTENSION_COMMITS = "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
 | 
			
		||||
private const val URL_EXTENSION_BLOB = "https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master"
 | 
			
		||||
 
 | 
			
		||||
@@ -99,25 +99,14 @@ class MangaPresenter(
 | 
			
		||||
) : BasePresenter<MangaController>() {
 | 
			
		||||
 | 
			
		||||
    private val _state: MutableStateFlow<MangaScreenState> = MutableStateFlow(MangaScreenState.Loading)
 | 
			
		||||
 | 
			
		||||
    val state = _state.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    private val successState: MangaScreenState.Success?
 | 
			
		||||
        get() = state.value as? MangaScreenState.Success
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription to update the manga from the source.
 | 
			
		||||
     */
 | 
			
		||||
    private var fetchMangaJob: Job? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription to retrieve the new list of chapters from the source.
 | 
			
		||||
     */
 | 
			
		||||
    private var fetchChaptersJob: Job? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription to observe download status changes.
 | 
			
		||||
     */
 | 
			
		||||
    private var observeDownloadsStatusJob: Job? = null
 | 
			
		||||
    private var observeDownloadsPageJob: Job? = null
 | 
			
		||||
 | 
			
		||||
@@ -138,7 +127,7 @@ class MangaPresenter(
 | 
			
		||||
    val isFavoritedManga: Boolean
 | 
			
		||||
        get() = manga?.favorite ?: false
 | 
			
		||||
 | 
			
		||||
    val processedChapters: Sequence<ChapterItem>?
 | 
			
		||||
    private val processedChapters: Sequence<ChapterItem>?
 | 
			
		||||
        get() = successState?.processedChapters
 | 
			
		||||
 | 
			
		||||
    private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
 | 
			
		||||
@@ -164,8 +153,6 @@ class MangaPresenter(
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        // Manga info - start
 | 
			
		||||
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            val manga = getMangaAndChapters.awaitManga(mangaId)
 | 
			
		||||
 | 
			
		||||
@@ -221,15 +208,11 @@ class MangaPresenter(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        preferences.incognitoMode()
 | 
			
		||||
            .asHotFlow { incognito ->
 | 
			
		||||
                incognitoMode = incognito
 | 
			
		||||
            }
 | 
			
		||||
            .asHotFlow { incognitoMode = it }
 | 
			
		||||
            .launchIn(presenterScope)
 | 
			
		||||
 | 
			
		||||
        preferences.downloadedOnly()
 | 
			
		||||
            .asHotFlow { downloadedOnly ->
 | 
			
		||||
                downloadedOnlyMode = downloadedOnly
 | 
			
		||||
            }
 | 
			
		||||
            .asHotFlow { downloadedOnlyMode = it }
 | 
			
		||||
            .launchIn(presenterScope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -239,6 +222,7 @@ class MangaPresenter(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Manga info - start
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch manga information from source.
 | 
			
		||||
     */
 | 
			
		||||
@@ -395,7 +379,7 @@ class MangaPresenter(
 | 
			
		||||
     * @param manga the manga to get categories from.
 | 
			
		||||
     * @return Array of category ids the manga is in, if none returns default id
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun getMangaCategoryIds(manga: DomainManga): List<Long> {
 | 
			
		||||
    private suspend fun getMangaCategoryIds(manga: DomainManga): List<Long> {
 | 
			
		||||
        return getCategories.await(manga.id)
 | 
			
		||||
            .map { it.id }
 | 
			
		||||
    }
 | 
			
		||||
@@ -420,7 +404,7 @@ class MangaPresenter(
 | 
			
		||||
        moveMangaToCategory(categoryIds)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun moveMangaToCategory(categoryIds: List<Long>) {
 | 
			
		||||
    private fun moveMangaToCategory(categoryIds: List<Long>) {
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            setMangaCategories.await(mangaId, categoryIds)
 | 
			
		||||
        }
 | 
			
		||||
@@ -951,7 +935,7 @@ class MangaPresenter(
 | 
			
		||||
                                .lastOrNull()
 | 
			
		||||
                                ?.chapterNumber?.toDouble() ?: -1.0
 | 
			
		||||
 | 
			
		||||
                            if (latestLocalReadChapterNumber >= track.lastChapterRead) {
 | 
			
		||||
                            if (latestLocalReadChapterNumber > track.lastChapterRead) {
 | 
			
		||||
                                val updatedTrack = track.copy(
 | 
			
		||||
                                    lastChapterRead = latestLocalReadChapterNumber,
 | 
			
		||||
                                )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto">
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_history"
 | 
			
		||||
        android:icon="@drawable/ic_history_24dp"
 | 
			
		||||
        android:title="@string/whats_new"
 | 
			
		||||
        android:visible="false"
 | 
			
		||||
        app:iconTint="?attr/colorOnSurface"
 | 
			
		||||
        app:showAsAction="ifRoom" />
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_faq_and_guides"
 | 
			
		||||
        android:icon="@drawable/ic_help_24dp"
 | 
			
		||||
        android:title="@string/action_faq_and_guides"
 | 
			
		||||
        android:visible="false"
 | 
			
		||||
        app:iconTint="?attr/colorOnSurface"
 | 
			
		||||
        app:showAsAction="ifRoom" />
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_enable_all"
 | 
			
		||||
        android:title="@string/action_enable_all"
 | 
			
		||||
        app:showAsAction="never" />
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_disable_all"
 | 
			
		||||
        android:title="@string/action_disable_all"
 | 
			
		||||
        app:showAsAction="never" />
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_clear_cookies"
 | 
			
		||||
        android:title="@string/pref_clear_cookies"
 | 
			
		||||
        app:showAsAction="never" />
 | 
			
		||||
 | 
			
		||||
</menu>
 | 
			
		||||
		Reference in New Issue
	
	Block a user