Use Voyager on Library tab (#8620)
(cherry picked from commit e14909fff40360bec99acfb80ac7c1100c62ceb9) # Conflicts: # app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt # app/src/main/java/eu/kanade/presentation/library/LibraryState.kt # app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt # app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt # app/src/main/java/eu/kanade/presentation/library/components/LibraryToolbar.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt
This commit is contained in:
parent
cae8e18a9d
commit
9384d45282
@ -66,6 +66,7 @@ import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
|
|||||||
import eu.kanade.domain.source.repository.FeedSavedSearchRepository
|
import eu.kanade.domain.source.repository.FeedSavedSearchRepository
|
||||||
import eu.kanade.domain.source.repository.SavedSearchRepository
|
import eu.kanade.domain.source.repository.SavedSearchRepository
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
|
import exh.search.SearchEngine
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
import uy.kohesive.injekt.api.addFactory
|
import uy.kohesive.injekt.api.addFactory
|
||||||
@ -99,6 +100,7 @@ class SYDomainModule : InjektModule {
|
|||||||
addFactory { DeleteSortTag(get(), get()) }
|
addFactory { DeleteSortTag(get(), get()) }
|
||||||
addFactory { ReorderSortTag(get(), get()) }
|
addFactory { ReorderSortTag(get(), get()) }
|
||||||
addFactory { GetPagePreviews(get(), get()) }
|
addFactory { GetPagePreviews(get(), get()) }
|
||||||
|
addFactory { SearchEngine() }
|
||||||
|
|
||||||
// Required for [MetadataSource]
|
// Required for [MetadataSource]
|
||||||
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
|
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
|
||||||
|
@ -49,6 +49,9 @@ object CommonMangaItemDefaults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val ContinueReadingButtonSize = 32.dp
|
private val ContinueReadingButtonSize = 32.dp
|
||||||
|
private val ContinueReadingButtonGridPadding = 6.dp
|
||||||
|
private val ContinueReadingButtonListSpacing = 8.dp
|
||||||
|
|
||||||
private const val GridSelectedCoverAlpha = 0.76f
|
private const val GridSelectedCoverAlpha = 0.76f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,9 +64,8 @@ fun MangaCompactGridItem(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
coverData: eu.kanade.domain.manga.model.MangaCover,
|
coverData: eu.kanade.domain.manga.model.MangaCover,
|
||||||
coverAlpha: Float = 1f,
|
coverAlpha: Float = 1f,
|
||||||
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeStart: @Composable (RowScope.() -> Unit)? = null,
|
||||||
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeEnd: @Composable (RowScope.() -> Unit)? = null,
|
||||||
showContinueReadingButton: Boolean = false,
|
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onClickContinueReading: (() -> Unit)? = null,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
@ -86,12 +88,17 @@ fun MangaCompactGridItem(
|
|||||||
badgesEnd = coverBadgeEnd,
|
badgesEnd = coverBadgeEnd,
|
||||||
content = {
|
content = {
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
CoverTextOverlay(title = title, showContinueReadingButton)
|
CoverTextOverlay(
|
||||||
}
|
title = title,
|
||||||
},
|
onClickContinueReading = onClickContinueReading,
|
||||||
continueReadingButton = {
|
)
|
||||||
if (showContinueReadingButton && onClickContinueReading != null) {
|
} else if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(onClickContinueReading)
|
ContinueReadingButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(ContinueReadingButtonGridPadding)
|
||||||
|
.align(Alignment.BottomEnd),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -104,7 +111,7 @@ fun MangaCompactGridItem(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun BoxScope.CoverTextOverlay(
|
private fun BoxScope.CoverTextOverlay(
|
||||||
title: String,
|
title: String,
|
||||||
showContinueReadingButton: Boolean = false,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -119,20 +126,33 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter),
|
.align(Alignment.BottomCenter),
|
||||||
)
|
)
|
||||||
val endPadding = if (showContinueReadingButton) ContinueReadingButtonSize else 0.dp
|
Row(
|
||||||
GridItemTitle(
|
modifier = Modifier.align(Alignment.BottomStart),
|
||||||
modifier = Modifier
|
verticalAlignment = Alignment.Bottom,
|
||||||
.padding(start = 8.dp, top = 8.dp, end = endPadding + 8.dp, bottom = 8.dp)
|
) {
|
||||||
.align(Alignment.BottomStart),
|
GridItemTitle(
|
||||||
title = title,
|
modifier = Modifier
|
||||||
style = MaterialTheme.typography.titleSmall.copy(
|
.weight(1f)
|
||||||
color = Color.White,
|
.padding(8.dp),
|
||||||
shadow = Shadow(
|
title = title,
|
||||||
color = Color.Black,
|
style = MaterialTheme.typography.titleSmall.copy(
|
||||||
blurRadius = 4f,
|
color = Color.White,
|
||||||
|
shadow = Shadow(
|
||||||
|
color = Color.Black,
|
||||||
|
blurRadius = 4f,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
if (onClickContinueReading != null) {
|
||||||
|
ContinueReadingButton(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
end = ContinueReadingButtonGridPadding,
|
||||||
|
bottom = ContinueReadingButtonGridPadding,
|
||||||
|
),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,7 +166,6 @@ fun MangaComfortableGridItem(
|
|||||||
coverAlpha: Float = 1f,
|
coverAlpha: Float = 1f,
|
||||||
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||||
showContinueReadingButton: Boolean = false,
|
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onClickContinueReading: (() -> Unit)? = null,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
@ -168,9 +187,14 @@ fun MangaComfortableGridItem(
|
|||||||
},
|
},
|
||||||
badgesStart = coverBadgeStart,
|
badgesStart = coverBadgeStart,
|
||||||
badgesEnd = coverBadgeEnd,
|
badgesEnd = coverBadgeEnd,
|
||||||
continueReadingButton = {
|
content = {
|
||||||
if (showContinueReadingButton && onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(onClickContinueReading)
|
ContinueReadingButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(ContinueReadingButtonGridPadding)
|
||||||
|
.align(Alignment.BottomEnd),
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -192,7 +216,6 @@ private fun MangaGridCover(
|
|||||||
cover: @Composable BoxScope.() -> Unit = {},
|
cover: @Composable BoxScope.() -> Unit = {},
|
||||||
badgesStart: (@Composable RowScope.() -> Unit)? = null,
|
badgesStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
badgesEnd: (@Composable RowScope.() -> Unit)? = null,
|
badgesEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||||
continueReadingButton: (@Composable BoxScope.() -> Unit)? = null,
|
|
||||||
content: @Composable (BoxScope.() -> Unit)? = null,
|
content: @Composable (BoxScope.() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
@ -219,7 +242,6 @@ private fun MangaGridCover(
|
|||||||
content = badgesEnd,
|
content = badgesEnd,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
continueReadingButton?.invoke(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,8 +332,7 @@ fun MangaListItem(
|
|||||||
title: String,
|
title: String,
|
||||||
coverData: eu.kanade.domain.manga.model.MangaCover,
|
coverData: eu.kanade.domain.manga.model.MangaCover,
|
||||||
coverAlpha: Float = 1f,
|
coverAlpha: Float = 1f,
|
||||||
badge: @Composable RowScope.() -> Unit,
|
badge: @Composable (RowScope.() -> Unit),
|
||||||
showContinueReadingButton: Boolean = false,
|
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onClickContinueReading: (() -> Unit)? = null,
|
onClickContinueReading: (() -> Unit)? = null,
|
||||||
@ -343,23 +364,21 @@ fun MangaListItem(
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
BadgeGroup(content = badge)
|
BadgeGroup(content = badge)
|
||||||
if (showContinueReadingButton && onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
Box {
|
ContinueReadingButton(
|
||||||
ContinueReadingButton(onClickContinueReading)
|
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
||||||
}
|
onClickContinueReading = onClickContinueReading,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BoxScope.ContinueReadingButton(
|
private fun ContinueReadingButton(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
onClickContinueReading: () -> Unit,
|
onClickContinueReading: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(modifier = modifier) {
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp),
|
|
||||||
) {
|
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onClickContinueReading,
|
onClick = onClickContinueReading,
|
||||||
modifier = Modifier.size(ContinueReadingButtonSize),
|
modifier = Modifier.size(ContinueReadingButtonSize),
|
||||||
|
@ -1,148 +0,0 @@
|
|||||||
package eu.kanade.presentation.library
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.util.fastAll
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
|
||||||
import eu.kanade.presentation.components.EmptyScreenAction
|
|
||||||
import eu.kanade.presentation.components.LibraryBottomActionMenu
|
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.library.components.LibraryContent
|
|
||||||
import eu.kanade.presentation.library.components.LibraryToolbar
|
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryScreen(
|
|
||||||
presenter: LibraryPresenter,
|
|
||||||
onMangaClicked: (Long) -> Unit,
|
|
||||||
onContinueReadingClicked: (LibraryManga) -> Unit,
|
|
||||||
onGlobalSearchClicked: () -> Unit,
|
|
||||||
onChangeCategoryClicked: () -> Unit,
|
|
||||||
onMarkAsReadClicked: () -> Unit,
|
|
||||||
onMarkAsUnreadClicked: () -> Unit,
|
|
||||||
onDownloadClicked: (DownloadAction) -> Unit,
|
|
||||||
onDeleteClicked: () -> Unit,
|
|
||||||
onClickUnselectAll: () -> Unit,
|
|
||||||
onClickSelectAll: () -> Unit,
|
|
||||||
onClickInvertSelection: () -> Unit,
|
|
||||||
onClickFilter: () -> Unit,
|
|
||||||
onClickRefresh: (Category?) -> Boolean,
|
|
||||||
onClickOpenRandomManga: () -> Unit,
|
|
||||||
// SY -->
|
|
||||||
onClickCleanTitles: () -> Unit,
|
|
||||||
onClickMigrate: () -> Unit,
|
|
||||||
onClickAddToMangaDex: () -> Unit,
|
|
||||||
onClickSyncExh: () -> Unit,
|
|
||||||
// SY <--
|
|
||||||
) {
|
|
||||||
val haptic = LocalHapticFeedback.current
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = { scrollBehavior ->
|
|
||||||
val title by presenter.getToolbarTitle()
|
|
||||||
val tabVisible = presenter.tabVisibility && presenter.categories.size > 1
|
|
||||||
LibraryToolbar(
|
|
||||||
state = presenter,
|
|
||||||
title = title,
|
|
||||||
incognitoMode = !tabVisible && presenter.isIncognitoMode,
|
|
||||||
downloadedOnlyMode = !tabVisible && presenter.isDownloadOnly,
|
|
||||||
onClickUnselectAll = onClickUnselectAll,
|
|
||||||
onClickSelectAll = onClickSelectAll,
|
|
||||||
onClickInvertSelection = onClickInvertSelection,
|
|
||||||
onClickFilter = onClickFilter,
|
|
||||||
onClickRefresh = { onClickRefresh(null) },
|
|
||||||
onClickOpenRandomManga = onClickOpenRandomManga,
|
|
||||||
// SY -->
|
|
||||||
onClickSyncExh = onClickSyncExh,
|
|
||||||
// SY <--
|
|
||||||
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
|
|
||||||
)
|
|
||||||
},
|
|
||||||
bottomBar = {
|
|
||||||
LibraryBottomActionMenu(
|
|
||||||
visible = presenter.selectionMode,
|
|
||||||
onChangeCategoryClicked = onChangeCategoryClicked,
|
|
||||||
onMarkAsReadClicked = onMarkAsReadClicked,
|
|
||||||
onMarkAsUnreadClicked = onMarkAsUnreadClicked,
|
|
||||||
onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.fastAll { !it.manga.isLocal() } },
|
|
||||||
onDeleteClicked = onDeleteClicked,
|
|
||||||
// SY -->
|
|
||||||
onClickCleanTitles = onClickCleanTitles.takeIf { presenter.showCleanTitles },
|
|
||||||
onClickMigrate = onClickMigrate,
|
|
||||||
onClickAddToMangaDex = onClickAddToMangaDex.takeIf { presenter.showAddToMangadex },
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
if (presenter.isLoading) {
|
|
||||||
LoadingScreen()
|
|
||||||
return@Scaffold
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(paddingValues)
|
|
||||||
if (presenter.searchQuery.isNullOrEmpty() && presenter.isLibraryEmpty) {
|
|
||||||
val handler = LocalUriHandler.current
|
|
||||||
EmptyScreen(
|
|
||||||
textResource = R.string.information_empty_library,
|
|
||||||
modifier = Modifier.padding(contentPadding),
|
|
||||||
actions = listOf(
|
|
||||||
EmptyScreenAction(
|
|
||||||
stringResId = R.string.getting_started_guide,
|
|
||||||
icon = Icons.Outlined.HelpOutline,
|
|
||||||
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return@Scaffold
|
|
||||||
}
|
|
||||||
|
|
||||||
LibraryContent(
|
|
||||||
state = presenter,
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
currentPage = { presenter.activeCategory },
|
|
||||||
isLibraryEmpty = presenter.isLibraryEmpty,
|
|
||||||
showPageTabs = presenter.tabVisibility,
|
|
||||||
showMangaCount = presenter.mangaCountVisibility,
|
|
||||||
onChangeCurrentPage = { presenter.activeCategory = it },
|
|
||||||
onMangaClicked = onMangaClicked,
|
|
||||||
onContinueReadingClicked = onContinueReadingClicked,
|
|
||||||
onToggleSelection = { presenter.toggleSelection(it) },
|
|
||||||
onToggleRangeSelection = {
|
|
||||||
presenter.toggleRangeSelection(it)
|
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
|
||||||
},
|
|
||||||
onRefresh = onClickRefresh,
|
|
||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
|
||||||
getNumberOfMangaForCategory = { presenter.getMangaCountForCategory(it) },
|
|
||||||
// SY -->
|
|
||||||
getDisplayModeForPage = { presenter.getDisplayMode(index = it) },
|
|
||||||
// SY <--
|
|
||||||
getColumnsForOrientation = { presenter.getColumnsPreferenceForCurrentOrientation(it) },
|
|
||||||
getLibraryForPage = { presenter.getMangaForCategory(page = it) },
|
|
||||||
showDownloadBadges = presenter.showDownloadBadges,
|
|
||||||
showUnreadBadges = presenter.showUnreadBadges,
|
|
||||||
showLocalBadges = presenter.showLocalBadges,
|
|
||||||
showLanguageBadges = presenter.showLanguageBadges,
|
|
||||||
showContinueReadingButton = presenter.showContinueReadingButton,
|
|
||||||
isIncognitoMode = presenter.isIncognitoMode,
|
|
||||||
isDownloadOnly = presenter.isDownloadOnly,
|
|
||||||
// SY -->
|
|
||||||
getCategoryName = presenter::getCategoryName,
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package eu.kanade.presentation.library
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.library.model.LibraryGroup
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
|
||||||
import exh.source.isEhBasedManga
|
|
||||||
import exh.source.mangaDexSourceIds
|
|
||||||
import exh.source.nHentaiSourceIds
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
interface LibraryState {
|
|
||||||
val isLoading: Boolean
|
|
||||||
val categories: List<Category>
|
|
||||||
var searchQuery: String?
|
|
||||||
val selection: List<LibraryManga>
|
|
||||||
val selectionMode: Boolean
|
|
||||||
var hasActiveFilters: Boolean
|
|
||||||
var dialog: LibraryPresenter.Dialog?
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
val ogCategories: List<Category>
|
|
||||||
val groupType: Int
|
|
||||||
val showSyncExh: Boolean
|
|
||||||
val showCleanTitles: Boolean
|
|
||||||
val showAddToMangadex: Boolean
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
|
|
||||||
fun LibraryState(): LibraryState {
|
|
||||||
return LibraryStateImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
class LibraryStateImpl : LibraryState {
|
|
||||||
override var isLoading: Boolean by mutableStateOf(true)
|
|
||||||
override var categories: List<Category> by mutableStateOf(emptyList())
|
|
||||||
override var searchQuery: String? by mutableStateOf(null)
|
|
||||||
override var selection: List<LibraryManga> by mutableStateOf(emptyList())
|
|
||||||
override val selectionMode: Boolean by derivedStateOf { selection.isNotEmpty() }
|
|
||||||
override var hasActiveFilters: Boolean by mutableStateOf(false)
|
|
||||||
override var dialog: LibraryPresenter.Dialog? by mutableStateOf(null)
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
override var groupType: Int by mutableStateOf(LibraryGroup.BY_DEFAULT)
|
|
||||||
|
|
||||||
override var ogCategories: List<Category> by mutableStateOf(emptyList())
|
|
||||||
|
|
||||||
override var showSyncExh: Boolean by mutableStateOf(true)
|
|
||||||
override val showCleanTitles: Boolean by derivedStateOf {
|
|
||||||
selection.any {
|
|
||||||
it.manga.isEhBasedManga() ||
|
|
||||||
it.manga.source in nHentaiSourceIds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override val showAddToMangadex: Boolean by derivedStateOf {
|
|
||||||
selection.any { it.manga.source in mangaDexSourceIds }
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
}
|
|
@ -5,16 +5,12 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.Badge
|
import eu.kanade.presentation.components.Badge
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DownloadsBadge(
|
fun DownloadsBadge(count: Int) {
|
||||||
enabled: Boolean,
|
if (count > 0) {
|
||||||
item: LibraryItem,
|
|
||||||
) {
|
|
||||||
if (enabled && item.downloadCount > 0) {
|
|
||||||
Badge(
|
Badge(
|
||||||
text = "${item.downloadCount}",
|
text = "$count",
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
@ -22,30 +18,26 @@ fun DownloadsBadge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UnreadBadge(
|
fun UnreadBadge(count: Int) {
|
||||||
enabled: Boolean,
|
if (count > 0) {
|
||||||
item: LibraryItem,
|
Badge(text = "$count")
|
||||||
) {
|
|
||||||
if (enabled && item.unreadCount > 0) {
|
|
||||||
Badge(text = "${item.unreadCount}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LanguageBadge(
|
fun LanguageBadge(
|
||||||
showLanguage: Boolean,
|
isLocal: Boolean,
|
||||||
showLocal: Boolean,
|
sourceLanguage: String,
|
||||||
item: LibraryItem,
|
|
||||||
) {
|
) {
|
||||||
if (showLocal && item.isLocal) {
|
if (isLocal) {
|
||||||
Badge(
|
Badge(
|
||||||
text = stringResource(R.string.local_source_badge),
|
text = stringResource(R.string.local_source_badge),
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
} else if (showLanguage && item.sourceLanguage.isNotEmpty()) {
|
} else if (sourceLanguage.isNotEmpty()) {
|
||||||
Badge(
|
Badge(
|
||||||
text = item.sourceLanguage.uppercase(),
|
text = sourceLanguage.uppercase(),
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
textColor = MaterialTheme.colorScheme.onTertiary,
|
textColor = MaterialTheme.colorScheme.onTertiary,
|
||||||
)
|
)
|
||||||
|
@ -14,17 +14,12 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|||||||
@Composable
|
@Composable
|
||||||
fun LibraryComfortableGrid(
|
fun LibraryComfortableGrid(
|
||||||
items: List<LibraryItem>,
|
items: List<LibraryItem>,
|
||||||
showDownloadBadges: Boolean,
|
|
||||||
showUnreadBadges: Boolean,
|
|
||||||
showLocalBadges: Boolean,
|
|
||||||
showLanguageBadges: Boolean,
|
|
||||||
showContinueReadingButton: Boolean,
|
|
||||||
columns: Int,
|
columns: Int,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
selection: List<LibraryManga>,
|
selection: List<LibraryManga>,
|
||||||
onClick: (LibraryManga) -> Unit,
|
onClick: (LibraryManga) -> Unit,
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
onLongClick: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: (LibraryManga) -> Unit,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -51,26 +46,22 @@ fun LibraryComfortableGrid(
|
|||||||
lastModified = manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
coverBadgeStart = {
|
coverBadgeStart = {
|
||||||
DownloadsBadge(
|
DownloadsBadge(count = libraryItem.downloadCount.toInt())
|
||||||
enabled = showDownloadBadges,
|
UnreadBadge(count = libraryItem.unreadCount.toInt())
|
||||||
item = libraryItem,
|
|
||||||
)
|
|
||||||
UnreadBadge(
|
|
||||||
enabled = showUnreadBadges,
|
|
||||||
item = libraryItem,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
coverBadgeEnd = {
|
coverBadgeEnd = {
|
||||||
LanguageBadge(
|
LanguageBadge(
|
||||||
showLanguage = showLanguageBadges,
|
isLocal = libraryItem.isLocal,
|
||||||
showLocal = showLocalBadges,
|
sourceLanguage = libraryItem.sourceLanguage,
|
||||||
item = libraryItem,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
onClick = { onClick(libraryItem.libraryManga) },
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
|
onClickContinueReading = if (onClickContinueReading != null) {
|
||||||
|
{ onClickContinueReading(libraryItem.libraryManga) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,12 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|||||||
fun LibraryCompactGrid(
|
fun LibraryCompactGrid(
|
||||||
items: List<LibraryItem>,
|
items: List<LibraryItem>,
|
||||||
showTitle: Boolean,
|
showTitle: Boolean,
|
||||||
showDownloadBadges: Boolean,
|
|
||||||
showUnreadBadges: Boolean,
|
|
||||||
showLocalBadges: Boolean,
|
|
||||||
showLanguageBadges: Boolean,
|
|
||||||
showContinueReadingButton: Boolean,
|
|
||||||
columns: Int,
|
columns: Int,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
selection: List<LibraryManga>,
|
selection: List<LibraryManga>,
|
||||||
onClick: (LibraryManga) -> Unit,
|
onClick: (LibraryManga) -> Unit,
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
onLongClick: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: (LibraryManga) -> Unit,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -52,26 +47,22 @@ fun LibraryCompactGrid(
|
|||||||
lastModified = manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
coverBadgeStart = {
|
coverBadgeStart = {
|
||||||
DownloadsBadge(
|
DownloadsBadge(count = libraryItem.downloadCount.toInt())
|
||||||
enabled = showDownloadBadges,
|
UnreadBadge(count = libraryItem.unreadCount.toInt())
|
||||||
item = libraryItem,
|
|
||||||
)
|
|
||||||
UnreadBadge(
|
|
||||||
enabled = showUnreadBadges,
|
|
||||||
item = libraryItem,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
coverBadgeEnd = {
|
coverBadgeEnd = {
|
||||||
LanguageBadge(
|
LanguageBadge(
|
||||||
showLanguage = showLanguageBadges,
|
isLocal = libraryItem.isLocal,
|
||||||
showLocal = showLocalBadges,
|
sourceLanguage = libraryItem.sourceLanguage,
|
||||||
item = libraryItem,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
onClick = { onClick(libraryItem.libraryManga) },
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
|
onClickContinueReading = if (onClickContinueReading != null) {
|
||||||
|
{ onClickContinueReading(libraryItem.libraryManga) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.library.components
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
@ -8,14 +7,12 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
|
||||||
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.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
@ -23,7 +20,6 @@ import eu.kanade.domain.library.model.LibraryDisplayMode
|
|||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
import eu.kanade.presentation.components.SwipeRefresh
|
import eu.kanade.presentation.components.SwipeRefresh
|
||||||
import eu.kanade.presentation.components.rememberPagerState
|
import eu.kanade.presentation.components.rememberPagerState
|
||||||
import eu.kanade.presentation.library.LibraryState
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -31,33 +27,26 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryContent(
|
fun LibraryContent(
|
||||||
state: LibraryState,
|
categories: List<Category>,
|
||||||
|
searchQuery: String?,
|
||||||
|
selection: List<LibraryManga>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
currentPage: () -> Int,
|
currentPage: () -> Int,
|
||||||
isLibraryEmpty: Boolean,
|
isLibraryEmpty: Boolean,
|
||||||
showPageTabs: Boolean,
|
showPageTabs: Boolean,
|
||||||
showMangaCount: Boolean,
|
|
||||||
onChangeCurrentPage: (Int) -> Unit,
|
onChangeCurrentPage: (Int) -> Unit,
|
||||||
onMangaClicked: (Long) -> Unit,
|
onMangaClicked: (Long) -> Unit,
|
||||||
onContinueReadingClicked: (LibraryManga) -> Unit,
|
onContinueReadingClicked: ((LibraryManga) -> Unit)?,
|
||||||
onToggleSelection: (LibraryManga) -> Unit,
|
onToggleSelection: (LibraryManga) -> Unit,
|
||||||
onToggleRangeSelection: (LibraryManga) -> Unit,
|
onToggleRangeSelection: (LibraryManga) -> Unit,
|
||||||
onRefresh: (Category?) -> Boolean,
|
onRefresh: (Category?) -> Boolean,
|
||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
|
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||||
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
|
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
|
||||||
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
||||||
getLibraryForPage: @Composable (Int) -> List<LibraryItem>,
|
getLibraryForPage: (Int) -> List<LibraryItem>,
|
||||||
showDownloadBadges: Boolean,
|
|
||||||
showUnreadBadges: Boolean,
|
|
||||||
showLocalBadges: Boolean,
|
|
||||||
showLanguageBadges: Boolean,
|
|
||||||
showContinueReadingButton: Boolean,
|
|
||||||
isDownloadOnly: Boolean,
|
isDownloadOnly: Boolean,
|
||||||
isIncognitoMode: Boolean,
|
isIncognitoMode: Boolean,
|
||||||
// SY -->
|
|
||||||
getCategoryName: (Context, Category, Int, String) -> String,
|
|
||||||
// SY <--
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
@ -66,46 +55,30 @@ fun LibraryContent(
|
|||||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
val categories = state.categories
|
|
||||||
val coercedCurrentPage = remember { currentPage().coerceAtMost(categories.lastIndex) }
|
val coercedCurrentPage = remember { currentPage().coerceAtMost(categories.lastIndex) }
|
||||||
val pagerState = rememberPagerState(coercedCurrentPage)
|
val pagerState = rememberPagerState(coercedCurrentPage)
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
|
||||||
|
|
||||||
if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) {
|
if (!isLibraryEmpty && showPageTabs && categories.size > 1) {
|
||||||
LibraryTabs(
|
LibraryTabs(
|
||||||
categories = categories,
|
categories = categories,
|
||||||
currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex),
|
currentPageIndex = pagerState.currentPage,
|
||||||
showMangaCount = showMangaCount,
|
|
||||||
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
|
|
||||||
isDownloadOnly = isDownloadOnly,
|
isDownloadOnly = isDownloadOnly,
|
||||||
isIncognitoMode = isIncognitoMode,
|
isIncognitoMode = isIncognitoMode,
|
||||||
onTabItemClick = { scope.launch { pagerState.animateScrollToPage(it) } },
|
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
|
||||||
// SY -->
|
) { scope.launch { pagerState.animateScrollToPage(it) } }
|
||||||
getCategoryName = { category, name ->
|
|
||||||
val context = LocalContext.current
|
|
||||||
remember(context, category, state.groupType, name) {
|
|
||||||
getCategoryName(context, category, state.groupType, name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val notSelectionMode = selection.isEmpty()
|
||||||
val onClickManga = { manga: LibraryManga ->
|
val onClickManga = { manga: LibraryManga ->
|
||||||
if (state.selectionMode.not()) {
|
if (notSelectionMode) {
|
||||||
onMangaClicked(manga.manga.id)
|
onMangaClicked(manga.manga.id)
|
||||||
} else {
|
} else {
|
||||||
onToggleSelection(manga)
|
onToggleSelection(manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val onLongClickManga = { manga: LibraryManga ->
|
|
||||||
onToggleRangeSelection(manga)
|
|
||||||
}
|
|
||||||
val onClickContinueReading = { manga: LibraryManga ->
|
|
||||||
onContinueReadingClicked(manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeRefresh(
|
SwipeRefresh(
|
||||||
refreshing = isRefreshing,
|
refreshing = isRefreshing,
|
||||||
@ -119,26 +92,21 @@ fun LibraryContent(
|
|||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = state.selectionMode.not(),
|
enabled = notSelectionMode,
|
||||||
) {
|
) {
|
||||||
LibraryPager(
|
LibraryPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||||
pageCount = categories.size,
|
pageCount = categories.size,
|
||||||
selectedManga = state.selection,
|
selectedManga = selection,
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||||
getDisplayModeForPage = getDisplayModeForPage,
|
getDisplayModeForPage = getDisplayModeForPage,
|
||||||
getColumnsForOrientation = getColumnsForOrientation,
|
getColumnsForOrientation = getColumnsForOrientation,
|
||||||
getLibraryForPage = getLibraryForPage,
|
getLibraryForPage = getLibraryForPage,
|
||||||
showDownloadBadges = showDownloadBadges,
|
|
||||||
showUnreadBadges = showUnreadBadges,
|
|
||||||
showLocalBadges = showLocalBadges,
|
|
||||||
showLanguageBadges = showLanguageBadges,
|
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
onClickManga = onClickManga,
|
onClickManga = onClickManga,
|
||||||
onLongClickManga = onLongClickManga,
|
onLongClickManga = onToggleRangeSelection,
|
||||||
onClickContinueReading = onClickContinueReading,
|
onClickContinueReading = onContinueReadingClicked,
|
||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
|
||||||
searchQuery = state.searchQuery,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,16 +23,11 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|||||||
@Composable
|
@Composable
|
||||||
fun LibraryList(
|
fun LibraryList(
|
||||||
items: List<LibraryItem>,
|
items: List<LibraryItem>,
|
||||||
showDownloadBadges: Boolean,
|
|
||||||
showUnreadBadges: Boolean,
|
|
||||||
showLocalBadges: Boolean,
|
|
||||||
showLanguageBadges: Boolean,
|
|
||||||
showContinueReadingButton: Boolean,
|
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
selection: List<LibraryManga>,
|
selection: List<LibraryManga>,
|
||||||
onClick: (LibraryManga) -> Unit,
|
onClick: (LibraryManga) -> Unit,
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
onLongClick: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: (LibraryManga) -> Unit,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -41,13 +36,13 @@ fun LibraryList(
|
|||||||
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
|
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
if (searchQuery.isNullOrEmpty().not()) {
|
if (!searchQuery.isNullOrEmpty()) {
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = onGlobalSearchClicked,
|
onClick = onGlobalSearchClicked,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.action_global_search_query, searchQuery!!),
|
text = stringResource(R.string.action_global_search_query, searchQuery),
|
||||||
modifier = Modifier.zIndex(99f),
|
modifier = Modifier.zIndex(99f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -70,14 +65,20 @@ fun LibraryList(
|
|||||||
lastModified = manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
badge = {
|
badge = {
|
||||||
DownloadsBadge(enabled = showDownloadBadges, item = libraryItem)
|
DownloadsBadge(count = libraryItem.downloadCount.toInt())
|
||||||
UnreadBadge(enabled = showUnreadBadges, item = libraryItem)
|
UnreadBadge(count = libraryItem.unreadCount.toInt())
|
||||||
LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem)
|
LanguageBadge(
|
||||||
|
isLocal = libraryItem.isLocal,
|
||||||
|
sourceLanguage = libraryItem.sourceLanguage,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
onClick = { onClick(libraryItem.libraryManga) },
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
|
onClickContinueReading = if (onClickContinueReading != null) {
|
||||||
|
{ onClickContinueReading(libraryItem.libraryManga) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,15 +27,10 @@ fun LibraryPager(
|
|||||||
onGlobalSearchClicked: () -> Unit,
|
onGlobalSearchClicked: () -> Unit,
|
||||||
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
|
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
|
||||||
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
|
||||||
getLibraryForPage: @Composable (Int) -> List<LibraryItem>,
|
getLibraryForPage: (Int) -> List<LibraryItem>,
|
||||||
showDownloadBadges: Boolean,
|
|
||||||
showUnreadBadges: Boolean,
|
|
||||||
showLocalBadges: Boolean,
|
|
||||||
showLanguageBadges: Boolean,
|
|
||||||
showContinueReadingButton: Boolean,
|
|
||||||
onClickManga: (LibraryManga) -> Unit,
|
onClickManga: (LibraryManga) -> Unit,
|
||||||
onLongClickManga: (LibraryManga) -> Unit,
|
onLongClickManga: (LibraryManga) -> Unit,
|
||||||
onClickContinueReading: (LibraryManga) -> Unit,
|
onClickContinueReading: ((LibraryManga) -> Unit)?,
|
||||||
) {
|
) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
count = pageCount,
|
count = pageCount,
|
||||||
@ -62,11 +57,6 @@ fun LibraryPager(
|
|||||||
LibraryDisplayMode.List -> {
|
LibraryDisplayMode.List -> {
|
||||||
LibraryList(
|
LibraryList(
|
||||||
items = library,
|
items = library,
|
||||||
showDownloadBadges = showDownloadBadges,
|
|
||||||
showUnreadBadges = showUnreadBadges,
|
|
||||||
showLocalBadges = showLocalBadges,
|
|
||||||
showLanguageBadges = showLanguageBadges,
|
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
selection = selectedManga,
|
selection = selectedManga,
|
||||||
onClick = onClickManga,
|
onClick = onClickManga,
|
||||||
@ -80,11 +70,6 @@ fun LibraryPager(
|
|||||||
LibraryCompactGrid(
|
LibraryCompactGrid(
|
||||||
items = library,
|
items = library,
|
||||||
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
|
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
|
||||||
showDownloadBadges = showDownloadBadges,
|
|
||||||
showUnreadBadges = showUnreadBadges,
|
|
||||||
showLocalBadges = showLocalBadges,
|
|
||||||
showLanguageBadges = showLanguageBadges,
|
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
selection = selectedManga,
|
selection = selectedManga,
|
||||||
@ -98,17 +83,12 @@ fun LibraryPager(
|
|||||||
LibraryDisplayMode.ComfortableGrid -> {
|
LibraryDisplayMode.ComfortableGrid -> {
|
||||||
LibraryComfortableGrid(
|
LibraryComfortableGrid(
|
||||||
items = library,
|
items = library,
|
||||||
showDownloadBadges = showDownloadBadges,
|
|
||||||
showUnreadBadges = showUnreadBadges,
|
|
||||||
showLocalBadges = showLocalBadges,
|
|
||||||
showLanguageBadges = showLanguageBadges,
|
|
||||||
showContinueReadingButton = showContinueReadingButton,
|
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
selection = selectedManga,
|
selection = selectedManga,
|
||||||
onClick = onClickManga,
|
onClick = onClickManga,
|
||||||
onClickContinueReading = onClickContinueReading,
|
|
||||||
onLongClick = onLongClickManga,
|
onLongClick = onLongClickManga,
|
||||||
|
onClickContinueReading = onClickContinueReading,
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,6 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
@ -18,14 +17,10 @@ import eu.kanade.presentation.components.TabText
|
|||||||
fun LibraryTabs(
|
fun LibraryTabs(
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
currentPageIndex: Int,
|
currentPageIndex: Int,
|
||||||
showMangaCount: Boolean,
|
|
||||||
isDownloadOnly: Boolean,
|
isDownloadOnly: Boolean,
|
||||||
isIncognitoMode: Boolean,
|
isIncognitoMode: Boolean,
|
||||||
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
|
getNumberOfMangaForCategory: (Category) -> Int?,
|
||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
// SY -->
|
|
||||||
getCategoryName: @Composable (Category, String) -> String,
|
|
||||||
// SY <--
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
ScrollableTabRow(
|
ScrollableTabRow(
|
||||||
@ -42,14 +37,8 @@ fun LibraryTabs(
|
|||||||
onClick = { onTabItemClick(index) },
|
onClick = { onTabItemClick(index) },
|
||||||
text = {
|
text = {
|
||||||
TabText(
|
TabText(
|
||||||
// SY -->
|
text = category.visualName,
|
||||||
text = getCategoryName(category, category.visualName),
|
badgeCount = getNumberOfMangaForCategory(category),
|
||||||
// SY <--,
|
|
||||||
badgeCount = if (showMangaCount) {
|
|
||||||
getNumberOfMangaForCategory(category.id)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}?.value,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
@ -14,6 +14,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@ -23,13 +24,13 @@ import eu.kanade.presentation.components.AppBar
|
|||||||
import eu.kanade.presentation.components.OverflowMenu
|
import eu.kanade.presentation.components.OverflowMenu
|
||||||
import eu.kanade.presentation.components.Pill
|
import eu.kanade.presentation.components.Pill
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.library.LibraryState
|
|
||||||
import eu.kanade.presentation.theme.active
|
import eu.kanade.presentation.theme.active
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryToolbar(
|
fun LibraryToolbar(
|
||||||
state: LibraryState,
|
hasActiveFilters: Boolean,
|
||||||
|
selectedCount: Int,
|
||||||
title: LibraryToolbarTitle,
|
title: LibraryToolbarTitle,
|
||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
@ -40,12 +41,14 @@ fun LibraryToolbar(
|
|||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
onClickOpenRandomManga: () -> Unit,
|
onClickOpenRandomManga: () -> Unit,
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickSyncExh: () -> Unit,
|
onClickSyncExh: (() -> Unit)?,
|
||||||
// SY <--
|
// SY <--
|
||||||
|
searchQuery: String?,
|
||||||
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior?,
|
scrollBehavior: TopAppBarScrollBehavior?,
|
||||||
) = when {
|
) = when {
|
||||||
state.selectionMode -> LibrarySelectionToolbar(
|
selectedCount > 0 -> LibrarySelectionToolbar(
|
||||||
state = state,
|
selectedCount = selectedCount,
|
||||||
incognitoMode = incognitoMode,
|
incognitoMode = incognitoMode,
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
downloadedOnlyMode = downloadedOnlyMode,
|
||||||
onClickUnselectAll = onClickUnselectAll,
|
onClickUnselectAll = onClickUnselectAll,
|
||||||
@ -54,16 +57,16 @@ fun LibraryToolbar(
|
|||||||
)
|
)
|
||||||
else -> LibraryRegularToolbar(
|
else -> LibraryRegularToolbar(
|
||||||
title = title,
|
title = title,
|
||||||
hasFilters = state.hasActiveFilters,
|
hasFilters = hasActiveFilters,
|
||||||
incognitoMode = incognitoMode,
|
incognitoMode = incognitoMode,
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
downloadedOnlyMode = downloadedOnlyMode,
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = searchQuery,
|
||||||
onChangeSearchQuery = { state.searchQuery = it },
|
onSearchQueryChange = onSearchQueryChange,
|
||||||
onClickFilter = onClickFilter,
|
onClickFilter = onClickFilter,
|
||||||
onClickRefresh = onClickRefresh,
|
onClickRefresh = onClickRefresh,
|
||||||
onClickOpenRandomManga = onClickOpenRandomManga,
|
onClickOpenRandomManga = onClickOpenRandomManga,
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickSyncExh = onClickSyncExh.takeIf { state.showSyncExh },
|
onClickSyncExh = onClickSyncExh,
|
||||||
// SY <--
|
// SY <--
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -76,7 +79,7 @@ fun LibraryRegularToolbar(
|
|||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
onClickFilter: () -> Unit,
|
onClickFilter: () -> Unit,
|
||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
onClickOpenRandomManga: () -> Unit,
|
onClickOpenRandomManga: () -> Unit,
|
||||||
@ -105,7 +108,7 @@ fun LibraryRegularToolbar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchQuery = searchQuery,
|
searchQuery = searchQuery,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onSearchQueryChange,
|
||||||
actions = {
|
actions = {
|
||||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||||
IconButton(onClick = onClickFilter) {
|
IconButton(onClick = onClickFilter) {
|
||||||
@ -148,7 +151,7 @@ fun LibraryRegularToolbar(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibrarySelectionToolbar(
|
fun LibrarySelectionToolbar(
|
||||||
state: LibraryState,
|
selectedCount: Int,
|
||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
onClickUnselectAll: () -> Unit,
|
onClickUnselectAll: () -> Unit,
|
||||||
@ -156,7 +159,7 @@ fun LibrarySelectionToolbar(
|
|||||||
onClickInvertSelection: () -> Unit,
|
onClickInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
AppBar(
|
AppBar(
|
||||||
titleContent = { Text(text = "${state.selection.size}") },
|
titleContent = { Text(text = "$selectedCount") },
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onClickSelectAll) {
|
IconButton(onClick = onClickSelectAll) {
|
||||||
Icon(Icons.Outlined.SelectAll, contentDescription = stringResource(R.string.action_select_all))
|
Icon(Icons.Outlined.SelectAll, contentDescription = stringResource(R.string.action_select_all))
|
||||||
@ -172,6 +175,7 @@ fun LibrarySelectionToolbar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class LibraryToolbarTitle(
|
data class LibraryToolbarTitle(
|
||||||
val text: String,
|
val text: String,
|
||||||
val numberOfManga: Int? = null,
|
val numberOfManga: Int? = null,
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SyncFavoritesConfirmDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onAccept: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onAccept) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.favorites_sync))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.favorites_sync_conformation_message))
|
||||||
|
},
|
||||||
|
properties = DialogProperties(dismissOnClickOutside = false),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import exh.favorites.FavoritesSyncStatus
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
data class SyncFavoritesProgressProperties(
|
||||||
|
val title: String,
|
||||||
|
val text: String,
|
||||||
|
val canDismiss: Boolean,
|
||||||
|
val positiveButtonText: String? = null,
|
||||||
|
val positiveButton: (() -> Unit)? = null,
|
||||||
|
val negativeButtonText: String? = null,
|
||||||
|
val negativeButton: (() -> Unit)? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SyncFavoritesProgressDialog(
|
||||||
|
status: FavoritesSyncStatus,
|
||||||
|
setStatusIdle: () -> Unit,
|
||||||
|
openManga: (Manga) -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
|
||||||
|
when (status) {
|
||||||
|
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.getString(R.string.favorites_sync_error),
|
||||||
|
text = context.getString(R.string.favorites_sync_bad_library_state, status.message),
|
||||||
|
canDismiss = false,
|
||||||
|
positiveButtonText = context.getString(R.string.show_gallery),
|
||||||
|
positiveButton = {
|
||||||
|
openManga(status.manga)
|
||||||
|
setStatusIdle()
|
||||||
|
},
|
||||||
|
negativeButtonText = context.getString(android.R.string.ok),
|
||||||
|
negativeButton = setStatusIdle,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.getString(R.string.favorites_sync_done_errors),
|
||||||
|
text = context.getString(R.string.favorites_sync_done_errors_message, status.message),
|
||||||
|
canDismiss = false,
|
||||||
|
positiveButtonText = context.getString(android.R.string.ok),
|
||||||
|
positiveButton = setStatusIdle,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.getString(R.string.favorites_sync_error),
|
||||||
|
text = context.getString(R.string.favorites_sync_error_string, status.message),
|
||||||
|
canDismiss = false,
|
||||||
|
positiveButtonText = context.getString(android.R.string.ok),
|
||||||
|
positiveButton = setStatusIdle,
|
||||||
|
)
|
||||||
|
is FavoritesSyncStatus.Idle -> value = null
|
||||||
|
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> {
|
||||||
|
value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.getString(R.string.favorites_syncing),
|
||||||
|
text = status.message,
|
||||||
|
canDismiss = false,
|
||||||
|
)
|
||||||
|
if (status is FavoritesSyncStatus.Processing && status.title != null) {
|
||||||
|
delay(5.seconds)
|
||||||
|
value = SyncFavoritesProgressProperties(
|
||||||
|
title = context.getString(R.string.favorites_syncing),
|
||||||
|
text = status.delayedMessage ?: status.message,
|
||||||
|
canDismiss = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val dialog = properties
|
||||||
|
if (dialog != null) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
confirmButton = {
|
||||||
|
if (dialog.positiveButton != null && dialog.positiveButtonText != null) {
|
||||||
|
TextButton(onClick = dialog.positiveButton) {
|
||||||
|
Text(text = dialog.positiveButtonText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
if (dialog.negativeButton != null && dialog.negativeButtonText != null) {
|
||||||
|
TextButton(onClick = dialog.negativeButton) {
|
||||||
|
Text(text = dialog.negativeButtonText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = dialog.title)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Text(text = dialog.text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnClickOutside = dialog.canDismiss,
|
||||||
|
dismissOnBackPress = dialog.canDismiss,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import exh.util.toAnnotatedString
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SyncFavoritesWarningDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onAccept: () -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val text = remember {
|
||||||
|
HtmlCompat.fromHtml(context.getString(R.string.favorites_sync_notes_message), HtmlCompat.FROM_HTML_MODE_LEGACY).toAnnotatedString()
|
||||||
|
}
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onAccept) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(stringResource(R.string.favorites_sync_notes))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Text(text = text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties = DialogProperties(dismissOnClickOutside = false),
|
||||||
|
)
|
||||||
|
}
|
@ -1,246 +1,37 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import eu.kanade.domain.category.model.Category
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.core.prefs.CheckboxState
|
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.library.model.LibraryGroup
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
|
||||||
import eu.kanade.presentation.components.ChangeCategoryDialog
|
|
||||||
import eu.kanade.presentation.components.DeleteLibraryMangaDialog
|
|
||||||
import eu.kanade.presentation.library.LibraryScreen
|
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
|
||||||
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import exh.favorites.FavoritesIntroDialog
|
|
||||||
import exh.favorites.FavoritesSyncStatus
|
|
||||||
import exh.source.MERGED_SOURCE_ID
|
|
||||||
import exh.source.isEhBasedManga
|
|
||||||
import exh.source.mangaDexSourceIds
|
|
||||||
import exh.source.nHentaiSourceIds
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.flow.sample
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
class LibraryController(
|
class LibraryController(
|
||||||
bundle: Bundle? = null,
|
bundle: Bundle? = null,
|
||||||
) : FullComposeController<LibraryPresenter>(bundle), RootController {
|
) : BasicFullComposeController(bundle), RootController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sheet containing filter/sort/display items.
|
* Sheet containing filter/sort/display items.
|
||||||
*/
|
*/
|
||||||
private var settingsSheet: LibrarySettingsSheet? = null
|
private var settingsSheet: LibrarySettingsSheet? = null
|
||||||
|
|
||||||
// --> EH
|
|
||||||
// Sync dialog
|
|
||||||
private var favSyncDialog: AlertDialog? = null
|
|
||||||
|
|
||||||
// Old sync status
|
|
||||||
private var oldSyncStatus: FavoritesSyncStatus? = null
|
|
||||||
|
|
||||||
// Favorites
|
|
||||||
private var favoritesSyncJob: Job? = null
|
|
||||||
// <-- EH
|
|
||||||
|
|
||||||
override fun createPresenter(): LibraryPresenter = LibraryPresenter()
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
val context = LocalContext.current
|
Navigator(screen = LibraryScreen)
|
||||||
val getMangaForCategory = presenter.getMangaForCategory(page = presenter.activeCategory)
|
|
||||||
|
|
||||||
LibraryScreen(
|
|
||||||
presenter = presenter,
|
|
||||||
onMangaClicked = ::openManga,
|
|
||||||
onContinueReadingClicked = ::continueReading,
|
|
||||||
onGlobalSearchClicked = {
|
|
||||||
router.pushController(GlobalSearchController(presenter.searchQuery))
|
|
||||||
},
|
|
||||||
onChangeCategoryClicked = ::showMangaCategoriesDialog,
|
|
||||||
onMarkAsReadClicked = { markReadStatus(true) },
|
|
||||||
onMarkAsUnreadClicked = { markReadStatus(false) },
|
|
||||||
onDownloadClicked = ::runDownloadChapterAction,
|
|
||||||
onDeleteClicked = ::showDeleteMangaDialog,
|
|
||||||
onClickFilter = ::showSettingsSheet,
|
|
||||||
onClickRefresh = {
|
|
||||||
// SY -->
|
|
||||||
val groupType = presenter.groupType
|
|
||||||
// SY -->
|
|
||||||
val started = LibraryUpdateService.start(
|
|
||||||
context = context,
|
|
||||||
category = if (groupType == LibraryGroup.BY_DEFAULT) it else null,
|
|
||||||
group = groupType,
|
|
||||||
groupExtra = when (groupType) {
|
|
||||||
LibraryGroup.BY_DEFAULT -> null
|
|
||||||
LibraryGroup.BY_SOURCE, LibraryGroup.BY_STATUS, LibraryGroup.BY_TRACK_STATUS -> it?.id?.toString()
|
|
||||||
else -> null
|
|
||||||
},
|
|
||||||
)
|
|
||||||
// SY <--
|
|
||||||
context.toast(if (started) R.string.updating_category else R.string.update_already_running)
|
|
||||||
started
|
|
||||||
},
|
|
||||||
onClickOpenRandomManga = {
|
|
||||||
val items = getMangaForCategory.map { it.libraryManga.manga.id }
|
|
||||||
if (getMangaForCategory.isNotEmpty()) {
|
|
||||||
openManga(items.random())
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.information_no_entries_found)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClickInvertSelection = { presenter.invertSelection(presenter.activeCategory) },
|
|
||||||
onClickSelectAll = { presenter.selectAll(presenter.activeCategory) },
|
|
||||||
onClickUnselectAll = ::clearSelection,
|
|
||||||
// SY -->
|
|
||||||
onClickCleanTitles = ::cleanTitles,
|
|
||||||
onClickMigrate = {
|
|
||||||
val selectedMangaIds = presenter.selection
|
|
||||||
.filterNot { it.manga.source == MERGED_SOURCE_ID }
|
|
||||||
.map { it.manga.id }
|
|
||||||
presenter.clearSelection()
|
|
||||||
if (selectedMangaIds.isNotEmpty()) {
|
|
||||||
PreMigrationController.navigateToMigration(
|
|
||||||
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
|
||||||
router,
|
|
||||||
selectedMangaIds,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
activity?.toast(R.string.no_valid_manga)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClickAddToMangaDex = ::pushToMdList,
|
|
||||||
onClickSyncExh = {
|
|
||||||
// TODO
|
|
||||||
if (Injekt.get<UnsortedPreferences>().exhShowSyncIntro().get()) {
|
|
||||||
activity?.let { FavoritesIntroDialog().show(it) }
|
|
||||||
} else {
|
|
||||||
MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.favorites_sync)
|
|
||||||
.setMessage(R.string.favorites_sync_conformation_message)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
presenter.runSync()
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// SY <--
|
|
||||||
)
|
|
||||||
|
|
||||||
val onDismissRequest = { presenter.dialog = null }
|
|
||||||
when (val dialog = presenter.dialog) {
|
|
||||||
is LibraryPresenter.Dialog.ChangeCategory -> {
|
|
||||||
ChangeCategoryDialog(
|
|
||||||
initialSelection = dialog.initialSelection,
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onEditCategories = {
|
|
||||||
presenter.clearSelection()
|
|
||||||
router.pushController(CategoryController())
|
|
||||||
},
|
|
||||||
onConfirm = { include, exclude ->
|
|
||||||
presenter.clearSelection()
|
|
||||||
presenter.setMangaCategories(dialog.manga, include, exclude)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is LibraryPresenter.Dialog.DeleteManga -> {
|
|
||||||
DeleteLibraryMangaDialog(
|
|
||||||
containsLocalManga = dialog.manga.any(Manga::isLocal),
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onConfirm = { deleteManga, deleteChapter ->
|
|
||||||
presenter.removeMangas(dialog.manga, deleteManga, deleteChapter)
|
|
||||||
presenter.clearSelection()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is LibraryPresenter.Dialog.DownloadCustomAmount -> {
|
|
||||||
DownloadCustomAmountDialog(
|
|
||||||
maxAmount = dialog.max,
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
onConfirm = { amount ->
|
|
||||||
presenter.downloadUnreadChapters(dialog.manga, amount)
|
|
||||||
presenter.clearSelection()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
null -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(presenter.selectionMode) {
|
|
||||||
// Could perhaps be removed when navigation is in a Compose world
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
(activity as? MainActivity)?.showBottomNav(presenter.selectionMode.not())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(presenter.isLoading) {
|
|
||||||
if (!presenter.isLoading) {
|
|
||||||
(activity as? MainActivity)?.ready = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleBack(): Boolean {
|
|
||||||
return when {
|
|
||||||
presenter.selection.isNotEmpty() -> {
|
|
||||||
presenter.clearSelection()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
presenter.searchQuery != null -> {
|
|
||||||
presenter.searchQuery = null
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
settingsSheet = LibrarySettingsSheet(router) { group ->
|
settingsSheet = LibrarySettingsSheet(router)
|
||||||
when (group) {
|
viewScope.launch {
|
||||||
is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
|
LibraryScreen.openSettingsSheetEvent
|
||||||
else -> {} // Handled via different mechanisms
|
.collectLatest(::showSettingsSheet)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
|
||||||
super.onChangeStarted(handler, type)
|
|
||||||
if (type.isEnter) {
|
|
||||||
presenter.subscribeLibrary()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,265 +41,13 @@ class LibraryController(
|
|||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showSettingsSheet() {
|
fun showSettingsSheet(category: Category? = null) {
|
||||||
presenter.categories.getOrNull(presenter.activeCategory)?.let { category ->
|
if (category != null) {
|
||||||
settingsSheet?.show(category)
|
settingsSheet?.show(category)
|
||||||
|
} else {
|
||||||
|
viewScope.launch { LibraryScreen.requestOpenSettingsSheet() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFilterChanged() {
|
fun search(query: String) = LibraryScreen.search(query)
|
||||||
viewScope.launchUI {
|
|
||||||
presenter.requestFilterUpdate()
|
|
||||||
activity?.invalidateOptionsMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun search(query: String) {
|
|
||||||
presenter.searchQuery = query
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
||||||
val settingsSheet = settingsSheet ?: return
|
|
||||||
presenter.hasActiveFilters = settingsSheet.filters.hasActiveFilters()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openManga(mangaId: Long) {
|
|
||||||
presenter.onOpenManga()
|
|
||||||
router.pushController(MangaController(mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun continueReading(libraryManga: LibraryManga) {
|
|
||||||
viewScope.launchIO {
|
|
||||||
val chapter = presenter.getNextUnreadChapter(libraryManga.manga)
|
|
||||||
if (chapter != null) {
|
|
||||||
openChapter(chapter)
|
|
||||||
} else {
|
|
||||||
withUIContext { activity?.toast(R.string.no_next_chapter) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openChapter(chapter: Chapter) {
|
|
||||||
activity?.run {
|
|
||||||
startActivity(ReaderActivity.newIntent(this, chapter.mangaId, chapter.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all of the manga currently selected, and
|
|
||||||
* invalidate the action mode to revert the top toolbar
|
|
||||||
*/
|
|
||||||
private fun clearSelection() {
|
|
||||||
presenter.clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move the selected manga to a list of categories.
|
|
||||||
*/
|
|
||||||
private fun showMangaCategoriesDialog() {
|
|
||||||
viewScope.launchIO {
|
|
||||||
// Create a copy of selected manga
|
|
||||||
val mangaList = presenter.selection.map { it.manga }
|
|
||||||
|
|
||||||
// Hide the default category because it has a different behavior than the ones from db.
|
|
||||||
val categories = presenter.ogCategories.filter { it.id != 0L } // SY <--
|
|
||||||
|
|
||||||
// Get indexes of the common categories to preselect.
|
|
||||||
val common = presenter.getCommonCategories(mangaList)
|
|
||||||
// Get indexes of the mix categories to preselect.
|
|
||||||
val mix = presenter.getMixCategories(mangaList)
|
|
||||||
val preselected = categories.map {
|
|
||||||
when (it) {
|
|
||||||
in common -> CheckboxState.State.Checked(it)
|
|
||||||
in mix -> CheckboxState.TriState.Exclude(it)
|
|
||||||
else -> CheckboxState.State.None(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
presenter.dialog = LibraryPresenter.Dialog.ChangeCategory(mangaList, preselected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun runDownloadChapterAction(action: DownloadAction) {
|
|
||||||
val mangas = presenter.selection.map { it.manga }.toList()
|
|
||||||
when (action) {
|
|
||||||
DownloadAction.NEXT_1_CHAPTER -> presenter.downloadUnreadChapters(mangas, 1)
|
|
||||||
DownloadAction.NEXT_5_CHAPTERS -> presenter.downloadUnreadChapters(mangas, 5)
|
|
||||||
DownloadAction.NEXT_10_CHAPTERS -> presenter.downloadUnreadChapters(mangas, 10)
|
|
||||||
DownloadAction.UNREAD_CHAPTERS -> presenter.downloadUnreadChapters(mangas, null)
|
|
||||||
DownloadAction.CUSTOM -> {
|
|
||||||
presenter.dialog = LibraryPresenter.Dialog.DownloadCustomAmount(
|
|
||||||
mangas,
|
|
||||||
presenter.selection.maxOf { it.unreadCount }.toInt(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
presenter.clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun markReadStatus(read: Boolean) {
|
|
||||||
val mangaList = presenter.selection.toList()
|
|
||||||
presenter.markReadStatus(mangaList.map { it.manga }, read)
|
|
||||||
presenter.clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showDeleteMangaDialog() {
|
|
||||||
val mangaList = presenter.selection.map { it.manga }
|
|
||||||
presenter.dialog = LibraryPresenter.Dialog.DeleteManga(mangaList)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
private fun cleanTitles() {
|
|
||||||
val mangas = presenter.selection.filter {
|
|
||||||
it.manga.isEhBasedManga() ||
|
|
||||||
it.manga.source in nHentaiSourceIds
|
|
||||||
}
|
|
||||||
presenter.cleanTitles(mangas)
|
|
||||||
presenter.clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pushToMdList() {
|
|
||||||
val mangas = presenter.selection.filter {
|
|
||||||
it.manga.source in mangaDexSourceIds
|
|
||||||
}
|
|
||||||
presenter.syncMangaToDex(mangas)
|
|
||||||
presenter.clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(view: View) {
|
|
||||||
super.onAttach(view)
|
|
||||||
|
|
||||||
// --> EXH
|
|
||||||
cleanupSyncState()
|
|
||||||
favoritesSyncJob =
|
|
||||||
presenter.favoritesSync.status
|
|
||||||
.sample(100.milliseconds)
|
|
||||||
.mapLatest {
|
|
||||||
updateSyncStatus(it)
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
// <-- EXH
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach(view: View) {
|
|
||||||
super.onDetach(view)
|
|
||||||
|
|
||||||
// EXH
|
|
||||||
cleanupSyncState()
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
// --> EXH
|
|
||||||
private fun cleanupSyncState() {
|
|
||||||
favoritesSyncJob?.cancel()
|
|
||||||
favoritesSyncJob = null
|
|
||||||
// Close sync status
|
|
||||||
favSyncDialog?.dismiss()
|
|
||||||
favSyncDialog = null
|
|
||||||
oldSyncStatus = null
|
|
||||||
// Clear flags
|
|
||||||
releaseSyncLocks()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildDialog() = activity?.let {
|
|
||||||
MaterialAlertDialogBuilder(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSyncProgressDialog() {
|
|
||||||
favSyncDialog?.dismiss()
|
|
||||||
favSyncDialog = buildDialog()
|
|
||||||
?.setTitle(R.string.favorites_syncing)
|
|
||||||
?.setMessage("")
|
|
||||||
?.setCancelable(false)
|
|
||||||
?.create()
|
|
||||||
favSyncDialog?.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun takeSyncLocks() {
|
|
||||||
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun releaseSyncLocks() {
|
|
||||||
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateSyncStatus(status: FavoritesSyncStatus) {
|
|
||||||
when (status) {
|
|
||||||
is FavoritesSyncStatus.Idle -> {
|
|
||||||
releaseSyncLocks()
|
|
||||||
|
|
||||||
favSyncDialog?.dismiss()
|
|
||||||
favSyncDialog = null
|
|
||||||
}
|
|
||||||
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> {
|
|
||||||
releaseSyncLocks()
|
|
||||||
|
|
||||||
favSyncDialog?.dismiss()
|
|
||||||
favSyncDialog = buildDialog()
|
|
||||||
?.setTitle(R.string.favorites_sync_error)
|
|
||||||
?.setMessage(activity!!.getString(R.string.favorites_sync_bad_library_state, status.message))
|
|
||||||
?.setCancelable(false)
|
|
||||||
?.setPositiveButton(R.string.show_gallery) { _, _ ->
|
|
||||||
openManga(status.manga.id)
|
|
||||||
presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!)
|
|
||||||
}
|
|
||||||
?.setNegativeButton(android.R.string.ok) { _, _ ->
|
|
||||||
presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!)
|
|
||||||
}
|
|
||||||
?.create()
|
|
||||||
favSyncDialog?.show()
|
|
||||||
}
|
|
||||||
is FavoritesSyncStatus.Error -> {
|
|
||||||
releaseSyncLocks()
|
|
||||||
|
|
||||||
favSyncDialog?.dismiss()
|
|
||||||
favSyncDialog = buildDialog()
|
|
||||||
?.setTitle(R.string.favorites_sync_error)
|
|
||||||
?.setMessage(activity!!.getString(R.string.favorites_sync_error_string, status.message))
|
|
||||||
?.setCancelable(false)
|
|
||||||
?.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!)
|
|
||||||
}
|
|
||||||
?.create()
|
|
||||||
favSyncDialog?.show()
|
|
||||||
}
|
|
||||||
is FavoritesSyncStatus.CompleteWithErrors -> {
|
|
||||||
releaseSyncLocks()
|
|
||||||
|
|
||||||
favSyncDialog?.dismiss()
|
|
||||||
favSyncDialog = buildDialog()
|
|
||||||
?.setTitle(R.string.favorites_sync_done_errors)
|
|
||||||
?.setMessage(activity!!.getString(R.string.favorites_sync_done_errors_message, status.message))
|
|
||||||
?.setCancelable(false)
|
|
||||||
?.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!)
|
|
||||||
}
|
|
||||||
?.create()
|
|
||||||
favSyncDialog?.show()
|
|
||||||
}
|
|
||||||
is FavoritesSyncStatus.Processing,
|
|
||||||
is FavoritesSyncStatus.Initializing,
|
|
||||||
-> {
|
|
||||||
takeSyncLocks()
|
|
||||||
|
|
||||||
if (favSyncDialog == null || (
|
|
||||||
oldSyncStatus != null &&
|
|
||||||
oldSyncStatus !is FavoritesSyncStatus.Initializing &&
|
|
||||||
oldSyncStatus !is FavoritesSyncStatus.Processing
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
showSyncProgressDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
favSyncDialog?.setMessage(status.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oldSyncStatus = status
|
|
||||||
if (status is FavoritesSyncStatus.Processing && status.delayedMessage != null) {
|
|
||||||
delay(5.seconds)
|
|
||||||
favSyncDialog?.setMessage(status.delayedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// <-- EXH
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,341 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
|
import androidx.compose.material3.ScaffoldDefaults
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.util.fastAll
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import com.bluelinelabs.conductor.Router
|
||||||
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
|
import eu.kanade.domain.category.model.Category
|
||||||
|
import eu.kanade.domain.library.model.LibraryGroup
|
||||||
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
|
import eu.kanade.domain.library.model.display
|
||||||
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.domain.manga.model.isLocal
|
||||||
|
import eu.kanade.presentation.components.ChangeCategoryDialog
|
||||||
|
import eu.kanade.presentation.components.DeleteLibraryMangaDialog
|
||||||
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
|
import eu.kanade.presentation.components.EmptyScreenAction
|
||||||
|
import eu.kanade.presentation.components.LibraryBottomActionMenu
|
||||||
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.library.components.LibraryContent
|
||||||
|
import eu.kanade.presentation.library.components.LibraryToolbar
|
||||||
|
import eu.kanade.presentation.library.components.SyncFavoritesConfirmDialog
|
||||||
|
import eu.kanade.presentation.library.components.SyncFavoritesProgressDialog
|
||||||
|
import eu.kanade.presentation.library.components.SyncFavoritesWarningDialog
|
||||||
|
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
||||||
|
import eu.kanade.presentation.util.LocalRouter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||||
|
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||||
|
import exh.favorites.FavoritesSyncStatus
|
||||||
|
import exh.source.MERGED_SOURCE_ID
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object LibraryScreen : Screen {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val router = LocalRouter.currentOrThrow
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val screenModel = rememberScreenModel { LibraryScreenModel() }
|
||||||
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
val onClickRefresh: (Category?) -> Boolean = {
|
||||||
|
// SY -->
|
||||||
|
val started = LibraryUpdateService.start(
|
||||||
|
context = context,
|
||||||
|
category = if (state.groupType == LibraryGroup.BY_DEFAULT) it else null,
|
||||||
|
group = state.groupType,
|
||||||
|
groupExtra = when (state.groupType) {
|
||||||
|
LibraryGroup.BY_DEFAULT -> null
|
||||||
|
LibraryGroup.BY_SOURCE, LibraryGroup.BY_TRACK_STATUS -> it?.id?.toString()
|
||||||
|
LibraryGroup.BY_STATUS -> it?.id?.minus(1)?.toString()
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// SY <--
|
||||||
|
scope.launch {
|
||||||
|
val msgRes = if (started) R.string.updating_category else R.string.update_already_running
|
||||||
|
snackbarHostState.showSnackbar(context.getString(msgRes))
|
||||||
|
}
|
||||||
|
started
|
||||||
|
}
|
||||||
|
val onClickFilter: () -> Unit = {
|
||||||
|
scope.launch { sendSettingsSheetIntent(state.categories[screenModel.activeCategory]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = { scrollBehavior ->
|
||||||
|
val title = state.getToolbarTitle(
|
||||||
|
defaultTitle = stringResource(R.string.label_library),
|
||||||
|
defaultCategoryTitle = stringResource(R.string.label_default),
|
||||||
|
page = screenModel.activeCategory,
|
||||||
|
)
|
||||||
|
val tabVisible = state.showCategoryTabs && state.categories.size > 1
|
||||||
|
LibraryToolbar(
|
||||||
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
|
selectedCount = state.selection.size,
|
||||||
|
title = title,
|
||||||
|
incognitoMode = !tabVisible && screenModel.isIncognitoMode,
|
||||||
|
downloadedOnlyMode = !tabVisible && screenModel.isDownloadOnly,
|
||||||
|
onClickUnselectAll = screenModel::clearSelection,
|
||||||
|
onClickSelectAll = { screenModel.selectAll(screenModel.activeCategory) },
|
||||||
|
onClickInvertSelection = { screenModel.invertSelection(screenModel.activeCategory) },
|
||||||
|
onClickFilter = onClickFilter,
|
||||||
|
onClickRefresh = { onClickRefresh(null) },
|
||||||
|
onClickOpenRandomManga = {
|
||||||
|
scope.launch {
|
||||||
|
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
||||||
|
if (randomItem != null) {
|
||||||
|
router.openManga(randomItem.libraryManga.manga.id)
|
||||||
|
} else {
|
||||||
|
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// SY -->
|
||||||
|
onClickSyncExh = screenModel::openFavoritesSyncDialog.takeIf { state.showSyncExh },
|
||||||
|
// SY <--
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
onSearchQueryChange = screenModel::search,
|
||||||
|
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
|
||||||
|
)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
LibraryBottomActionMenu(
|
||||||
|
visible = state.selectionMode,
|
||||||
|
onChangeCategoryClicked = screenModel::openChangeCategoryDialog,
|
||||||
|
onMarkAsReadClicked = { screenModel.markReadSelection(true) },
|
||||||
|
onMarkAsUnreadClicked = { screenModel.markReadSelection(false) },
|
||||||
|
onDownloadClicked = screenModel::runDownloadActionSelection
|
||||||
|
.takeIf { state.selection.fastAll { !it.manga.isLocal() } },
|
||||||
|
onDeleteClicked = screenModel::openDeleteMangaDialog,
|
||||||
|
// SY -->
|
||||||
|
onClickCleanTitles = screenModel::cleanTitles.takeIf { state.showCleanTitles },
|
||||||
|
onClickMigrate = {
|
||||||
|
val selectedMangaIds = state.selection
|
||||||
|
.filterNot { it.manga.source == MERGED_SOURCE_ID }
|
||||||
|
.map { it.manga.id }
|
||||||
|
screenModel.clearSelection()
|
||||||
|
if (selectedMangaIds.isNotEmpty()) {
|
||||||
|
PreMigrationController.navigateToMigration(
|
||||||
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
|
router,
|
||||||
|
selectedMangaIds,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.no_valid_manga)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickAddToMangaDex = screenModel::syncMangaToDex.takeIf { state.showAddToMangadex },
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
|
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
||||||
|
) { contentPadding ->
|
||||||
|
if (state.isLoading) {
|
||||||
|
LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.searchQuery.isNullOrEmpty() && state.library.isEmpty()) {
|
||||||
|
val handler = LocalUriHandler.current
|
||||||
|
EmptyScreen(
|
||||||
|
textResource = R.string.information_empty_library,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
actions = listOf(
|
||||||
|
EmptyScreenAction(
|
||||||
|
stringResId = R.string.getting_started_guide,
|
||||||
|
icon = Icons.Outlined.HelpOutline,
|
||||||
|
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return@Scaffold
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryContent(
|
||||||
|
categories = state.categories,
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
selection = state.selection,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
currentPage = { screenModel.activeCategory },
|
||||||
|
isLibraryEmpty = state.library.isEmpty(),
|
||||||
|
showPageTabs = state.showCategoryTabs,
|
||||||
|
onChangeCurrentPage = { screenModel.activeCategory = it },
|
||||||
|
onMangaClicked = { router.openManga(it) },
|
||||||
|
onContinueReadingClicked = { it: LibraryManga ->
|
||||||
|
scope.launchIO {
|
||||||
|
val chapter = screenModel.getNextUnreadChapter(it.manga)
|
||||||
|
if (chapter != null) {
|
||||||
|
context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id))
|
||||||
|
} else {
|
||||||
|
snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}.takeIf { state.showMangaContinueButton },
|
||||||
|
onToggleSelection = { screenModel.toggleSelection(it) },
|
||||||
|
onToggleRangeSelection = {
|
||||||
|
screenModel.toggleRangeSelection(it)
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
|
onRefresh = onClickRefresh,
|
||||||
|
onGlobalSearchClicked = {
|
||||||
|
router.pushController(GlobalSearchController(screenModel.state.value.searchQuery))
|
||||||
|
},
|
||||||
|
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
|
||||||
|
getDisplayModeForPage = { state.categories[it].display },
|
||||||
|
getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) },
|
||||||
|
getLibraryForPage = { state.getLibraryItemsByPage(it) },
|
||||||
|
isDownloadOnly = screenModel.isDownloadOnly,
|
||||||
|
isIncognitoMode = screenModel.isIncognitoMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val onDismissRequest = screenModel::closeDialog
|
||||||
|
when (val dialog = state.dialog) {
|
||||||
|
is LibraryScreenModel.Dialog.ChangeCategory -> {
|
||||||
|
ChangeCategoryDialog(
|
||||||
|
initialSelection = dialog.initialSelection,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onEditCategories = {
|
||||||
|
screenModel.clearSelection()
|
||||||
|
router.pushController(CategoryController())
|
||||||
|
},
|
||||||
|
onConfirm = { include, exclude ->
|
||||||
|
screenModel.clearSelection()
|
||||||
|
screenModel.setMangaCategories(dialog.manga, include, exclude)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is LibraryScreenModel.Dialog.DeleteManga -> {
|
||||||
|
DeleteLibraryMangaDialog(
|
||||||
|
containsLocalManga = dialog.manga.any(Manga::isLocal),
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onConfirm = { deleteManga, deleteChapter ->
|
||||||
|
screenModel.removeMangas(dialog.manga, deleteManga, deleteChapter)
|
||||||
|
screenModel.clearSelection()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is LibraryScreenModel.Dialog.DownloadCustomAmount -> {
|
||||||
|
DownloadCustomAmountDialog(
|
||||||
|
maxAmount = dialog.max,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onConfirm = { amount ->
|
||||||
|
screenModel.downloadUnreadChapters(dialog.manga, amount)
|
||||||
|
screenModel.clearSelection()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LibraryScreenModel.Dialog.SyncFavoritesWarning -> {
|
||||||
|
SyncFavoritesWarningDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onAccept = {
|
||||||
|
onDismissRequest()
|
||||||
|
screenModel.onAcceptSyncWarning()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LibraryScreenModel.Dialog.SyncFavoritesConfirm -> {
|
||||||
|
SyncFavoritesConfirmDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onAccept = {
|
||||||
|
onDismissRequest()
|
||||||
|
screenModel.runSync()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
null -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
SyncFavoritesProgressDialog(
|
||||||
|
status = screenModel.favoritesSync.status.collectAsState().value,
|
||||||
|
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
|
||||||
|
openManga = { router.openManga(it.id) },
|
||||||
|
)
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
BackHandler(enabled = state.selectionMode || state.searchQuery != null) {
|
||||||
|
when {
|
||||||
|
state.selectionMode -> screenModel.clearSelection()
|
||||||
|
state.searchQuery != null -> screenModel.search(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state.selectionMode) {
|
||||||
|
// Could perhaps be removed when navigation is in a Compose world
|
||||||
|
if (router.backstackSize == 1) {
|
||||||
|
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(state.isLoading) {
|
||||||
|
if (!state.isLoading) {
|
||||||
|
(context as? MainActivity)?.ready = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
launch { queryEvent.collectLatest(screenModel::search) }
|
||||||
|
launch { requestSettingsSheetEvent.collectLatest { onClickFilter() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Router.openManga(mangaId: Long) {
|
||||||
|
pushController(MangaController(mangaId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// For invoking search from other screen
|
||||||
|
private val queryEvent = MutableSharedFlow<String>(replay = 1)
|
||||||
|
fun search(query: String) = queryEvent.tryEmit(query)
|
||||||
|
|
||||||
|
// For opening settings sheet in LibraryController
|
||||||
|
private val requestSettingsSheetEvent = MutableSharedFlow<Unit>()
|
||||||
|
private val openSettingsSheetEvent_ = MutableSharedFlow<Category>()
|
||||||
|
val openSettingsSheetEvent = openSettingsSheetEvent_.asSharedFlow()
|
||||||
|
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.emit(category)
|
||||||
|
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.emit(Unit)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -35,7 +35,6 @@ class LibrarySettingsSheet(
|
|||||||
private val trackManager: TrackManager = Injekt.get(),
|
private val trackManager: TrackManager = Injekt.get(),
|
||||||
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
||||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||||
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit,
|
|
||||||
) : TabbedBottomSheetDialog(router.activity!!) {
|
) : TabbedBottomSheetDialog(router.activity!!) {
|
||||||
|
|
||||||
val filters: Filter
|
val filters: Filter
|
||||||
@ -50,17 +49,11 @@ class LibrarySettingsSheet(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
filters = Filter(router.activity!!)
|
filters = Filter(router.activity!!)
|
||||||
filters.onGroupClicked = onGroupClickListener
|
|
||||||
|
|
||||||
sort = Sort(router.activity!!)
|
sort = Sort(router.activity!!)
|
||||||
sort.onGroupClicked = onGroupClickListener
|
|
||||||
|
|
||||||
display = Display(router.activity!!)
|
display = Display(router.activity!!)
|
||||||
display.onGroupClicked = onGroupClickListener
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
grouping = Grouping(router.activity!!)
|
grouping = Grouping(router.activity!!)
|
||||||
grouping.onGroupClicked = onGroupClickListener
|
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user