mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-26 23:28:58 +03:00
Migrate extension details page to Compose
This commit is contained in:
parent
1c94ecdcdf
commit
13943f77f7
24 changed files with 363 additions and 452 deletions
|
@ -3,6 +3,7 @@ package eu.kanade.domain
|
||||||
import eu.kanade.data.history.HistoryRepositoryImpl
|
import eu.kanade.data.history.HistoryRepositoryImpl
|
||||||
import eu.kanade.data.manga.MangaRepositoryImpl
|
import eu.kanade.data.manga.MangaRepositoryImpl
|
||||||
import eu.kanade.data.source.SourceRepositoryImpl
|
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.GetExtensionUpdates
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensions
|
import eu.kanade.domain.extension.interactor.GetExtensions
|
||||||
import eu.kanade.domain.history.interactor.DeleteHistoryTable
|
import eu.kanade.domain.history.interactor.DeleteHistoryTable
|
||||||
|
@ -43,6 +44,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { RemoveHistoryByMangaId(get()) }
|
addFactory { RemoveHistoryByMangaId(get()) }
|
||||||
|
|
||||||
addFactory { GetExtensions(get(), get()) }
|
addFactory { GetExtensions(get(), get()) }
|
||||||
|
addFactory { GetExtensionSources(get()) }
|
||||||
addFactory { GetExtensionUpdates(get(), get()) }
|
addFactory { GetExtensionUpdates(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(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,
|
private val preferences: PreferencesHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun await(source: Source) {
|
fun await(source: Source, enable: Boolean = source.id.toString() in preferences.disabledSources().get()) {
|
||||||
val isEnabled = source.id.toString() !in preferences.disabledSources().get()
|
await(source.id, enable)
|
||||||
if (isEnabled) {
|
}
|
||||||
preferences.disabledSources() += source.id.toString()
|
|
||||||
|
fun await(sourceId: Long, enable: Boolean = sourceId.toString() in preferences.disabledSources().get()) {
|
||||||
|
if (enable) {
|
||||||
|
preferences.disabledSources() -= sourceId.toString()
|
||||||
} else {
|
} 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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
@ -51,7 +51,7 @@ fun MigrateMangaContent(
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
EmptyScreen(textResource = R.string.migrate_empty_screen)
|
EmptyScreen(textResource = R.string.empty_screen)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
LazyColumn(
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.source.model.Source
|
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.EmptyScreen
|
||||||
import eu.kanade.presentation.components.ItemBadges
|
import eu.kanade.presentation.components.ItemBadges
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.source.components.BaseSourceItem
|
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
import eu.kanade.presentation.util.plus
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
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.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import eu.kanade.domain.source.model.Source
|
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.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
import eu.kanade.presentation.components.PreferenceRow
|
||||||
import eu.kanade.presentation.source.components.BaseSourceItem
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
|
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterPresenter
|
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterPresenter
|
||||||
|
@ -59,6 +59,7 @@ fun SourceFilterContent(
|
||||||
EmptyScreen(textResource = R.string.source_filter_empty_screen)
|
EmptyScreen(textResource = R.string.source_filter_empty_screen)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -32,9 +32,9 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.source.model.Pin
|
import eu.kanade.domain.source.model.Pin
|
||||||
import eu.kanade.domain.source.model.Source
|
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.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.source.components.BaseSourceItem
|
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
import eu.kanade.presentation.util.plus
|
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.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
@ -9,8 +9,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import eu.kanade.domain.source.model.Source
|
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.presentation.util.horizontalPadding
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
|
@ -1,59 +1,30 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.extension.details
|
package eu.kanade.tachiyomi.ui.browse.extension.details
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.preference.PreferenceGroupAdapter
|
import eu.kanade.presentation.browse.ExtensionDetailsScreen
|
||||||
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.tachiyomi.R
|
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.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.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.openInBrowser
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
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 eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
class ExtensionDetailsController(bundle: Bundle? = null) :
|
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 val network: NetworkHelper by injectLazy()
|
||||||
|
|
||||||
private var preferenceScreen: PreferenceScreen? = null
|
|
||||||
|
|
||||||
constructor(pkgName: String) : this(
|
constructor(pkgName: String) : this(
|
||||||
bundleOf(PKGNAME_KEY to pkgName),
|
bundleOf(PKGNAME_KEY to pkgName),
|
||||||
)
|
)
|
||||||
|
@ -62,122 +33,22 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding {
|
override fun getTitle() = resources?.getString(R.string.label_extension_info)
|
||||||
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
|
||||||
return ExtensionDetailControllerBinding.inflate(themedInflater)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPresenter(): ExtensionDetailsPresenter {
|
override fun createPresenter() = ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
|
||||||
return ExtensionDetailsPresenter(this, args.getString(PKGNAME_KEY)!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
@Composable
|
||||||
return resources?.getString(R.string.label_extension_info)
|
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
||||||
}
|
ExtensionDetailsScreen(
|
||||||
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
@SuppressLint("PrivateResource")
|
presenter = presenter,
|
||||||
override fun onViewCreated(view: View) {
|
onClickUninstall = { presenter.uninstallExtension() },
|
||||||
super.onViewCreated(view)
|
onClickAppInfo = { presenter.openInSettings() },
|
||||||
|
onClickSourcePreferences = { router.pushController(SourcePreferencesController(it)) },
|
||||||
binding.extensionPrefsRecycler.applyInsetter {
|
onClickSource = { presenter.toggleSource(it) },
|
||||||
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),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.extension_details, menu)
|
inflater.inflate(R.menu.extension_details, menu)
|
||||||
|
|
||||||
|
@ -203,15 +74,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleAllSources(enable: Boolean) {
|
private fun toggleAllSources(enable: Boolean) {
|
||||||
presenter.extension?.sources?.forEach { toggleSource(it, enable) }
|
presenter.toggleSources(enable)
|
||||||
}
|
|
||||||
|
|
||||||
private fun toggleSource(source: Source, enable: Boolean) {
|
|
||||||
if (enable) {
|
|
||||||
preferences.disabledSources() -= source.id.toString()
|
|
||||||
} else {
|
|
||||||
preferences.disabledSources() += source.id.toString()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openChangelog() {
|
private fun openChangelog() {
|
||||||
|
@ -263,16 +126,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||||
|
|
||||||
logcat { "Cleared $cleared cookies for: ${urls.joinToString()}" }
|
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"
|
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
|
package eu.kanade.tachiyomi.ui.browse.extension.details
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
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.extension.ExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
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 rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionDetailsPresenter(
|
class ExtensionDetailsPresenter(
|
||||||
private val controller: ExtensionDetailsController,
|
|
||||||
private val pkgName: String,
|
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>() {
|
) : BasePresenter<ExtensionDetailsController>() {
|
||||||
|
|
||||||
private val extensionManager: ExtensionManager by injectLazy()
|
|
||||||
|
|
||||||
val extension = extensionManager.installedExtensions.find { it.pkgName == pkgName }
|
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?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
val extension = extension ?: return
|
||||||
|
|
||||||
bindToUninstalledExtension()
|
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() {
|
private fun bindToUninstalledExtension() {
|
||||||
|
@ -45,6 +76,20 @@ class ExtensionDetailsPresenter(
|
||||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
data = Uri.fromParts("package", pkgName, null)
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.core.os.bundleOf
|
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.ComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
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.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
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.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import eu.kanade.domain.source.model.Source
|
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.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.SearchableComposeController
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import eu.kanade.domain.source.model.Source
|
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.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ class SourceFilterPresenter(
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getLanguagesWithSources.subscribe()
|
getLanguagesWithSources.subscribe()
|
||||||
.catch { exception ->
|
.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.interactor.ToggleSourcePin
|
||||||
import eu.kanade.domain.source.model.Pin
|
import eu.kanade.domain.source.model.Pin
|
||||||
import eu.kanade.domain.source.model.Source
|
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.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.AdaptiveTitlePreferenceCategory
|
||||||
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||||
import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
|
import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
|
||||||
import eu.kanade.tachiyomi.widget.preference.SwitchSettingsPreference
|
|
||||||
|
|
||||||
@DslMarker
|
@DslMarker
|
||||||
@Target(AnnotationTarget.TYPE)
|
@Target(AnnotationTarget.TYPE)
|
||||||
|
@ -56,10 +55,6 @@ inline fun PreferenceGroup.switchPreferenceCategory(block: (@DSL SwitchPreferenc
|
||||||
return initThenAdd(SwitchPreferenceCategory(context), block)
|
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 {
|
inline fun PreferenceGroup.checkBoxPreference(block: (@DSL CheckBoxPreference).() -> Unit): CheckBoxPreference {
|
||||||
return initThenAdd(CheckBoxPreference(context), block)
|
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="migration_selection_prompt">Select a source to migrate from</string>
|
||||||
<string name="migrate">Migrate</string>
|
<string name="migrate">Migrate</string>
|
||||||
<string name="copy">Copy</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 -->
|
<!-- Downloads activity and service -->
|
||||||
<string name="download_queue_error">Couldn\'t download chapters. You can try again in the downloads section</string>
|
<string name="download_queue_error">Couldn\'t download chapters. You can try again in the downloads section</string>
|
||||||
|
|
Loading…
Reference in a new issue