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:
Ivan Iskandar 2022-11-27 03:48:57 +07:00 committed by Jobobby04
parent cae8e18a9d
commit 9384d45282
19 changed files with 1228 additions and 1224 deletions

View File

@ -66,6 +66,7 @@ import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
import eu.kanade.domain.source.repository.FeedSavedSearchRepository
import eu.kanade.domain.source.repository.SavedSearchRepository
import eu.kanade.tachiyomi.source.online.MetadataSource
import exh.search.SearchEngine
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
@ -99,6 +100,7 @@ class SYDomainModule : InjektModule {
addFactory { DeleteSortTag(get(), get()) }
addFactory { ReorderSortTag(get(), get()) }
addFactory { GetPagePreviews(get(), get()) }
addFactory { SearchEngine() }
// Required for [MetadataSource]
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }

View File

@ -49,6 +49,9 @@ object CommonMangaItemDefaults {
}
private val ContinueReadingButtonSize = 32.dp
private val ContinueReadingButtonGridPadding = 6.dp
private val ContinueReadingButtonListSpacing = 8.dp
private const val GridSelectedCoverAlpha = 0.76f
/**
@ -61,9 +64,8 @@ fun MangaCompactGridItem(
title: String? = null,
coverData: eu.kanade.domain.manga.model.MangaCover,
coverAlpha: Float = 1f,
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
showContinueReadingButton: Boolean = false,
coverBadgeStart: @Composable (RowScope.() -> Unit)? = null,
coverBadgeEnd: @Composable (RowScope.() -> Unit)? = null,
onLongClick: () -> Unit,
onClick: () -> Unit,
onClickContinueReading: (() -> Unit)? = null,
@ -86,12 +88,17 @@ fun MangaCompactGridItem(
badgesEnd = coverBadgeEnd,
content = {
if (title != null) {
CoverTextOverlay(title = title, showContinueReadingButton)
}
},
continueReadingButton = {
if (showContinueReadingButton && onClickContinueReading != null) {
ContinueReadingButton(onClickContinueReading)
CoverTextOverlay(
title = title,
onClickContinueReading = onClickContinueReading,
)
} else if (onClickContinueReading != null) {
ContinueReadingButton(
modifier = Modifier
.padding(ContinueReadingButtonGridPadding)
.align(Alignment.BottomEnd),
onClickContinueReading = onClickContinueReading,
)
}
},
)
@ -104,7 +111,7 @@ fun MangaCompactGridItem(
@Composable
private fun BoxScope.CoverTextOverlay(
title: String,
showContinueReadingButton: Boolean = false,
onClickContinueReading: (() -> Unit)? = null,
) {
Box(
modifier = Modifier
@ -119,20 +126,33 @@ private fun BoxScope.CoverTextOverlay(
.fillMaxWidth()
.align(Alignment.BottomCenter),
)
val endPadding = if (showContinueReadingButton) ContinueReadingButtonSize else 0.dp
GridItemTitle(
modifier = Modifier
.padding(start = 8.dp, top = 8.dp, end = endPadding + 8.dp, bottom = 8.dp)
.align(Alignment.BottomStart),
title = title,
style = MaterialTheme.typography.titleSmall.copy(
color = Color.White,
shadow = Shadow(
color = Color.Black,
blurRadius = 4f,
Row(
modifier = Modifier.align(Alignment.BottomStart),
verticalAlignment = Alignment.Bottom,
) {
GridItemTitle(
modifier = Modifier
.weight(1f)
.padding(8.dp),
title = title,
style = MaterialTheme.typography.titleSmall.copy(
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,
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
showContinueReadingButton: Boolean = false,
onLongClick: () -> Unit,
onClick: () -> Unit,
onClickContinueReading: (() -> Unit)? = null,
@ -168,9 +187,14 @@ fun MangaComfortableGridItem(
},
badgesStart = coverBadgeStart,
badgesEnd = coverBadgeEnd,
continueReadingButton = {
if (showContinueReadingButton && onClickContinueReading != null) {
ContinueReadingButton(onClickContinueReading)
content = {
if (onClickContinueReading != null) {
ContinueReadingButton(
modifier = Modifier
.padding(ContinueReadingButtonGridPadding)
.align(Alignment.BottomEnd),
onClickContinueReading = onClickContinueReading,
)
}
},
)
@ -192,7 +216,6 @@ private fun MangaGridCover(
cover: @Composable BoxScope.() -> Unit = {},
badgesStart: (@Composable RowScope.() -> Unit)? = null,
badgesEnd: (@Composable RowScope.() -> Unit)? = null,
continueReadingButton: (@Composable BoxScope.() -> Unit)? = null,
content: @Composable (BoxScope.() -> Unit)? = null,
) {
Box(
@ -219,7 +242,6 @@ private fun MangaGridCover(
content = badgesEnd,
)
}
continueReadingButton?.invoke(this)
}
}
@ -310,8 +332,7 @@ fun MangaListItem(
title: String,
coverData: eu.kanade.domain.manga.model.MangaCover,
coverAlpha: Float = 1f,
badge: @Composable RowScope.() -> Unit,
showContinueReadingButton: Boolean = false,
badge: @Composable (RowScope.() -> Unit),
onLongClick: () -> Unit,
onClick: () -> Unit,
onClickContinueReading: (() -> Unit)? = null,
@ -343,23 +364,21 @@ fun MangaListItem(
style = MaterialTheme.typography.bodyMedium,
)
BadgeGroup(content = badge)
if (showContinueReadingButton && onClickContinueReading != null) {
Box {
ContinueReadingButton(onClickContinueReading)
}
if (onClickContinueReading != null) {
ContinueReadingButton(
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
onClickContinueReading = onClickContinueReading,
)
}
}
}
@Composable
private fun BoxScope.ContinueReadingButton(
private fun ContinueReadingButton(
modifier: Modifier = Modifier,
onClickContinueReading: () -> Unit,
) {
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(horizontal = 4.dp, vertical = 8.dp),
) {
Box(modifier = modifier) {
FilledIconButton(
onClick = onClickContinueReading,
modifier = Modifier.size(ContinueReadingButtonSize),

View File

@ -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 <--
)
}
}

View File

@ -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 <--
}

View File

@ -5,16 +5,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.Badge
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
fun DownloadsBadge(
enabled: Boolean,
item: LibraryItem,
) {
if (enabled && item.downloadCount > 0) {
fun DownloadsBadge(count: Int) {
if (count > 0) {
Badge(
text = "${item.downloadCount}",
text = "$count",
color = MaterialTheme.colorScheme.tertiary,
textColor = MaterialTheme.colorScheme.onTertiary,
)
@ -22,30 +18,26 @@ fun DownloadsBadge(
}
@Composable
fun UnreadBadge(
enabled: Boolean,
item: LibraryItem,
) {
if (enabled && item.unreadCount > 0) {
Badge(text = "${item.unreadCount}")
fun UnreadBadge(count: Int) {
if (count > 0) {
Badge(text = "$count")
}
}
@Composable
fun LanguageBadge(
showLanguage: Boolean,
showLocal: Boolean,
item: LibraryItem,
isLocal: Boolean,
sourceLanguage: String,
) {
if (showLocal && item.isLocal) {
if (isLocal) {
Badge(
text = stringResource(R.string.local_source_badge),
color = MaterialTheme.colorScheme.tertiary,
textColor = MaterialTheme.colorScheme.onTertiary,
)
} else if (showLanguage && item.sourceLanguage.isNotEmpty()) {
} else if (sourceLanguage.isNotEmpty()) {
Badge(
text = item.sourceLanguage.uppercase(),
text = sourceLanguage.uppercase(),
color = MaterialTheme.colorScheme.tertiary,
textColor = MaterialTheme.colorScheme.onTertiary,
)

View File

@ -14,17 +14,12 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
fun LibraryComfortableGrid(
items: List<LibraryItem>,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
columns: Int,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
) {
@ -51,26 +46,22 @@ fun LibraryComfortableGrid(
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(
enabled = showDownloadBadges,
item = libraryItem,
)
UnreadBadge(
enabled = showUnreadBadges,
item = libraryItem,
)
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
},
coverBadgeEnd = {
LanguageBadge(
showLanguage = showLanguageBadges,
showLocal = showLocalBadges,
item = libraryItem,
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
showContinueReadingButton = showContinueReadingButton,
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
}
}

View File

@ -15,17 +15,12 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
fun LibraryCompactGrid(
items: List<LibraryItem>,
showTitle: Boolean,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
columns: Int,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
) {
@ -52,26 +47,22 @@ fun LibraryCompactGrid(
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(
enabled = showDownloadBadges,
item = libraryItem,
)
UnreadBadge(
enabled = showUnreadBadges,
item = libraryItem,
)
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
},
coverBadgeEnd = {
LanguageBadge(
showLanguage = showLanguageBadges,
showLocal = showLocalBadges,
item = libraryItem,
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
showContinueReadingButton = showContinueReadingButton,
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
}
}

View File

@ -1,6 +1,5 @@
package eu.kanade.presentation.library.components
import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import eu.kanade.core.prefs.PreferenceMutableState
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.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.rememberPagerState
import eu.kanade.presentation.library.LibraryState
import eu.kanade.tachiyomi.ui.library.LibraryItem
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -31,33 +27,26 @@ import kotlin.time.Duration.Companion.seconds
@Composable
fun LibraryContent(
state: LibraryState,
categories: List<Category>,
searchQuery: String?,
selection: List<LibraryManga>,
contentPadding: PaddingValues,
currentPage: () -> Int,
isLibraryEmpty: Boolean,
showPageTabs: Boolean,
showMangaCount: Boolean,
onChangeCurrentPage: (Int) -> Unit,
onMangaClicked: (Long) -> Unit,
onContinueReadingClicked: (LibraryManga) -> Unit,
onContinueReadingClicked: ((LibraryManga) -> Unit)?,
onToggleSelection: (LibraryManga) -> Unit,
onToggleRangeSelection: (LibraryManga) -> Unit,
onRefresh: (Category?) -> Boolean,
onGlobalSearchClicked: () -> Unit,
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
getNumberOfMangaForCategory: (Category) -> Int?,
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: @Composable (Int) -> List<LibraryItem>,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
getLibraryForPage: (Int) -> List<LibraryItem>,
isDownloadOnly: Boolean,
isIncognitoMode: Boolean,
// SY -->
getCategoryName: (Context, Category, Int, String) -> String,
// SY <--
) {
Column(
modifier = Modifier.padding(
@ -66,46 +55,30 @@ fun LibraryContent(
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
),
) {
val categories = state.categories
val coercedCurrentPage = remember { currentPage().coerceAtMost(categories.lastIndex) }
val pagerState = rememberPagerState(coercedCurrentPage)
val scope = rememberCoroutineScope()
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) {
if (!isLibraryEmpty && showPageTabs && categories.size > 1) {
LibraryTabs(
categories = categories,
currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex),
showMangaCount = showMangaCount,
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
currentPageIndex = pagerState.currentPage,
isDownloadOnly = isDownloadOnly,
isIncognitoMode = isIncognitoMode,
onTabItemClick = { scope.launch { pagerState.animateScrollToPage(it) } },
// SY -->
getCategoryName = { category, name ->
val context = LocalContext.current
remember(context, category, state.groupType, name) {
getCategoryName(context, category, state.groupType, name)
}
},
// SY <--
)
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
) { scope.launch { pagerState.animateScrollToPage(it) } }
}
val notSelectionMode = selection.isEmpty()
val onClickManga = { manga: LibraryManga ->
if (state.selectionMode.not()) {
if (notSelectionMode) {
onMangaClicked(manga.manga.id)
} else {
onToggleSelection(manga)
}
}
val onLongClickManga = { manga: LibraryManga ->
onToggleRangeSelection(manga)
}
val onClickContinueReading = { manga: LibraryManga ->
onContinueReadingClicked(manga)
}
SwipeRefresh(
refreshing = isRefreshing,
@ -119,26 +92,21 @@ fun LibraryContent(
isRefreshing = false
}
},
enabled = state.selectionMode.not(),
enabled = notSelectionMode,
) {
LibraryPager(
state = pagerState,
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
pageCount = categories.size,
selectedManga = state.selection,
selectedManga = selection,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
getDisplayModeForPage = getDisplayModeForPage,
getColumnsForOrientation = getColumnsForOrientation,
getLibraryForPage = getLibraryForPage,
showDownloadBadges = showDownloadBadges,
showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
onClickManga = onClickManga,
onLongClickManga = onLongClickManga,
onClickContinueReading = onClickContinueReading,
onGlobalSearchClicked = onGlobalSearchClicked,
searchQuery = state.searchQuery,
onLongClickManga = onToggleRangeSelection,
onClickContinueReading = onContinueReadingClicked,
)
}

View File

@ -23,16 +23,11 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable
fun LibraryList(
items: List<LibraryItem>,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
) {
@ -41,13 +36,13 @@ fun LibraryList(
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
) {
item {
if (searchQuery.isNullOrEmpty().not()) {
if (!searchQuery.isNullOrEmpty()) {
TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = onGlobalSearchClicked,
) {
Text(
text = stringResource(R.string.action_global_search_query, searchQuery!!),
text = stringResource(R.string.action_global_search_query, searchQuery),
modifier = Modifier.zIndex(99f),
)
}
@ -70,14 +65,20 @@ fun LibraryList(
lastModified = manga.coverLastModified,
),
badge = {
DownloadsBadge(enabled = showDownloadBadges, item = libraryItem)
UnreadBadge(enabled = showUnreadBadges, item = libraryItem)
LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem)
DownloadsBadge(count = libraryItem.downloadCount.toInt())
UnreadBadge(count = libraryItem.unreadCount.toInt())
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
)
},
showContinueReadingButton = showContinueReadingButton,
onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
onClickContinueReading = if (onClickContinueReading != null) {
{ onClickContinueReading(libraryItem.libraryManga) }
} else {
null
},
)
}
}

View File

@ -27,15 +27,10 @@ fun LibraryPager(
onGlobalSearchClicked: () -> Unit,
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: @Composable (Int) -> List<LibraryItem>,
showDownloadBadges: Boolean,
showUnreadBadges: Boolean,
showLocalBadges: Boolean,
showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
getLibraryForPage: (Int) -> List<LibraryItem>,
onClickManga: (LibraryManga) -> Unit,
onLongClickManga: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
) {
HorizontalPager(
count = pageCount,
@ -62,11 +57,6 @@ fun LibraryPager(
LibraryDisplayMode.List -> {
LibraryList(
items = library,
showDownloadBadges = showDownloadBadges,
showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
contentPadding = contentPadding,
selection = selectedManga,
onClick = onClickManga,
@ -80,11 +70,6 @@ fun LibraryPager(
LibraryCompactGrid(
items = library,
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
showDownloadBadges = showDownloadBadges,
showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
columns = columns,
contentPadding = contentPadding,
selection = selectedManga,
@ -98,17 +83,12 @@ fun LibraryPager(
LibraryDisplayMode.ComfortableGrid -> {
LibraryComfortableGrid(
items = library,
showDownloadBadges = showDownloadBadges,
showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
columns = columns,
contentPadding = contentPadding,
selection = selectedManga,
onClick = onClickManga,
onClickContinueReading = onClickContinueReading,
onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
)

View File

@ -5,7 +5,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.unit.dp
import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
@ -18,14 +17,10 @@ import eu.kanade.presentation.components.TabText
fun LibraryTabs(
categories: List<Category>,
currentPageIndex: Int,
showMangaCount: Boolean,
isDownloadOnly: Boolean,
isIncognitoMode: Boolean,
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
getNumberOfMangaForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit,
// SY -->
getCategoryName: @Composable (Category, String) -> String,
// SY <--
) {
Column {
ScrollableTabRow(
@ -42,14 +37,8 @@ fun LibraryTabs(
onClick = { onTabItemClick(index) },
text = {
TabText(
// SY -->
text = getCategoryName(category, category.visualName),
// SY <--,
badgeCount = if (showMangaCount) {
getNumberOfMangaForCategory(category.id)
} else {
null
}?.value,
text = category.visualName,
badgeCount = getNumberOfMangaForCategory(category),
)
},
unselectedContentColor = MaterialTheme.colorScheme.onSurface,

View File

@ -14,6 +14,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.Pill
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.library.LibraryState
import eu.kanade.presentation.theme.active
import eu.kanade.tachiyomi.R
@Composable
fun LibraryToolbar(
state: LibraryState,
hasActiveFilters: Boolean,
selectedCount: Int,
title: LibraryToolbarTitle,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
@ -40,12 +41,14 @@ fun LibraryToolbar(
onClickRefresh: () -> Unit,
onClickOpenRandomManga: () -> Unit,
// SY -->
onClickSyncExh: () -> Unit,
onClickSyncExh: (() -> Unit)?,
// SY <--
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
scrollBehavior: TopAppBarScrollBehavior?,
) = when {
state.selectionMode -> LibrarySelectionToolbar(
state = state,
selectedCount > 0 -> LibrarySelectionToolbar(
selectedCount = selectedCount,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
onClickUnselectAll = onClickUnselectAll,
@ -54,16 +57,16 @@ fun LibraryToolbar(
)
else -> LibraryRegularToolbar(
title = title,
hasFilters = state.hasActiveFilters,
hasFilters = hasActiveFilters,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
searchQuery = state.searchQuery,
onChangeSearchQuery = { state.searchQuery = it },
searchQuery = searchQuery,
onSearchQueryChange = onSearchQueryChange,
onClickFilter = onClickFilter,
onClickRefresh = onClickRefresh,
onClickOpenRandomManga = onClickOpenRandomManga,
// SY -->
onClickSyncExh = onClickSyncExh.takeIf { state.showSyncExh },
onClickSyncExh = onClickSyncExh,
// SY <--
scrollBehavior = scrollBehavior,
)
@ -76,7 +79,7 @@ fun LibraryRegularToolbar(
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
searchQuery: String?,
onChangeSearchQuery: (String?) -> Unit,
onSearchQueryChange: (String?) -> Unit,
onClickFilter: () -> Unit,
onClickRefresh: () -> Unit,
onClickOpenRandomManga: () -> Unit,
@ -105,7 +108,7 @@ fun LibraryRegularToolbar(
}
},
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
onChangeSearchQuery = onSearchQueryChange,
actions = {
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
IconButton(onClick = onClickFilter) {
@ -148,7 +151,7 @@ fun LibraryRegularToolbar(
@Composable
fun LibrarySelectionToolbar(
state: LibraryState,
selectedCount: Int,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onClickUnselectAll: () -> Unit,
@ -156,7 +159,7 @@ fun LibrarySelectionToolbar(
onClickInvertSelection: () -> Unit,
) {
AppBar(
titleContent = { Text(text = "${state.selection.size}") },
titleContent = { Text(text = "$selectedCount") },
actions = {
IconButton(onClick = onClickSelectAll) {
Icon(Icons.Outlined.SelectAll, contentDescription = stringResource(R.string.action_select_all))
@ -172,6 +175,7 @@ fun LibrarySelectionToolbar(
)
}
@Immutable
data class LibraryToolbarTitle(
val text: String,
val numberOfManga: Int? = null,

View File

@ -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),
)
}

View File

@ -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,
),
)
}
}

View File

@ -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),
)
}

View File

@ -1,246 +1,37 @@
package eu.kanade.tachiyomi.ui.library
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import com.bluelinelabs.conductor.ControllerChangeHandler
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 cafe.adriel.voyager.navigator.Navigator
import eu.kanade.domain.category.model.Category
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
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.delay
import kotlinx.coroutines.flow.launchIn
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
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class LibraryController(
bundle: Bundle? = null,
) : FullComposeController<LibraryPresenter>(bundle), RootController {
) : BasicFullComposeController(bundle), RootController {
/**
* Sheet containing filter/sort/display items.
*/
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
override fun ComposeContent() {
val context = LocalContext.current
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
}
Navigator(screen = LibraryScreen)
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
settingsSheet = LibrarySettingsSheet(router) { group ->
when (group) {
is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
else -> {} // Handled via different mechanisms
}
}
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type.isEnter) {
presenter.subscribeLibrary()
settingsSheet = LibrarySettingsSheet(router)
viewScope.launch {
LibraryScreen.openSettingsSheetEvent
.collectLatest(::showSettingsSheet)
}
}
@ -250,265 +41,13 @@ class LibraryController(
super.onDestroyView(view)
}
fun showSettingsSheet() {
presenter.categories.getOrNull(presenter.activeCategory)?.let { category ->
fun showSettingsSheet(category: Category? = null) {
if (category != null) {
settingsSheet?.show(category)
} else {
viewScope.launch { LibraryScreen.requestOpenSettingsSheet() }
}
}
private fun onFilterChanged() {
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
fun search(query: String) = LibraryScreen.search(query)
}

View File

@ -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)
}

View File

@ -35,7 +35,6 @@ class LibrarySettingsSheet(
private val trackManager: TrackManager = Injekt.get(),
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit,
) : TabbedBottomSheetDialog(router.activity!!) {
val filters: Filter
@ -50,17 +49,11 @@ class LibrarySettingsSheet(
init {
filters = Filter(router.activity!!)
filters.onGroupClicked = onGroupClickListener
sort = Sort(router.activity!!)
sort.onGroupClicked = onGroupClickListener
display = Display(router.activity!!)
display.onGroupClicked = onGroupClickListener
// SY -->
grouping = Grouping(router.activity!!)
grouping.onGroupClicked = onGroupClickListener
// SY <--
}