mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Migrate extension details page to Compose
This commit is contained in:
		@@ -3,6 +3,7 @@ package eu.kanade.domain
 | 
			
		||||
import eu.kanade.data.history.HistoryRepositoryImpl
 | 
			
		||||
import eu.kanade.data.manga.MangaRepositoryImpl
 | 
			
		||||
import eu.kanade.data.source.SourceRepositoryImpl
 | 
			
		||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
 | 
			
		||||
import eu.kanade.domain.extension.interactor.GetExtensionUpdates
 | 
			
		||||
import eu.kanade.domain.extension.interactor.GetExtensions
 | 
			
		||||
import eu.kanade.domain.history.interactor.DeleteHistoryTable
 | 
			
		||||
@@ -43,6 +44,7 @@ class DomainModule : InjektModule {
 | 
			
		||||
        addFactory { RemoveHistoryByMangaId(get()) }
 | 
			
		||||
 | 
			
		||||
        addFactory { GetExtensions(get(), get()) }
 | 
			
		||||
        addFactory { GetExtensionSources(get()) }
 | 
			
		||||
        addFactory { GetExtensionUpdates(get(), get()) }
 | 
			
		||||
 | 
			
		||||
        addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package eu.kanade.domain.extension.interactor
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.extension.model.Extension
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
 | 
			
		||||
class GetExtensionSources(
 | 
			
		||||
    private val preferences: PreferencesHelper,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun subscribe(extension: Extension.Installed): Flow<List<ExtensionSourceItem>> {
 | 
			
		||||
        val isMultiSource = extension.sources.size > 1
 | 
			
		||||
        val isMultiLangSingleSource =
 | 
			
		||||
            isMultiSource && extension.sources.map { it.name }.distinct().size == 1
 | 
			
		||||
 | 
			
		||||
        return preferences.disabledSources().asFlow().map { disabledSources ->
 | 
			
		||||
            fun Source.isEnabled() = id.toString() !in disabledSources
 | 
			
		||||
 | 
			
		||||
            extension.sources
 | 
			
		||||
                .map { source ->
 | 
			
		||||
                    ExtensionSourceItem(
 | 
			
		||||
                        source = source,
 | 
			
		||||
                        enabled = source.isEnabled(),
 | 
			
		||||
                        labelAsName = isMultiSource && isMultiLangSingleSource.not(),
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,12 +9,15 @@ class ToggleSource(
 | 
			
		||||
    private val preferences: PreferencesHelper,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun await(source: Source) {
 | 
			
		||||
        val isEnabled = source.id.toString() !in preferences.disabledSources().get()
 | 
			
		||||
        if (isEnabled) {
 | 
			
		||||
            preferences.disabledSources() += source.id.toString()
 | 
			
		||||
    fun await(source: Source, enable: Boolean = source.id.toString() in preferences.disabledSources().get()) {
 | 
			
		||||
        await(source.id, enable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun await(sourceId: Long, enable: Boolean = sourceId.toString() in preferences.disabledSources().get()) {
 | 
			
		||||
        if (enable) {
 | 
			
		||||
            preferences.disabledSources() -= sourceId.toString()
 | 
			
		||||
        } else {
 | 
			
		||||
            preferences.disabledSources() -= source.id.toString()
 | 
			
		||||
            preferences.disabledSources() += sourceId.toString()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,237 @@
 | 
			
		||||
package eu.kanade.presentation.browse
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.StringRes
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
import androidx.compose.foundation.layout.Spacer
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
import androidx.compose.foundation.layout.asPaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		||||
import androidx.compose.foundation.layout.height
 | 
			
		||||
import androidx.compose.foundation.layout.navigationBars
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.width
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.foundation.lazy.items
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.outlined.Settings
 | 
			
		||||
import androidx.compose.material3.Button
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.IconButton
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.OutlinedButton
 | 
			
		||||
import androidx.compose.material3.Switch
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
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.res.stringResource
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.presentation.browse.components.ExtensionIcon
 | 
			
		||||
import eu.kanade.presentation.components.Divider
 | 
			
		||||
import eu.kanade.presentation.components.EmptyScreen
 | 
			
		||||
import eu.kanade.presentation.components.PreferenceRow
 | 
			
		||||
import eu.kanade.presentation.util.horizontalPadding
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.extension.model.Extension
 | 
			
		||||
import eu.kanade.tachiyomi.source.ConfigurableSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsPresenter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionSourceItem
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun ExtensionDetailsScreen(
 | 
			
		||||
    nestedScrollInterop: NestedScrollConnection,
 | 
			
		||||
    presenter: ExtensionDetailsPresenter,
 | 
			
		||||
    onClickUninstall: () -> Unit,
 | 
			
		||||
    onClickAppInfo: () -> Unit,
 | 
			
		||||
    onClickSourcePreferences: (sourceId: Long) -> Unit,
 | 
			
		||||
    onClickSource: (sourceId: Long) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val extension = presenter.extension
 | 
			
		||||
 | 
			
		||||
    if (extension == null) {
 | 
			
		||||
        EmptyScreen(textResource = R.string.empty_screen)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val sources by presenter.sourcesState.collectAsState()
 | 
			
		||||
 | 
			
		||||
    LazyColumn(
 | 
			
		||||
        modifier = Modifier.nestedScroll(nestedScrollInterop),
 | 
			
		||||
        contentPadding = WindowInsets.navigationBars.asPaddingValues(),
 | 
			
		||||
    ) {
 | 
			
		||||
        if (extension.isObsolete) {
 | 
			
		||||
            item {
 | 
			
		||||
                WarningBanner(R.string.obsolete_extension_message)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (extension.isUnofficial) {
 | 
			
		||||
            item {
 | 
			
		||||
                WarningBanner(R.string.unofficial_extension_message)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
            DetailsHeader(extension, onClickUninstall, onClickAppInfo)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        items(
 | 
			
		||||
            items = sources,
 | 
			
		||||
            key = { it.source.id },
 | 
			
		||||
        ) { source ->
 | 
			
		||||
            SourceSwitchPreference(
 | 
			
		||||
                modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                source = source,
 | 
			
		||||
                onClickSourcePreferences = onClickSourcePreferences,
 | 
			
		||||
                onClickSource = onClickSource,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun WarningBanner(@StringRes textRes: Int) {
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxWidth()
 | 
			
		||||
            .background(MaterialTheme.colorScheme.error)
 | 
			
		||||
            .padding(16.dp),
 | 
			
		||||
        contentAlignment = Alignment.Center,
 | 
			
		||||
    ) {
 | 
			
		||||
        Text(
 | 
			
		||||
            text = stringResource(textRes),
 | 
			
		||||
            color = MaterialTheme.colorScheme.onError,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun DetailsHeader(
 | 
			
		||||
    extension: Extension,
 | 
			
		||||
    onClickUninstall: () -> Unit,
 | 
			
		||||
    onClickAppInfo: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val context = LocalContext.current
 | 
			
		||||
 | 
			
		||||
    Column {
 | 
			
		||||
        Row(
 | 
			
		||||
            modifier = Modifier.padding(
 | 
			
		||||
                start = horizontalPadding,
 | 
			
		||||
                end = horizontalPadding,
 | 
			
		||||
                top = 16.dp,
 | 
			
		||||
                bottom = 8.dp,
 | 
			
		||||
            ),
 | 
			
		||||
        ) {
 | 
			
		||||
            ExtensionIcon(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .height(56.dp)
 | 
			
		||||
                    .width(56.dp),
 | 
			
		||||
                extension = extension,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            Column(
 | 
			
		||||
                modifier = Modifier.padding(start = 16.dp),
 | 
			
		||||
            ) {
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = extension.name,
 | 
			
		||||
                    style = MaterialTheme.typography.titleMedium,
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = stringResource(R.string.ext_version_info, extension.versionName),
 | 
			
		||||
                    style = MaterialTheme.typography.bodySmall,
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = stringResource(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context)),
 | 
			
		||||
                    style = MaterialTheme.typography.bodySmall,
 | 
			
		||||
                )
 | 
			
		||||
                if (extension.isNsfw) {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        text = stringResource(R.string.ext_nsfw_warning),
 | 
			
		||||
                        color = MaterialTheme.colorScheme.error,
 | 
			
		||||
                        style = MaterialTheme.typography.bodySmall,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = extension.pkgName,
 | 
			
		||||
                    style = MaterialTheme.typography.bodySmall,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Row(
 | 
			
		||||
            modifier = Modifier.padding(
 | 
			
		||||
                start = horizontalPadding,
 | 
			
		||||
                end = horizontalPadding,
 | 
			
		||||
                top = 8.dp,
 | 
			
		||||
                bottom = 16.dp,
 | 
			
		||||
            ),
 | 
			
		||||
        ) {
 | 
			
		||||
            OutlinedButton(
 | 
			
		||||
                modifier = Modifier.weight(1f),
 | 
			
		||||
                onClick = onClickUninstall,
 | 
			
		||||
            ) {
 | 
			
		||||
                Text(stringResource(R.string.ext_uninstall))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Spacer(Modifier.width(16.dp))
 | 
			
		||||
 | 
			
		||||
            Button(
 | 
			
		||||
                modifier = Modifier.weight(1f),
 | 
			
		||||
                onClick = onClickAppInfo,
 | 
			
		||||
            ) {
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = stringResource(R.string.ext_app_info),
 | 
			
		||||
                    color = MaterialTheme.colorScheme.onPrimary,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Divider()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun SourceSwitchPreference(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    source: ExtensionSourceItem,
 | 
			
		||||
    onClickSourcePreferences: (sourceId: Long) -> Unit,
 | 
			
		||||
    onClickSource: (sourceId: Long) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val context = LocalContext.current
 | 
			
		||||
 | 
			
		||||
    PreferenceRow(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        title = if (source.labelAsName) {
 | 
			
		||||
            source.source.toString()
 | 
			
		||||
        } else {
 | 
			
		||||
            LocaleHelper.getSourceDisplayName(source.source.lang, context)
 | 
			
		||||
        },
 | 
			
		||||
        onClick = { onClickSource(source.source.id) },
 | 
			
		||||
        action = {
 | 
			
		||||
            Row(
 | 
			
		||||
                verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
            ) {
 | 
			
		||||
                if (source.source is ConfigurableSource) {
 | 
			
		||||
                    IconButton(onClick = { onClickSourcePreferences(source.source.id) }) {
 | 
			
		||||
                        Icon(
 | 
			
		||||
                            imageVector = Icons.Outlined.Settings,
 | 
			
		||||
                            contentDescription = stringResource(R.string.label_settings),
 | 
			
		||||
                            tint = MaterialTheme.colorScheme.onSurface,
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Switch(checked = source.enabled, onCheckedChange = null)
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.presentation.source
 | 
			
		||||
package eu.kanade.presentation.browse
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
import androidx.compose.foundation.layout.asPaddingValues
 | 
			
		||||
@@ -51,7 +51,7 @@ fun MigrateMangaContent(
 | 
			
		||||
    onClickCover: (Manga) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    if (list.isEmpty()) {
 | 
			
		||||
        EmptyScreen(textResource = R.string.migrate_empty_screen)
 | 
			
		||||
        EmptyScreen(textResource = R.string.empty_screen)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
    LazyColumn(
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.presentation.source
 | 
			
		||||
package eu.kanade.presentation.browse
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
import androidx.compose.foundation.layout.asPaddingValues
 | 
			
		||||
@@ -17,10 +17,10 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.browse.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.presentation.components.EmptyScreen
 | 
			
		||||
import eu.kanade.presentation.components.ItemBadges
 | 
			
		||||
import eu.kanade.presentation.components.LoadingScreen
 | 
			
		||||
import eu.kanade.presentation.source.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.presentation.theme.header
 | 
			
		||||
import eu.kanade.presentation.util.horizontalPadding
 | 
			
		||||
import eu.kanade.presentation.util.plus
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.presentation.source
 | 
			
		||||
package eu.kanade.presentation.browse
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
import androidx.compose.foundation.layout.asPaddingValues
 | 
			
		||||
@@ -16,10 +16,10 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.browse.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.presentation.components.EmptyScreen
 | 
			
		||||
import eu.kanade.presentation.components.LoadingScreen
 | 
			
		||||
import eu.kanade.presentation.components.PreferenceRow
 | 
			
		||||
import eu.kanade.presentation.source.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterPresenter
 | 
			
		||||
@@ -59,6 +59,7 @@ fun SourceFilterContent(
 | 
			
		||||
        EmptyScreen(textResource = R.string.source_filter_empty_screen)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LazyColumn(
 | 
			
		||||
        modifier = Modifier.nestedScroll(nestedScrollInterop),
 | 
			
		||||
        contentPadding = WindowInsets.navigationBars.asPaddingValues(),
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.presentation.source
 | 
			
		||||
package eu.kanade.presentation.browse
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
@@ -32,9 +32,9 @@ import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.domain.source.model.Pin
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.browse.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.presentation.components.EmptyScreen
 | 
			
		||||
import eu.kanade.presentation.components.LoadingScreen
 | 
			
		||||
import eu.kanade.presentation.source.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.presentation.theme.header
 | 
			
		||||
import eu.kanade.presentation.util.horizontalPadding
 | 
			
		||||
import eu.kanade.presentation.util.plus
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package eu.kanade.presentation.source.components
 | 
			
		||||
package eu.kanade.presentation.browse.components
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.RowScope
 | 
			
		||||
@@ -9,8 +9,6 @@ import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.text.style.TextOverflow
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
 | 
			
		||||
import eu.kanade.presentation.browse.components.SourceIcon
 | 
			
		||||
import eu.kanade.presentation.util.horizontalPadding
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
 | 
			
		||||
@@ -1,59 +1,30 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.extension.details
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.TypedValue
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.MenuInflater
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.appcompat.view.ContextThemeWrapper
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import androidx.core.os.bundleOf
 | 
			
		||||
import androidx.preference.PreferenceGroupAdapter
 | 
			
		||||
import androidx.preference.PreferenceManager
 | 
			
		||||
import androidx.preference.PreferenceScreen
 | 
			
		||||
import androidx.preference.SwitchPreferenceCompat
 | 
			
		||||
import androidx.recyclerview.widget.ConcatAdapter
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import dev.chrisbanes.insetter.applyInsetter
 | 
			
		||||
import eu.kanade.presentation.browse.ExtensionDetailsScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.databinding.ExtensionDetailControllerBinding
 | 
			
		||||
import eu.kanade.tachiyomi.extension.model.Extension
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.ConfigurableSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.getPreferenceKey
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.DSL
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.minusAssign
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.onChange
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.plusAssign
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.switchPreference
 | 
			
		||||
import eu.kanade.tachiyomi.util.preference.switchSettingsPreference
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
import kotlinx.coroutines.flow.launchIn
 | 
			
		||||
import kotlinx.coroutines.flow.onEach
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrl
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
@SuppressLint("RestrictedApi")
 | 
			
		||||
class ExtensionDetailsController(bundle: Bundle? = null) :
 | 
			
		||||
    NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle) {
 | 
			
		||||
    ComposeController<ExtensionDetailsPresenter>(bundle) {
 | 
			
		||||
 | 
			
		||||
    private val preferences: PreferencesHelper by injectLazy()
 | 
			
		||||
    private val network: NetworkHelper by injectLazy()
 | 
			
		||||
 | 
			
		||||
    private var preferenceScreen: PreferenceScreen? = null
 | 
			
		||||
 | 
			
		||||
    constructor(pkgName: String) : this(
 | 
			
		||||
        bundleOf(PKGNAME_KEY to pkgName),
 | 
			
		||||
    )
 | 
			
		||||
@@ -62,122 +33,22 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
 | 
			
		||||
        setHasOptionsMenu(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding {
 | 
			
		||||
        val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
 | 
			
		||||
        return ExtensionDetailControllerBinding.inflate(themedInflater)
 | 
			
		||||
    }
 | 
			
		||||
    override fun getTitle() = resources?.getString(R.string.label_extension_info)
 | 
			
		||||
 | 
			
		||||
    override fun createPresenter(): ExtensionDetailsPresenter {
 | 
			
		||||
        return ExtensionDetailsPresenter(this, args.getString(PKGNAME_KEY)!!)
 | 
			
		||||
    }
 | 
			
		||||
    override fun createPresenter() = ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
 | 
			
		||||
 | 
			
		||||
    override fun getTitle(): String? {
 | 
			
		||||
        return resources?.getString(R.string.label_extension_info)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("PrivateResource")
 | 
			
		||||
    override fun onViewCreated(view: View) {
 | 
			
		||||
        super.onViewCreated(view)
 | 
			
		||||
 | 
			
		||||
        binding.extensionPrefsRecycler.applyInsetter {
 | 
			
		||||
            type(navigationBars = true) {
 | 
			
		||||
                padding()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val extension = presenter.extension ?: return
 | 
			
		||||
        val context = view.context
 | 
			
		||||
 | 
			
		||||
        binding.extensionPrefsRecycler.layoutManager = LinearLayoutManager(context)
 | 
			
		||||
        binding.extensionPrefsRecycler.adapter = ConcatAdapter(
 | 
			
		||||
            ExtensionDetailsHeaderAdapter(presenter),
 | 
			
		||||
            initPreferencesAdapter(context, extension),
 | 
			
		||||
    @Composable
 | 
			
		||||
    override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
 | 
			
		||||
        ExtensionDetailsScreen(
 | 
			
		||||
            nestedScrollInterop = nestedScrollInterop,
 | 
			
		||||
            presenter = presenter,
 | 
			
		||||
            onClickUninstall = { presenter.uninstallExtension() },
 | 
			
		||||
            onClickAppInfo = { presenter.openInSettings() },
 | 
			
		||||
            onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) },
 | 
			
		||||
            onClickSource = { presenter.toggleSource(it) },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter {
 | 
			
		||||
        val themedContext = getPreferenceThemeContext()
 | 
			
		||||
        val manager = PreferenceManager(themedContext)
 | 
			
		||||
        manager.preferenceDataStore = EmptyPreferenceDataStore()
 | 
			
		||||
        val screen = manager.createPreferenceScreen(themedContext)
 | 
			
		||||
        preferenceScreen = screen
 | 
			
		||||
 | 
			
		||||
        val isMultiSource = extension.sources.size > 1
 | 
			
		||||
        val isMultiLangSingleSource = isMultiSource && extension.sources.map { it.name }.distinct().size == 1
 | 
			
		||||
 | 
			
		||||
        with(screen) {
 | 
			
		||||
            if (isMultiSource && isMultiLangSingleSource.not()) {
 | 
			
		||||
                multiLanguagePreference(context, extension.sources)
 | 
			
		||||
            } else {
 | 
			
		||||
                singleLanguagePreference(context, extension.sources)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return PreferenceGroupAdapter(screen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun PreferenceScreen.singleLanguagePreference(context: Context, sources: List<Source>) {
 | 
			
		||||
        sources
 | 
			
		||||
            .map { source -> LocaleHelper.getSourceDisplayName(source.lang, context) to source }
 | 
			
		||||
            .sortedWith(compareBy({ (_, source) -> !source.isEnabled() }, { (lang, _) -> lang.lowercase() }))
 | 
			
		||||
            .forEach { (lang, source) ->
 | 
			
		||||
                sourceSwitchPreference(source, lang)
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun PreferenceScreen.multiLanguagePreference(context: Context, sources: List<Source>) {
 | 
			
		||||
        sources
 | 
			
		||||
            .groupBy { (it as CatalogueSource).lang }
 | 
			
		||||
            .toSortedMap(compareBy { LocaleHelper.getSourceDisplayName(it, context) })
 | 
			
		||||
            .forEach { entry ->
 | 
			
		||||
                entry.value
 | 
			
		||||
                    .sortedWith(compareBy({ source -> !source.isEnabled() }, { source -> source.name.lowercase() }))
 | 
			
		||||
                    .forEach { source ->
 | 
			
		||||
                        sourceSwitchPreference(source, source.toString())
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun PreferenceScreen.sourceSwitchPreference(source: Source, name: String) {
 | 
			
		||||
        val block: (@DSL SwitchPreferenceCompat).() -> Unit = {
 | 
			
		||||
            key = source.getPreferenceKey()
 | 
			
		||||
            title = name
 | 
			
		||||
            isPersistent = false
 | 
			
		||||
            isChecked = source.isEnabled()
 | 
			
		||||
 | 
			
		||||
            onChange { newValue ->
 | 
			
		||||
                val checked = newValue as Boolean
 | 
			
		||||
                toggleSource(source, checked)
 | 
			
		||||
                true
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // React to enable/disable all changes
 | 
			
		||||
            preferences.disabledSources().asFlow()
 | 
			
		||||
                .onEach {
 | 
			
		||||
                    val enabled = source.isEnabled()
 | 
			
		||||
                    isChecked = enabled
 | 
			
		||||
                }
 | 
			
		||||
                .launchIn(viewScope)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Source enable/disable
 | 
			
		||||
        if (source is ConfigurableSource) {
 | 
			
		||||
            switchSettingsPreference {
 | 
			
		||||
                block()
 | 
			
		||||
                onSettingsClick = View.OnClickListener {
 | 
			
		||||
                    router.pushController(SourcePreferencesController(source.id))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            switchPreference(block)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView(view: View) {
 | 
			
		||||
        preferenceScreen = null
 | 
			
		||||
        super.onDestroyView(view)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
			
		||||
        inflater.inflate(R.menu.extension_details, menu)
 | 
			
		||||
 | 
			
		||||
@@ -203,15 +74,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun toggleAllSources(enable: Boolean) {
 | 
			
		||||
        presenter.extension?.sources?.forEach { toggleSource(it, enable) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun toggleSource(source: Source, enable: Boolean) {
 | 
			
		||||
        if (enable) {
 | 
			
		||||
            preferences.disabledSources() -= source.id.toString()
 | 
			
		||||
        } else {
 | 
			
		||||
            preferences.disabledSources() += source.id.toString()
 | 
			
		||||
        }
 | 
			
		||||
        presenter.toggleSources(enable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun openChangelog() {
 | 
			
		||||
@@ -263,16 +126,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
 | 
			
		||||
 | 
			
		||||
        logcat { "Cleared $cleared cookies for: ${urls.joinToString()}" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Source.isEnabled(): Boolean {
 | 
			
		||||
        return id.toString() !in preferences.disabledSources().get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getPreferenceThemeContext(): Context {
 | 
			
		||||
        val tv = TypedValue()
 | 
			
		||||
        activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
 | 
			
		||||
        return ContextThemeWrapper(activity, tv.resourceId)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private const val PKGNAME_KEY = "pkg_name"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.extension.details
 | 
			
		||||
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.databinding.ExtensionDetailHeaderBinding
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.extension.getApplicationIcon
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import kotlinx.coroutines.flow.launchIn
 | 
			
		||||
import kotlinx.coroutines.flow.onEach
 | 
			
		||||
import reactivecircus.flowbinding.android.view.clicks
 | 
			
		||||
 | 
			
		||||
class ExtensionDetailsHeaderAdapter(private val presenter: ExtensionDetailsPresenter) :
 | 
			
		||||
    RecyclerView.Adapter<ExtensionDetailsHeaderAdapter.HeaderViewHolder>() {
 | 
			
		||||
 | 
			
		||||
    private lateinit var binding: ExtensionDetailHeaderBinding
 | 
			
		||||
 | 
			
		||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
 | 
			
		||||
        binding = ExtensionDetailHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 | 
			
		||||
        return HeaderViewHolder(binding.root)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItemCount(): Int = 1
 | 
			
		||||
 | 
			
		||||
    override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
 | 
			
		||||
        holder.bind()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
 | 
			
		||||
        fun bind() {
 | 
			
		||||
            val extension = presenter.extension ?: return
 | 
			
		||||
            val context = view.context
 | 
			
		||||
 | 
			
		||||
            extension.getApplicationIcon(context)?.let { binding.icon.setImageDrawable(it) }
 | 
			
		||||
            binding.title.text = extension.name
 | 
			
		||||
            binding.version.text = context.getString(R.string.ext_version_info, extension.versionName)
 | 
			
		||||
            binding.lang.text = context.getString(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context))
 | 
			
		||||
            binding.nsfw.isVisible = extension.isNsfw
 | 
			
		||||
            binding.pkgname.text = extension.pkgName
 | 
			
		||||
 | 
			
		||||
            binding.btnUninstall.clicks()
 | 
			
		||||
                .onEach { presenter.uninstallExtension() }
 | 
			
		||||
                .launchIn(presenter.presenterScope)
 | 
			
		||||
            binding.btnAppInfo.clicks()
 | 
			
		||||
                .onEach { presenter.openInSettings() }
 | 
			
		||||
                .launchIn(presenter.presenterScope)
 | 
			
		||||
 | 
			
		||||
            if (extension.isObsolete) {
 | 
			
		||||
                binding.warningBanner.isVisible = true
 | 
			
		||||
                binding.warningBanner.setText(R.string.obsolete_extension_message)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (extension.isUnofficial) {
 | 
			
		||||
                binding.warningBanner.isVisible = true
 | 
			
		||||
                binding.warningBanner.setText(R.string.unofficial_extension_message)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +1,58 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.browse.extension.details
 | 
			
		||||
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.provider.Settings
 | 
			
		||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
 | 
			
		||||
import eu.kanade.domain.source.interactor.ToggleSource
 | 
			
		||||
import eu.kanade.tachiyomi.extension.ExtensionManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.launchIO
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import rx.android.schedulers.AndroidSchedulers
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
class ExtensionDetailsPresenter(
 | 
			
		||||
    private val controller: ExtensionDetailsController,
 | 
			
		||||
    private val pkgName: String,
 | 
			
		||||
    private val context: Application = Injekt.get(),
 | 
			
		||||
    private val getExtensionSources: GetExtensionSources = Injekt.get(),
 | 
			
		||||
    private val toggleSource: ToggleSource = Injekt.get(),
 | 
			
		||||
    private val extensionManager: ExtensionManager = Injekt.get(),
 | 
			
		||||
) : BasePresenter<ExtensionDetailsController>() {
 | 
			
		||||
 | 
			
		||||
    private val extensionManager: ExtensionManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    val extension = extensionManager.installedExtensions.find { it.pkgName == pkgName }
 | 
			
		||||
 | 
			
		||||
    private val _state: MutableStateFlow<List<ExtensionSourceItem>> = MutableStateFlow(emptyList())
 | 
			
		||||
    val sourcesState: StateFlow<List<ExtensionSourceItem>> = _state.asStateFlow()
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        val extension = extension ?: return
 | 
			
		||||
 | 
			
		||||
        bindToUninstalledExtension()
 | 
			
		||||
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            getExtensionSources.subscribe(extension)
 | 
			
		||||
                .map {
 | 
			
		||||
                    it.sortedWith(
 | 
			
		||||
                        compareBy(
 | 
			
		||||
                            { item -> item.enabled.not() },
 | 
			
		||||
                            { item -> if (item.labelAsName) item.source.name else LocaleHelper.getSourceDisplayName(item.source.lang, context).lowercase() },
 | 
			
		||||
                        ),
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                .collectLatest { _state.value = it }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun bindToUninstalledExtension() {
 | 
			
		||||
@@ -45,6 +76,20 @@ class ExtensionDetailsPresenter(
 | 
			
		||||
        val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
 | 
			
		||||
            data = Uri.fromParts("package", pkgName, null)
 | 
			
		||||
        }
 | 
			
		||||
        controller.startActivity(intent)
 | 
			
		||||
        view?.startActivity(intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toggleSource(sourceId: Long) {
 | 
			
		||||
        toggleSource.await(sourceId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toggleSources(enable: Boolean) {
 | 
			
		||||
        extension?.sources?.forEach { toggleSource.await(it.id, enable) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data class ExtensionSourceItem(
 | 
			
		||||
    val source: Source,
 | 
			
		||||
    val enabled: Boolean,
 | 
			
		||||
    val labelAsName: Boolean,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import android.os.Bundle
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import androidx.core.os.bundleOf
 | 
			
		||||
import eu.kanade.presentation.source.MigrateMangaScreen
 | 
			
		||||
import eu.kanade.presentation.browse.MigrateMangaScreen
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import android.view.MenuInflater
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import eu.kanade.presentation.source.MigrateSourceScreen
 | 
			
		||||
import eu.kanade.presentation.browse.MigrateSourceScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.source.SourceScreen
 | 
			
		||||
import eu.kanade.presentation.browse.SourceScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.SearchableComposeController
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.source
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.source.SourceFilterScreen
 | 
			
		||||
import eu.kanade.presentation.browse.SourceFilterScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ class SourceFilterPresenter(
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        presenterScope.launchIO {
 | 
			
		||||
            getLanguagesWithSources.subscribe()
 | 
			
		||||
                .catch { exception ->
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import eu.kanade.domain.source.interactor.ToggleSource
 | 
			
		||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
 | 
			
		||||
import eu.kanade.domain.source.model.Pin
 | 
			
		||||
import eu.kanade.domain.source.model.Source
 | 
			
		||||
import eu.kanade.presentation.source.SourceUiModel
 | 
			
		||||
import eu.kanade.presentation.browse.SourceUiModel
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.launchIO
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
import eu.kanade.tachiyomi.widget.preference.AdaptiveTitlePreferenceCategory
 | 
			
		||||
import eu.kanade.tachiyomi.widget.preference.IntListPreference
 | 
			
		||||
import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
 | 
			
		||||
import eu.kanade.tachiyomi.widget.preference.SwitchSettingsPreference
 | 
			
		||||
 | 
			
		||||
@DslMarker
 | 
			
		||||
@Target(AnnotationTarget.TYPE)
 | 
			
		||||
@@ -56,10 +55,6 @@ inline fun PreferenceGroup.switchPreferenceCategory(block: (@DSL SwitchPreferenc
 | 
			
		||||
    return initThenAdd(SwitchPreferenceCategory(context), block)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun PreferenceGroup.switchSettingsPreference(block: (@DSL SwitchSettingsPreference).() -> Unit): SwitchSettingsPreference {
 | 
			
		||||
    return initThenAdd(SwitchSettingsPreference(context), block)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun PreferenceGroup.checkBoxPreference(block: (@DSL CheckBoxPreference).() -> Unit): CheckBoxPreference {
 | 
			
		||||
    return initThenAdd(CheckBoxPreference(context), block)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.widget.preference
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.util.AttributeSet
 | 
			
		||||
import android.view.MotionEvent
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.preference.PreferenceViewHolder
 | 
			
		||||
import androidx.preference.SwitchPreferenceCompat
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
 | 
			
		||||
class SwitchSettingsPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
 | 
			
		||||
    SwitchPreferenceCompat(context, attrs) {
 | 
			
		||||
 | 
			
		||||
    var onSettingsClick: View.OnClickListener? = null
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        widgetLayoutResource = R.layout.pref_settings
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("ClickableViewAccessibility")
 | 
			
		||||
    override fun onBindViewHolder(holder: PreferenceViewHolder) {
 | 
			
		||||
        super.onBindViewHolder(holder)
 | 
			
		||||
 | 
			
		||||
        holder.findViewById(R.id.button).setOnClickListener {
 | 
			
		||||
            onSettingsClick?.onClick(it)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Disable swiping to align with SwitchPreferenceCompat
 | 
			
		||||
        holder.findViewById(R.id.switchWidget).setOnTouchListener { _, event ->
 | 
			
		||||
            event.actionMasked == MotionEvent.ACTION_MOVE
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:id="@+id/extension_prefs_recycler"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content" />
 | 
			
		||||
@@ -1,130 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/warning_banner"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:background="?attr/colorError"
 | 
			
		||||
        android:gravity="center"
 | 
			
		||||
        android:padding="16dp"
 | 
			
		||||
        android:textColor="?attr/colorOnError"
 | 
			
		||||
        android:visibility="gone"
 | 
			
		||||
        tools:visibility="visible" />
 | 
			
		||||
 | 
			
		||||
    <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:padding="16dp">
 | 
			
		||||
 | 
			
		||||
        <ImageView
 | 
			
		||||
            android:id="@+id/icon"
 | 
			
		||||
            android:layout_width="56dp"
 | 
			
		||||
            android:layout_height="56dp"
 | 
			
		||||
            android:src="@mipmap/ic_launcher"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="@id/pkgname"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            tools:ignore="ContentDescription" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/title"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="16dp"
 | 
			
		||||
            android:elevation="3dp"
 | 
			
		||||
            android:textAppearance="?attr/textAppearanceTitleMedium"
 | 
			
		||||
            app:layout_constraintStart_toEndOf="@id/icon"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            tools:text="Tachiyomi: Extension" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/version"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:layout_weight="1"
 | 
			
		||||
            android:elevation="3dp"
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:textAppearance="?attr/textAppearanceBodySmall"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@id/title"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/title"
 | 
			
		||||
            tools:text="Version: 1.0.0" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/lang"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:layout_weight="1"
 | 
			
		||||
            android:elevation="3dp"
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:textAppearance="?attr/textAppearanceBodySmall"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@id/title"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/version"
 | 
			
		||||
            tools:text="Language: English" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/nsfw"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:layout_weight="1"
 | 
			
		||||
            android:elevation="3dp"
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:text="@string/ext_nsfw_warning"
 | 
			
		||||
            android:textAppearance="?attr/textAppearanceBodySmall"
 | 
			
		||||
            android:textColor="?attr/colorError"
 | 
			
		||||
            android:visibility="gone"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@id/title"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/lang"
 | 
			
		||||
            tools:visibility="visible" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/pkgname"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:elevation="3dp"
 | 
			
		||||
            android:ellipsize="middle"
 | 
			
		||||
            android:singleLine="true"
 | 
			
		||||
            android:textAppearance="?attr/textAppearanceBodySmall"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@id/title"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/nsfw"
 | 
			
		||||
            tools:text="eu.kanade.tachiyomi.extension.en.myext" />
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
            android:id="@+id/btn_uninstall"
 | 
			
		||||
            style="@style/Widget.Tachiyomi.Button.OutlinedButton"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginTop="8dp"
 | 
			
		||||
            android:layout_marginEnd="4dp"
 | 
			
		||||
            android:text="@string/ext_uninstall"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toStartOf="@+id/btn_app_info"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/pkgname" />
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
            android:id="@+id/btn_app_info"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginTop="8dp"
 | 
			
		||||
            android:layout_marginStart="4dp"
 | 
			
		||||
            android:text="@string/ext_app_info"
 | 
			
		||||
            app:layout_constraintStart_toEndOf="@+id/btn_uninstall"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/pkgname" />
 | 
			
		||||
 | 
			
		||||
    </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
 | 
			
		||||
    <com.google.android.material.divider.MaterialDivider
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content" />
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    android:layout_width="wrap_content"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:gravity="center_vertical">
 | 
			
		||||
 | 
			
		||||
    <ImageButton
 | 
			
		||||
        android:id="@+id/button"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginEnd="8dp"
 | 
			
		||||
        android:background="?attr/selectableItemBackgroundBorderless"
 | 
			
		||||
        android:contentDescription="@string/label_settings"
 | 
			
		||||
        android:padding="8dp"
 | 
			
		||||
        app:srcCompat="@drawable/ic_settings_24dp"
 | 
			
		||||
        app:tint="?attr/colorOnBackground" />
 | 
			
		||||
 | 
			
		||||
    <!-- Matches ID used in SwitchPreferenceCompat -->
 | 
			
		||||
    <androidx.appcompat.widget.SwitchCompat
 | 
			
		||||
        android:id="@+id/switchWidget"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content" />
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@@ -730,7 +730,7 @@
 | 
			
		||||
    <string name="migration_selection_prompt">Select a source to migrate from</string>
 | 
			
		||||
    <string name="migrate">Migrate</string>
 | 
			
		||||
    <string name="copy">Copy</string>
 | 
			
		||||
    <string name="migrate_empty_screen">Well, this is awkward</string>
 | 
			
		||||
    <string name="empty_screen">Well, this is awkward</string>
 | 
			
		||||
 | 
			
		||||
    <!-- Downloads activity and service -->
 | 
			
		||||
    <string name="download_queue_error">Couldn\'t download chapters. You can try again in the downloads section</string>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user