mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Use Compose in Migrate tab (#7008)
* Use Compose in Migrate tab * Add missing header * Remove unused files * Fix build after rebase * Changes from review comments
This commit is contained in:
parent
a4a4503311
commit
7261fcccda
20 changed files with 432 additions and 456 deletions
|
@ -3,11 +3,15 @@ package eu.kanade.data.source
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
|
||||||
val sourceMapper: (CatalogueSource) -> Source = { source ->
|
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
||||||
Source(
|
Source(
|
||||||
source.id,
|
source.id,
|
||||||
source.lang,
|
source.lang,
|
||||||
source.name,
|
source.name,
|
||||||
source.supportsLatest
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
||||||
|
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,35 @@
|
||||||
package eu.kanade.data.source
|
package eu.kanade.data.source
|
||||||
|
|
||||||
|
import eu.kanade.data.DatabaseHandler
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class SourceRepositoryImpl(
|
class SourceRepositoryImpl(
|
||||||
private val sourceManager: SourceManager
|
private val sourceManager: SourceManager,
|
||||||
|
private val handler: DatabaseHandler
|
||||||
) : SourceRepository {
|
) : SourceRepository {
|
||||||
|
|
||||||
override fun getSources(): Flow<List<Source>> {
|
override fun getSources(): Flow<List<Source>> {
|
||||||
return sourceManager.catalogueSources.map { sources ->
|
return sourceManager.catalogueSources.map { sources ->
|
||||||
sources.map(sourceMapper)
|
sources.map(catalogueSourceMapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
|
||||||
|
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
||||||
|
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
||||||
|
sourceIdsWithCount
|
||||||
|
.map { (sourceId, count) ->
|
||||||
|
val source = sourceManager.getOrStub(sourceId).run {
|
||||||
|
sourceMapper(this)
|
||||||
|
}
|
||||||
|
source to count
|
||||||
|
}
|
||||||
|
.filterNot { it.first.id == LocalSource.ID }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
import eu.kanade.domain.history.repository.HistoryRepository
|
||||||
import eu.kanade.domain.source.interactor.DisableSource
|
import eu.kanade.domain.source.interactor.DisableSource
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
|
@ -29,9 +31,11 @@ class DomainModule : InjektModule {
|
||||||
addFactory { RemoveHistoryById(get()) }
|
addFactory { RemoveHistoryById(get()) }
|
||||||
addFactory { RemoveHistoryByMangaId(get()) }
|
addFactory { RemoveHistoryByMangaId(get()) }
|
||||||
|
|
||||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get()) }
|
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||||
addFactory { GetEnabledSources(get(), get()) }
|
addFactory { GetEnabledSources(get(), get()) }
|
||||||
addFactory { DisableSource(get()) }
|
addFactory { DisableSource(get()) }
|
||||||
addFactory { ToggleSourcePin(get()) }
|
addFactory { ToggleSourcePin(get()) }
|
||||||
|
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
||||||
|
addFactory { SetMigrateSorting(get()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import java.text.Collator
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.Comparator
|
||||||
|
|
||||||
|
class GetSourcesWithFavoriteCount(
|
||||||
|
private val repository: SourceRepository,
|
||||||
|
private val preferences: PreferencesHelper
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<List<Pair<Source, Long>>> {
|
||||||
|
return combine(
|
||||||
|
preferences.migrationSortingDirection().asFlow(),
|
||||||
|
preferences.migrationSortingMode().asFlow(),
|
||||||
|
repository.getSourcesWithFavoriteCount()
|
||||||
|
) { direction, mode, list ->
|
||||||
|
list.sortedWith(sortFn(direction, mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortFn(
|
||||||
|
direction: SetMigrateSorting.Direction,
|
||||||
|
sorting: SetMigrateSorting.Mode
|
||||||
|
): java.util.Comparator<Pair<Source, Long>> {
|
||||||
|
val locale = Locale.getDefault()
|
||||||
|
val collator = Collator.getInstance(locale).apply {
|
||||||
|
strength = Collator.PRIMARY
|
||||||
|
}
|
||||||
|
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||||
|
val id1 = a.first.name.toLongOrNull()
|
||||||
|
val id2 = b.first.name.toLongOrNull()
|
||||||
|
when (sorting) {
|
||||||
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
|
collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
||||||
|
}
|
||||||
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
|
when {
|
||||||
|
id1 != null && id2 != null -> a.second.compareTo(b.second)
|
||||||
|
id1 != null && id2 == null -> -1
|
||||||
|
id2 != null && id1 == null -> 1
|
||||||
|
else -> a.second.compareTo(b.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (direction) {
|
||||||
|
SetMigrateSorting.Direction.ASCENDING -> Comparator(sortFn)
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> Collections.reverseOrder(sortFn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
|
||||||
|
class SetMigrateSorting(
|
||||||
|
private val preferences: PreferencesHelper
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun await(mode: Mode, isAscending: Boolean) {
|
||||||
|
val direction = if (isAscending) Direction.ASCENDING else Direction.DESCENDING
|
||||||
|
preferences.migrationSortingDirection().set(direction)
|
||||||
|
preferences.migrationSortingMode().set(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
ALPHABETICAL,
|
||||||
|
TOTAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Direction {
|
||||||
|
ASCENDING,
|
||||||
|
DESCENDING;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,4 +6,6 @@ import kotlinx.coroutines.flow.Flow
|
||||||
interface SourceRepository {
|
interface SourceRepository {
|
||||||
|
|
||||||
fun getSources(): Flow<List<Source>>
|
fun getSources(): Flow<List<Source>>
|
||||||
|
|
||||||
|
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingScreen() {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.size(64.dp))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package eu.kanade.presentation.source
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
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.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.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrateSourceScreen(
|
||||||
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
|
presenter: MigrationSourcesPresenter,
|
||||||
|
onClickItem: (Source) -> Unit,
|
||||||
|
onLongClickItem: (Source) -> Unit,
|
||||||
|
) {
|
||||||
|
val state by presenter.state.collectAsState()
|
||||||
|
when {
|
||||||
|
state.isLoading -> LoadingScreen()
|
||||||
|
state.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library)
|
||||||
|
else -> {
|
||||||
|
MigrateSourceList(
|
||||||
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
|
list = state.sources!!,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrateSourceList(
|
||||||
|
nestedScrollInterop: NestedScrollConnection,
|
||||||
|
list: List<Pair<Source, Long>>,
|
||||||
|
onClickItem: (Source) -> Unit,
|
||||||
|
onLongClickItem: (Source) -> Unit,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
|
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||||
|
) {
|
||||||
|
item(key = "title") {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.migration_selection_prompt),
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItemPlacement()
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = 8.dp),
|
||||||
|
style = MaterialTheme.typography.header
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = list,
|
||||||
|
key = { (source, _) ->
|
||||||
|
source.id
|
||||||
|
}
|
||||||
|
) { (source, count) ->
|
||||||
|
MigrateSourceItem(
|
||||||
|
modifier = Modifier.animateItemPlacement(),
|
||||||
|
source = source,
|
||||||
|
count = count,
|
||||||
|
onClickItem = { onClickItem(source) },
|
||||||
|
onLongClickItem = { onLongClickItem(source) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MigrateSourceItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
source: Source,
|
||||||
|
count: Long,
|
||||||
|
onClickItem: () -> Unit,
|
||||||
|
onLongClickItem: () -> Unit,
|
||||||
|
) {
|
||||||
|
BaseSourceItem(
|
||||||
|
modifier = modifier,
|
||||||
|
source = source,
|
||||||
|
onClickItem = onClickItem,
|
||||||
|
onLongClickItem = onLongClickItem,
|
||||||
|
action = {
|
||||||
|
Text(
|
||||||
|
text = "$count",
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,9 +2,7 @@ package eu.kanade.presentation.source
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
@ -18,7 +16,6 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PushPin
|
import androidx.compose.material.icons.filled.PushPin
|
||||||
import androidx.compose.material.icons.outlined.PushPin
|
import androidx.compose.material.icons.outlined.PushPin
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
@ -30,18 +27,18 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
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 androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
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.components.EmptyScreen
|
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.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
@ -62,7 +59,7 @@ fun SourceScreen(
|
||||||
val state by presenter.state.collectAsState()
|
val state by presenter.state.collectAsState()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
state.isLoading -> CircularProgressIndicator()
|
state.isLoading -> LoadingScreen()
|
||||||
state.hasError -> Text(text = state.error!!.message!!)
|
state.hasError -> Text(text = state.error!!.message!!)
|
||||||
state.isEmpty -> EmptyScreen(message = "")
|
state.isEmpty -> EmptyScreen(message = "")
|
||||||
else -> SourceList(
|
else -> SourceList(
|
||||||
|
@ -115,7 +112,7 @@ fun SourceList(
|
||||||
}
|
}
|
||||||
is UiModel.Item -> SourceItem(
|
is UiModel.Item -> SourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
item = model.source,
|
source = model.source,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = {
|
onLongClickItem = {
|
||||||
setSourceState(it)
|
setSourceState(it)
|
||||||
|
@ -160,55 +157,34 @@ fun SourceHeader(
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceItem(
|
fun SourceItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
item: Source,
|
source: Source,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onLongClickItem: (Source) -> Unit,
|
onLongClickItem: (Source) -> Unit,
|
||||||
onClickLatest: (Source) -> Unit,
|
onClickLatest: (Source) -> Unit,
|
||||||
onClickPin: (Source) -> Unit
|
onClickPin: (Source) -> Unit
|
||||||
) {
|
) {
|
||||||
Row(
|
BaseSourceItem(
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
.combinedClickable(
|
source = source,
|
||||||
onClick = { onClickItem(item) },
|
onClickItem = { onClickItem(source) },
|
||||||
onLongClick = { onLongClickItem(item) }
|
onLongClickItem = { onLongClickItem(source) },
|
||||||
)
|
action = { source ->
|
||||||
.padding(horizontal = horizontalPadding, vertical = 8.dp),
|
if (source.supportsLatest) {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
TextButton(onClick = { onClickLatest(source) }) {
|
||||||
) {
|
Text(
|
||||||
SourceIcon(source = item)
|
text = stringResource(id = R.string.latest),
|
||||||
Column(
|
style = LocalTextStyle.current.copy(
|
||||||
modifier = Modifier
|
color = MaterialTheme.colorScheme.primary
|
||||||
.padding(horizontal = horizontalPadding)
|
)
|
||||||
.weight(1f)
|
)
|
||||||
) {
|
}
|
||||||
Text(
|
|
||||||
text = item.name,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = LocaleHelper.getDisplayName(item.lang),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.bodySmall
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (item.supportsLatest) {
|
|
||||||
TextButton(onClick = { onClickLatest(item) }) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.latest),
|
|
||||||
style = LocalTextStyle.current.copy(
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
SourcePinButton(
|
||||||
SourcePinButton(
|
isPinned = Pin.Pinned in source.pin,
|
||||||
isPinned = Pin.Pinned in item.pin,
|
onClick = { onClickPin(source) }
|
||||||
onClick = { onClickPin(item) }
|
)
|
||||||
)
|
},
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package eu.kanade.presentation.source.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.presentation.source.SourceIcon
|
||||||
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BaseSourceItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
source: Source,
|
||||||
|
onClickItem: () -> Unit = {},
|
||||||
|
onLongClickItem: () -> Unit = {},
|
||||||
|
icon: @Composable RowScope.(Source) -> Unit = defaultIcon,
|
||||||
|
action: @Composable RowScope.(Source) -> Unit = {},
|
||||||
|
content: @Composable RowScope.(Source) -> Unit = defaultContent,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClickItem,
|
||||||
|
onLongClick = onLongClickItem
|
||||||
|
)
|
||||||
|
.padding(horizontal = horizontalPadding, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
icon.invoke(this, source)
|
||||||
|
content.invoke(this, source)
|
||||||
|
action.invoke(this, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultIcon: @Composable RowScope.(Source) -> Unit = { source ->
|
||||||
|
SourceIcon(source = source)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultContent: @Composable RowScope.(Source) -> Unit = { source ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = horizontalPadding)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = source.name,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = LocaleHelper.getDisplayName(source.lang),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,11 +7,11 @@ import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
|
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
|
||||||
|
@ -254,8 +254,8 @@ class PreferencesHelper(val context: Context) {
|
||||||
fun librarySortingMode() = flowPrefs.getEnum(Keys.librarySortingMode, SortModeSetting.ALPHABETICAL)
|
fun librarySortingMode() = flowPrefs.getEnum(Keys.librarySortingMode, SortModeSetting.ALPHABETICAL)
|
||||||
fun librarySortingAscending() = flowPrefs.getEnum(Keys.librarySortingDirection, SortDirectionSetting.ASCENDING)
|
fun librarySortingAscending() = flowPrefs.getEnum(Keys.librarySortingDirection, SortDirectionSetting.ASCENDING)
|
||||||
|
|
||||||
fun migrationSortingMode() = flowPrefs.getEnum(Keys.migrationSortingMode, MigrationSourcesController.SortSetting.ALPHABETICAL)
|
fun migrationSortingMode() = flowPrefs.getEnum(Keys.migrationSortingMode, SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
fun migrationSortingDirection() = flowPrefs.getEnum(Keys.migrationSortingDirection, MigrationSourcesController.DirectionSetting.ASCENDING)
|
fun migrationSortingDirection() = flowPrefs.getEnum(Keys.migrationSortingDirection, SetMigrateSorting.Direction.ASCENDING)
|
||||||
|
|
||||||
fun automaticExtUpdates() = flowPrefs.getBoolean("automatic_ext_updates", true)
|
fun automaticExtUpdates() = flowPrefs.getBoolean("automatic_ext_updates", true)
|
||||||
|
|
||||||
|
|
|
@ -1,124 +1,68 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
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.recyclerview.widget.LinearLayoutManager
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
import eu.kanade.presentation.source.MigrateSourceScreen
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.ui.base.controller.ComposeController
|
||||||
import eu.kanade.tachiyomi.databinding.MigrationSourcesControllerBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class MigrationSourcesController :
|
class MigrationSourcesController : ComposeController<MigrationSourcesPresenter>() {
|
||||||
NucleusController<MigrationSourcesControllerBinding, MigrationSourcesPresenter>(),
|
|
||||||
FlexibleAdapter.OnItemClickListener,
|
|
||||||
FlexibleAdapter.OnItemLongClickListener {
|
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
|
|
||||||
private var adapter: SourceAdapter? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPresenter(): MigrationSourcesPresenter {
|
override fun createPresenter(): MigrationSourcesPresenter =
|
||||||
return MigrationSourcesPresenter()
|
MigrationSourcesPresenter()
|
||||||
}
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) = MigrationSourcesControllerBinding.inflate(inflater)
|
@Composable
|
||||||
|
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
||||||
override fun onViewCreated(view: View) {
|
MigrateSourceScreen(
|
||||||
super.onViewCreated(view)
|
nestedScrollInterop = nestedScrollInterop,
|
||||||
|
presenter = presenter,
|
||||||
binding.recycler.applyInsetter {
|
onClickItem = { source ->
|
||||||
type(navigationBars = true) {
|
parentController!!.router.pushController(
|
||||||
padding()
|
MigrationMangaController(
|
||||||
|
source.id,
|
||||||
|
source.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLongClickItem = { source ->
|
||||||
|
val sourceId = source.id.toString()
|
||||||
|
activity?.copyToClipboard(sourceId, sourceId)
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
adapter = SourceAdapter(this)
|
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
|
||||||
binding.recycler.adapter = adapter
|
|
||||||
adapter?.fastScroller = binding.fastScroller
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) =
|
||||||
adapter = null
|
|
||||||
super.onDestroyView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.browse_migrate, menu)
|
inflater.inflate(R.menu.browse_migrate, menu)
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (val itemId = item.itemId) {
|
return when (val itemId = item.itemId) {
|
||||||
R.id.action_source_migration_help -> activity?.openInBrowser(HELP_URL)
|
R.id.action_source_migration_help -> {
|
||||||
R.id.asc_alphabetical, R.id.desc_alphabetical -> {
|
activity?.openInBrowser(HELP_URL)
|
||||||
setSortingDirection(SortSetting.ALPHABETICAL, itemId == R.id.asc_alphabetical)
|
true
|
||||||
}
|
}
|
||||||
R.id.asc_count, R.id.desc_count -> {
|
R.id.asc_alphabetical,
|
||||||
setSortingDirection(SortSetting.TOTAL, itemId == R.id.asc_count)
|
R.id.desc_alphabetical -> {
|
||||||
|
presenter.setAlphabeticalSorting(itemId == R.id.asc_alphabetical)
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
R.id.asc_count,
|
||||||
|
R.id.desc_count -> {
|
||||||
|
presenter.setTotalSorting(itemId == R.id.asc_count)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setSortingDirection(sortSetting: SortSetting, isAscending: Boolean) {
|
|
||||||
val direction = if (isAscending) {
|
|
||||||
DirectionSetting.ASCENDING
|
|
||||||
} else {
|
|
||||||
DirectionSetting.DESCENDING
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences.migrationSortingDirection().set(direction)
|
|
||||||
preferences.migrationSortingMode().set(sortSetting)
|
|
||||||
|
|
||||||
presenter.requestSortUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSources(sourcesWithManga: List<SourceItem>) {
|
|
||||||
// Show empty view if needed
|
|
||||||
if (sourcesWithManga.isNotEmpty()) {
|
|
||||||
binding.emptyView.hide()
|
|
||||||
} else {
|
|
||||||
binding.emptyView.show(R.string.information_empty_library)
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter?.updateDataSet(sourcesWithManga)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(view: View, position: Int): Boolean {
|
|
||||||
val item = adapter?.getItem(position) as? SourceItem ?: return false
|
|
||||||
val controller = MigrationMangaController(item.source.id, item.source.name)
|
|
||||||
parentController!!.router.pushController(controller)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemLongClick(position: Int) {
|
|
||||||
val item = adapter?.getItem(position) as? SourceItem ?: return
|
|
||||||
val sourceId = item.source.id.toString()
|
|
||||||
activity?.copyToClipboard(sourceId, sourceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class DirectionSetting {
|
|
||||||
ASCENDING,
|
|
||||||
DESCENDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class SortSetting {
|
|
||||||
ALPHABETICAL,
|
|
||||||
TOTAL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,82 +1,60 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.domain.source.model.Source
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.combineLatest
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import rx.schedulers.Schedulers
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.text.Collator
|
|
||||||
import java.util.Collections
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class MigrationSourcesPresenter(
|
class MigrationSourcesPresenter(
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
||||||
private val db: DatabaseHelper = Injekt.get(),
|
private val setMigrateSorting: SetMigrateSorting = Injekt.get()
|
||||||
) : BasePresenter<MigrationSourcesController>() {
|
) : BasePresenter<MigrationSourcesController>() {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val _state: MutableStateFlow<MigrateSourceState> = MutableStateFlow(MigrateSourceState.EMPTY)
|
||||||
|
val state: StateFlow<MigrateSourceState> = _state.asStateFlow()
|
||||||
private val sortRelay = BehaviorRelay.create(Unit)
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
db.getFavoriteMangas()
|
presenterScope.launchIO {
|
||||||
.asRxObservable()
|
getSourcesWithFavoriteCount.subscribe()
|
||||||
.combineLatest(sortRelay.observeOn(Schedulers.io())) { sources, _ -> sources }
|
.collectLatest { sources ->
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
_state.update { state ->
|
||||||
.map { findSourcesWithManga(it) }
|
state.copy(sources = sources)
|
||||||
.subscribeLatestCache(MigrationSourcesController::setSources)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestSortUpdate() {
|
fun setAlphabeticalSorting(isAscending: Boolean) {
|
||||||
sortRelay.call(Unit)
|
setMigrateSorting.await(SetMigrateSorting.Mode.ALPHABETICAL, isAscending)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
|
fun setTotalSorting(isAscending: Boolean) {
|
||||||
val header = SelectionHeader()
|
setMigrateSorting.await(SetMigrateSorting.Mode.TOTAL, isAscending)
|
||||||
return library
|
}
|
||||||
.groupBy { it.source }
|
}
|
||||||
.filterKeys { it != LocalSource.ID }
|
|
||||||
.map {
|
data class MigrateSourceState(
|
||||||
val source = sourceManager.getOrStub(it.key)
|
val sources: List<Pair<Source, Long>>?
|
||||||
SourceItem(source, it.value.size, header)
|
) {
|
||||||
}
|
|
||||||
.sortedWith(sortFn())
|
val isLoading: Boolean
|
||||||
.toList()
|
get() = sources == null
|
||||||
}
|
|
||||||
|
val isEmpty: Boolean
|
||||||
private fun sortFn(): java.util.Comparator<SourceItem> {
|
get() = sources.isNullOrEmpty()
|
||||||
val sort by lazy {
|
|
||||||
preferences.migrationSortingMode().get()
|
companion object {
|
||||||
}
|
val EMPTY = MigrateSourceState(null)
|
||||||
val direction by lazy {
|
|
||||||
preferences.migrationSortingDirection().get()
|
|
||||||
}
|
|
||||||
|
|
||||||
val locale = Locale.getDefault()
|
|
||||||
val collator = Collator.getInstance(locale).apply {
|
|
||||||
strength = Collator.PRIMARY
|
|
||||||
}
|
|
||||||
val sortFn: (SourceItem, SourceItem) -> Int = { a, b ->
|
|
||||||
when (sort) {
|
|
||||||
MigrationSourcesController.SortSetting.ALPHABETICAL -> collator.compare(a.source.name.lowercase(locale), b.source.name.lowercase(locale))
|
|
||||||
MigrationSourcesController.SortSetting.TOTAL -> a.mangaCount.compareTo(b.mangaCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (direction) {
|
|
||||||
MigrationSourcesController.DirectionSetting.ASCENDING -> Comparator(sortFn)
|
|
||||||
MigrationSourcesController.DirectionSetting.DESCENDING -> Collections.reverseOrder(sortFn)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Item that contains the selection header.
|
|
||||||
*/
|
|
||||||
class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the layout resource of this item.
|
|
||||||
*/
|
|
||||||
override fun getLayoutRes(): Int {
|
|
||||||
return R.layout.section_header_item
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new view holder for this item.
|
|
||||||
*/
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
|
||||||
return Holder(
|
|
||||||
view,
|
|
||||||
adapter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds this item to the given view holder.
|
|
||||||
*/
|
|
||||||
override fun bindViewHolder(
|
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
|
||||||
holder: Holder,
|
|
||||||
position: Int,
|
|
||||||
payloads: List<Any?>?,
|
|
||||||
) {
|
|
||||||
// Intentionally empty
|
|
||||||
}
|
|
||||||
|
|
||||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
|
||||||
|
|
||||||
private val binding = SectionHeaderItemBinding.bind(view)
|
|
||||||
|
|
||||||
init {
|
|
||||||
binding.title.text = view.context.getString(R.string.migration_selection_prompt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return other is SelectionHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|
||||||
|
|
||||||
import com.bluelinelabs.conductor.Controller
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter that holds the catalogue cards.
|
|
||||||
*
|
|
||||||
* @param controller instance of [MigrationController].
|
|
||||||
*/
|
|
||||||
class SourceAdapter(controller: Controller) :
|
|
||||||
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
setDisplayHeadersAtStartUp(true)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import coil.load
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceMainControllerItemBinding
|
|
||||||
import eu.kanade.tachiyomi.source.icon
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
|
|
||||||
class SourceHolder(view: View, val adapter: SourceAdapter) :
|
|
||||||
FlexibleViewHolder(view, adapter) {
|
|
||||||
|
|
||||||
private val binding = SourceMainControllerItemBinding.bind(view)
|
|
||||||
|
|
||||||
fun bind(item: SourceItem) {
|
|
||||||
val source = item.source
|
|
||||||
|
|
||||||
binding.title.text = "${source.name} (${item.mangaCount})"
|
|
||||||
binding.subtitle.isVisible = source.lang != ""
|
|
||||||
binding.subtitle.text = LocaleHelper.getDisplayName(source.lang)
|
|
||||||
|
|
||||||
itemView.post {
|
|
||||||
binding.image.load(source.icon())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Item that contains source information.
|
|
||||||
*
|
|
||||||
* @param source Instance of [Source] containing source information.
|
|
||||||
* @param header The header for this item.
|
|
||||||
*/
|
|
||||||
data class SourceItem(val source: Source, val mangaCount: Int, val header: SelectionHeader) :
|
|
||||||
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the layout resource of this item.
|
|
||||||
*/
|
|
||||||
override fun getLayoutRes(): Int {
|
|
||||||
return R.layout.source_main_controller_item
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new view holder for this item.
|
|
||||||
*/
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): SourceHolder {
|
|
||||||
return SourceHolder(
|
|
||||||
view,
|
|
||||||
adapter as SourceAdapter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds this item to the given view holder.
|
|
||||||
*/
|
|
||||||
override fun bindViewHolder(
|
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
|
||||||
holder: SourceHolder,
|
|
||||||
position: Int,
|
|
||||||
payloads: List<Any?>?,
|
|
||||||
) {
|
|
||||||
holder.bind(this)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout 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">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingBottom="@dimen/action_toolbar_list_padding" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialFastScroll
|
|
||||||
android:id="@+id/fast_scroller"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
app:fastScrollerBubbleEnabled="false"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.EmptyView
|
|
||||||
android:id="@+id/empty_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -1,54 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout 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="64dp"
|
|
||||||
android:background="@drawable/list_item_selector_background">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/image"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
tools:src="@mipmap/ic_launcher_round" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/subtitle"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/image"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
|
||||||
tools:text="Source title" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/subtitle"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/image"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
|
||||||
tools:text="English"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -28,4 +28,12 @@ CREATE INDEX mangas_url_index ON mangas(url);
|
||||||
getMangaById:
|
getMangaById:
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM mangas
|
FROM mangas
|
||||||
WHERE _id = :id;
|
WHERE _id = :id;
|
||||||
|
|
||||||
|
getSourceIdWithFavoriteCount:
|
||||||
|
SELECT
|
||||||
|
source,
|
||||||
|
count(*)
|
||||||
|
FROM mangas
|
||||||
|
WHERE favorite = 1
|
||||||
|
GROUP BY source;
|
Loading…
Reference in a new issue