FIX IT ALL AAAAAAAAAAAAAAA

This commit is contained in:
Quickdesh 2023-02-17 04:48:34 -05:00
parent d76e5b2a81
commit 5e8c406f7a
163 changed files with 2654 additions and 3423 deletions

View file

@ -15,7 +15,7 @@ class GetLanguagesWithAnimeSources(
fun subscribe(): Flow<Map<String, List<AnimeSource>>> { fun subscribe(): Flow<Map<String, List<AnimeSource>>> {
return combine( return combine(
preferences.enabledLanguages().changes(), preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(), preferences.disabledAnimeSources().changes(),
repository.getOnlineSources(), repository.getOnlineSources(),
) { enabledLanguage, disabledSource, onlineSources -> ) { enabledLanguage, disabledSource, onlineSources ->
val sortedSources = onlineSources.sortedWith( val sortedSources = onlineSources.sortedWith(

View file

@ -1,8 +1,8 @@
package eu.kanade.domain.episode.model package eu.kanade.domain.episode.model
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.anime.model.isLocal import eu.kanade.domain.anime.model.isLocal
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
import eu.kanade.tachiyomi.ui.anime.EpisodeItem import eu.kanade.tachiyomi.ui.anime.EpisodeItem

View file

@ -50,6 +50,18 @@ class LibraryPreferences(
fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterDownloadedAnime() = preferenceStore.getInt("pref_filter_animelib_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterUnseen() = preferenceStore.getInt("pref_filter_animelib_unread", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterStartedAnime() = preferenceStore.getInt("pref_filter_animelib_started", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterBookmarkedAnime() = preferenceStore.getInt("pref_filter_animelib_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterCompletedAnime() = preferenceStore.getInt("pref_filter_animelib_completed", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterTrackingAnime(name: Int) = preferenceStore.getInt("pref_filter_animelib_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
// endregion // endregion
// region Badges // region Badges

View file

@ -42,5 +42,5 @@ class SourcePreferences(
fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false) fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
fun searchAnimePinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false) fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false)
} }

View file

@ -9,7 +9,6 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
import eu.kanade.domain.animetrack.model.toDbTrack import eu.kanade.domain.animetrack.model.toDbTrack

View file

@ -71,7 +71,7 @@ import eu.kanade.tachiyomi.animesource.getNameForAnimeInfo
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
import eu.kanade.tachiyomi.ui.anime.AnimeScreenState import eu.kanade.tachiyomi.ui.anime.AnimeScreenState
import eu.kanade.tachiyomi.ui.anime.EpisodeItem import eu.kanade.tachiyomi.ui.anime.EpisodeItem
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -252,6 +252,7 @@ private fun AnimeScreenSmallImpl(
onClickDownload = onDownloadActionClicked, onClickDownload = onDownloadActionClicked,
onClickEditCategory = onEditCategoryClicked, onClickEditCategory = onEditCategoryClicked,
onClickMigrate = onMigrateClicked, onClickMigrate = onMigrateClicked,
changeAnimeSkipIntro = changeAnimeSkipIntro,
actionModeCounter = episodes.count { it.selected }, actionModeCounter = episodes.count { it.selected },
onSelectAll = { onAllEpisodeSelected(true) }, onSelectAll = { onAllEpisodeSelected(true) },
onInvertSelection = { onInvertSelection() }, onInvertSelection = { onInvertSelection() },

View file

@ -2,7 +2,6 @@ package eu.kanade.presentation.anime
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -10,27 +9,19 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -39,11 +30,9 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.TrackLogoIcon import eu.kanade.presentation.components.TrackLogoIcon
import eu.kanade.presentation.components.VerticalDivider import eu.kanade.presentation.components.VerticalDivider
import eu.kanade.presentation.manga.TrackDetailsItem import eu.kanade.presentation.manga.TrackDetailsItem

View file

@ -5,10 +5,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.WindowInsets import androidx.compose.foundation.layout.WindowInsets

View file

@ -13,13 +13,13 @@ import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.animeextension.AnimeExtensionFilterState import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable
fun AnimeExtensionFilterScreen( fun AnimeExtensionFilterScreen(
navigateUp: () -> Unit, navigateUp: () -> Unit,
state: AnimeExtensionFilterState.Success, state: ExtensionFilterState.Success,
onClickToggle: (String) -> Unit, onClickToggle: (String) -> Unit,
) { ) {
Scaffold( Scaffold(
@ -49,7 +49,7 @@ fun AnimeExtensionFilterScreen(
@Composable @Composable
private fun AnimeExtensionFilterContent( private fun AnimeExtensionFilterContent(
contentPadding: PaddingValues, contentPadding: PaddingValues,
state: AnimeExtensionFilterState.Success, state: ExtensionFilterState.Success,
onClickLang: (String) -> Unit, onClickLang: (String) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current

View file

@ -83,7 +83,7 @@ private fun AnimeSourcesFilterContent(
AnimeSourcesFilterItem( AnimeSourcesFilterItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
source = source, source = source,
enabled = "${source.id}" !in state.disabledSources, isEnabled = "${source.id}" !in state.disabledSources,
onClickItem = onClickSource, onClickItem = onClickSource,
) )
} }

View file

@ -36,8 +36,6 @@ import eu.kanade.tachiyomi.animesource.LocalAnimeSource
import eu.kanade.tachiyomi.ui.browse.animesource.AnimeSourcesState import eu.kanade.tachiyomi.ui.browse.animesource.AnimeSourcesState
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable @Composable
fun AnimeSourcesScreen( fun AnimeSourcesScreen(

View file

@ -19,8 +19,8 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchState
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.AnimeSearchItemResult import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.AnimeSearchItemResult
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchState
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable

View file

@ -10,7 +10,6 @@ import eu.kanade.presentation.anime.components.BaseAnimeListItem
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.anime.MigrateAnimeState import eu.kanade.tachiyomi.ui.browse.migration.anime.MigrateAnimeState

View file

@ -13,8 +13,8 @@ import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchState
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.AnimeSearchItemResult import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.AnimeSearchItemResult
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchState
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable

View file

@ -39,12 +39,12 @@ import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.secondaryItemAlpha import eu.kanade.presentation.util.secondaryItemAlpha
import eu.kanade.presentation.util.topSmallPaddingValues import eu.kanade.presentation.util.topSmallPaddingValues
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.animesources.MigrateAnimeSourcesState import eu.kanade.tachiyomi.ui.browse.migration.animesources.MigrateAnimeSourceState
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
@Composable @Composable
fun MigrateAnimeSourceScreen( fun MigrateAnimeSourceScreen(
state: MigrateAnimeSourcesState, state: MigrateAnimeSourceState,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onClickItem: (AnimeSource) -> Unit, onClickItem: (AnimeSource) -> Unit,
onToggleSortingDirection: () -> Unit, onToggleSortingDirection: () -> Unit,

View file

@ -13,8 +13,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.AnimeCover import eu.kanade.domain.anime.model.AnimeCover
import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem
import eu.kanade.presentation.browse.InLibraryBadge import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem
import eu.kanade.presentation.components.CommonMangaItemDefaults import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaComfortableGridItem import eu.kanade.presentation.components.MangaComfortableGridItem
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus

View file

@ -13,8 +13,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.AnimeCover import eu.kanade.domain.anime.model.AnimeCover
import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem
import eu.kanade.presentation.browse.InLibraryBadge import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem
import eu.kanade.presentation.components.CommonMangaItemDefaults import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaCompactGridItem import eu.kanade.presentation.components.MangaCompactGridItem
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus

View file

@ -10,8 +10,8 @@ import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items import androidx.paging.compose.items
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.AnimeCover import eu.kanade.domain.anime.model.AnimeCover
import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem
import eu.kanade.presentation.browse.InLibraryBadge import eu.kanade.presentation.browse.InLibraryBadge
import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem
import eu.kanade.presentation.components.CommonMangaItemDefaults import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.components.MangaListItem import eu.kanade.presentation.components.MangaListItem

View file

@ -2,55 +2,32 @@ package eu.kanade.presentation.animehistory
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteSweep
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.animehistory.model.AnimeHistoryWithRelations import eu.kanade.domain.animehistory.model.AnimeHistoryWithRelations
import eu.kanade.presentation.animehistory.components.AnimeHistoryContent import eu.kanade.presentation.animehistory.components.AnimeHistoryContent
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryScreenModel import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryScreenModel
import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryState import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryState
import java.util.Date import java.util.Date
@Composable @Composable
fun AnimeHistoryScreen( fun AnimeHistoryScreen(
state: AnimeHistoryState, state: AnimeHistoryState,
contentPadding: PaddingValues,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
onSearchQueryChange: (String?) -> Unit,
onClickCover: (animeId: Long) -> Unit, onClickCover: (animeId: Long) -> Unit,
onClickResume: (animeId: Long, episodeId: Long) -> Unit, onClickResume: (animeId: Long, episodeId: Long) -> Unit,
onDialogChange: (AnimeHistoryScreenModel.Dialog?) -> Unit, onDialogChange: (AnimeHistoryScreenModel.Dialog?) -> Unit,
) { ) {
Scaffold( Scaffold(
topBar = { scrollBehavior ->
SearchToolbar(
titleContent = { AppBarTitle(stringResource(R.string.history)) },
searchQuery = state.searchQuery,
onChangeSearchQuery = onSearchQueryChange,
actions = {
IconButton(onClick = { onDialogChange(AnimeHistoryScreenModel.Dialog.DeleteAll) }) {
Icon(
Icons.Outlined.DeleteSweep,
contentDescription = stringResource(R.string.pref_clear_history),
)
}
},
scrollBehavior = scrollBehavior,
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding -> ) { _ ->
state.list.let { state.list.let {
if (it == null) { if (it == null) {
LoadingScreen(modifier = Modifier.padding(contentPadding)) LoadingScreen(modifier = Modifier.padding(contentPadding))

View file

@ -7,7 +7,6 @@ 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

View file

@ -2,6 +2,7 @@ package eu.kanade.presentation.animelib.components
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -1,13 +1,8 @@
package eu.kanade.presentation.animelib.components package eu.kanade.presentation.animelib.components
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -15,16 +10,13 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.animelib.model.AnimelibAnime import eu.kanade.domain.animelib.model.AnimelibAnime
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.HorizontalPager import eu.kanade.presentation.components.HorizontalPager
import eu.kanade.presentation.components.PagerState import eu.kanade.presentation.components.PagerState
import eu.kanade.presentation.library.components.LibraryPagerEmptyScreen import eu.kanade.presentation.library.components.LibraryPagerEmptyScreen
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.animelib.AnimelibItem import eu.kanade.tachiyomi.ui.animelib.AnimelibItem
@Composable @Composable

View file

@ -1,18 +1,11 @@
package eu.kanade.presentation.animeupdates package eu.kanade.presentation.animeupdates
import android.content.Context
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -30,12 +23,11 @@ import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PullRefresh import eu.kanade.presentation.components.PullRefresh
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.updates.updatesLastUpdatedItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesItem import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesState import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -46,6 +38,7 @@ import kotlin.time.Duration.Companion.seconds
fun AnimeUpdateScreen( fun AnimeUpdateScreen(
state: AnimeUpdatesState, state: AnimeUpdatesState,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
contentPadding: PaddingValues,
lastUpdated: Long, lastUpdated: Long,
relativeTime: Int, relativeTime: Int,
onClickCover: (AnimeUpdatesItem) -> Unit, onClickCover: (AnimeUpdatesItem) -> Unit,
@ -54,10 +47,10 @@ fun AnimeUpdateScreen(
onUpdateLibrary: () -> Boolean, onUpdateLibrary: () -> Boolean,
onDownloadEpisode: (List<AnimeUpdatesItem>, EpisodeDownloadAction) -> Unit, onDownloadEpisode: (List<AnimeUpdatesItem>, EpisodeDownloadAction) -> Unit,
onMultiBookmarkClicked: (List<AnimeUpdatesItem>, bookmark: Boolean) -> Unit, onMultiBookmarkClicked: (List<AnimeUpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<AnimeUpdatesItem>, read: Boolean) -> Unit, onMultiMarkAsSeenClicked: (List<AnimeUpdatesItem>, seen: Boolean) -> Unit,
onMultiDeleteClicked: (List<AnimeUpdatesItem>) -> Unit, onMultiDeleteClicked: (List<AnimeUpdatesItem>) -> Unit,
onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit, onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onOpenEpisode: (List<AnimeUpdatesItem>, context: Context, altPlayer: Boolean) -> Unit, onOpenEpisode: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit,
) { ) {
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) }) BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
@ -69,13 +62,13 @@ fun AnimeUpdateScreen(
selected = state.selected, selected = state.selected,
onDownloadEpisode = onDownloadEpisode, onDownloadEpisode = onDownloadEpisode,
onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsSeenClicked = onMultiMarkAsReadClicked, onMultiMarkAsSeenClicked = onMultiMarkAsSeenClicked,
onMultiDeleteClicked = onMultiDeleteClicked, onMultiDeleteClicked = onMultiDeleteClicked,
onOpenEpisode = onOpenEpisode, onOpenEpisode = onOpenEpisode,
) )
}, },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding -> ) {
when { when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.items.isEmpty() -> EmptyScreen( state.items.isEmpty() -> EmptyScreen(
@ -105,14 +98,14 @@ fun AnimeUpdateScreen(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
if (lastUpdated > 0L) { if (lastUpdated > 0L) {
updatesLastUpdatedItem(lastUpdated) animeupdatesLastUpdatedItem(lastUpdated)
} }
animeupdatesUiItems( animeupdatesUiItems(
uiModels = state.getUiModel(context, relativeTime), uiModels = state.getUiModel(context, relativeTime),
selectionMode = state.selectionMode, selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected, onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover, onClickCover = onClickCover,
onClickUpdate = { onOpenEpisode }, onClickUpdate = onOpenEpisode,
onDownloadEpisode = onDownloadEpisode, onDownloadEpisode = onDownloadEpisode,
) )
} }
@ -129,10 +122,9 @@ private fun AnimeUpdatesBottomBar(
onMultiBookmarkClicked: (List<AnimeUpdatesItem>, bookmark: Boolean) -> Unit, onMultiBookmarkClicked: (List<AnimeUpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsSeenClicked: (List<AnimeUpdatesItem>, seen: Boolean) -> Unit, onMultiMarkAsSeenClicked: (List<AnimeUpdatesItem>, seen: Boolean) -> Unit,
onMultiDeleteClicked: (List<AnimeUpdatesItem>) -> Unit, onMultiDeleteClicked: (List<AnimeUpdatesItem>) -> Unit,
onOpenEpisode: (List<AnimeUpdatesItem>, context: Context, altPlayer: Boolean) -> Unit, onOpenEpisode: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit,
) { ) {
val playerPreferences: PlayerPreferences = Injekt.get() val playerPreferences: PlayerPreferences = Injekt.get()
val context = LocalContext.current
AnimeBottomActionMenu( AnimeBottomActionMenu(
visible = selected.isNotEmpty(), visible = selected.isNotEmpty(),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -157,10 +149,10 @@ private fun AnimeUpdatesBottomBar(
onMultiDeleteClicked(selected) onMultiDeleteClicked(selected)
}.takeIf { selected.fastAny { it.downloadStateProvider() == AnimeDownload.State.DOWNLOADED } }, }.takeIf { selected.fastAny { it.downloadStateProvider() == AnimeDownload.State.DOWNLOADED } },
onExternalClicked = { onExternalClicked = {
onOpenEpisode(selected, context, true) onOpenEpisode(selected[0], true)
}.takeIf { !playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 }, }.takeIf { !playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 },
onInternalClicked = { onInternalClicked = {
onOpenEpisode(selected, context,false) onOpenEpisode(selected[0], true)
}.takeIf { playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 }, }.takeIf { playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 },
) )
} }

View file

@ -43,7 +43,7 @@ import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.selectedBackground import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesItem import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem
import java.util.Date import java.util.Date
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@ -82,7 +82,7 @@ fun LazyListScope.animeupdatesUiItems(
selectionMode: Boolean, selectionMode: Boolean,
onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit, onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onClickCover: (AnimeUpdatesItem) -> Unit, onClickCover: (AnimeUpdatesItem) -> Unit,
onClickUpdate: (AnimeUpdatesItem) -> Unit, onClickUpdate: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit,
onDownloadEpisode: (List<AnimeUpdatesItem>, EpisodeDownloadAction) -> Unit, onDownloadEpisode: (List<AnimeUpdatesItem>, EpisodeDownloadAction) -> Unit,
) { ) {
items( items(
@ -119,7 +119,7 @@ fun LazyListScope.animeupdatesUiItems(
onClick = { onClick = {
when { when {
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false) selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
else -> onClickUpdate(updatesItem) else -> onClickUpdate(updatesItem, false)
} }
}, },
onClickCover = { onClickCover(updatesItem) }.takeIf { !selectionMode }, onClickCover = { onClickCover(updatesItem) }.takeIf { !selectionMode },

View file

@ -6,49 +6,40 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.components.CategoryContent import eu.kanade.presentation.category.components.CategoryContent
import eu.kanade.presentation.category.components.CategoryFloatingActionButton import eu.kanade.presentation.category.components.CategoryFloatingActionButton
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topSmallPaddingValues import eu.kanade.presentation.util.topSmallPaddingValues
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreenState import eu.kanade.tachiyomi.ui.category.anime.AnimeCategoryScreenState
@Composable @Composable
fun AnimeCategoryScreen( fun AnimeCategoryScreen(
state: AnimeCategoryScreenState.Success, state: AnimeCategoryScreenState.Success,
contentPadding: PaddingValues,
onClickCreate: () -> Unit, onClickCreate: () -> Unit,
onClickRename: (Category) -> Unit, onClickRename: (Category) -> Unit,
onClickDelete: (Category) -> Unit, onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit, onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit, onClickMoveDown: (Category) -> Unit,
navigateUp: () -> Unit,
) { ) {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
Scaffold( Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.action_edit_categories),
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
floatingActionButton = { floatingActionButton = {
CategoryFloatingActionButton( CategoryFloatingActionButton(
lazyListState = lazyListState, lazyListState = lazyListState,
onCreate = onClickCreate, onCreate = onClickCreate,
) )
}, },
) { paddingValues -> ) {
if (state.isEmpty) { if (state.isEmpty) {
EmptyScreen( EmptyScreen(
textResource = R.string.information_empty_category, textResource = R.string.information_empty_category,
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(contentPadding),
) )
return@Scaffold return@Scaffold
} }
@ -56,7 +47,7 @@ fun AnimeCategoryScreen(
CategoryContent( CategoryContent(
categories = state.categories, categories = state.categories,
lazyListState = lazyListState, lazyListState = lazyListState,
paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename, onClickRename = onClickRename,
onClickDelete = onClickDelete, onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp, onMoveUp = onClickMoveUp,

View file

@ -6,49 +6,40 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.components.CategoryContent import eu.kanade.presentation.category.components.CategoryContent
import eu.kanade.presentation.category.components.CategoryFloatingActionButton import eu.kanade.presentation.category.components.CategoryFloatingActionButton
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topSmallPaddingValues import eu.kanade.presentation.util.topSmallPaddingValues
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.category.CategoryScreenState import eu.kanade.tachiyomi.ui.category.manga.CategoryScreenState
@Composable @Composable
fun CategoryScreen( fun CategoryScreen(
state: CategoryScreenState.Success, state: CategoryScreenState.Success,
contentPadding: PaddingValues,
onClickCreate: () -> Unit, onClickCreate: () -> Unit,
onClickRename: (Category) -> Unit, onClickRename: (Category) -> Unit,
onClickDelete: (Category) -> Unit, onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit, onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit, onClickMoveDown: (Category) -> Unit,
navigateUp: () -> Unit,
) { ) {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
Scaffold( Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.action_edit_categories),
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
floatingActionButton = { floatingActionButton = {
CategoryFloatingActionButton( CategoryFloatingActionButton(
lazyListState = lazyListState, lazyListState = lazyListState,
onCreate = onClickCreate, onCreate = onClickCreate,
) )
}, },
) { paddingValues -> ) {
if (state.isEmpty) { if (state.isEmpty) {
EmptyScreen( EmptyScreen(
textResource = R.string.information_empty_category, textResource = R.string.information_empty_category,
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(contentPadding),
) )
return@Scaffold return@Scaffold
} }
@ -56,7 +47,7 @@ fun CategoryScreen(
CategoryContent( CategoryContent(
categories = state.categories, categories = state.categories,
lazyListState = lazyListState, lazyListState = lazyListState,
paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename, onClickRename = onClickRename,
onClickDelete = onClickDelete, onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp, onMoveUp = onClickMoveUp,

View file

@ -1,9 +1,12 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -30,6 +33,7 @@ import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@ -149,7 +153,25 @@ fun AppBar(
fun AppBarTitle( fun AppBarTitle(
title: String?, title: String?,
subtitle: String? = null, subtitle: String? = null,
count: Int = 0,
) { ) {
if (count > 0) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = title!!,
maxLines = 1,
modifier = Modifier.weight(1f, false),
overflow = TextOverflow.Ellipsis,
)
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
Pill(
text = "$count",
modifier = Modifier.padding(start = 4.dp),
color = MaterialTheme.colorScheme.onBackground.copy(alpha = pillAlpha),
fontSize = 14.sp,
)
}
} else {
Column { Column {
title?.let { title?.let {
Text( Text(
@ -168,6 +190,7 @@ fun AppBarTitle(
} }
} }
} }
}
@Composable @Composable
fun AppBarActions( fun AppBarActions(
@ -317,8 +340,6 @@ fun SearchToolbar(
key("actions") { actions() } key("actions") { actions() }
}, },
isActionMode = false, isActionMode = false,
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onCancelActionMode = cancelAction, onCancelActionMode = cancelAction,
) )

View file

@ -32,15 +32,12 @@ fun TabbedScreen(
startIndex: Int? = null, startIndex: Int? = null,
searchQuery: String? = null, searchQuery: String? = null,
onChangeSearchQuery: (String?) -> Unit = {}, onChangeSearchQuery: (String?) -> Unit = {},
incognitoMode: Boolean = false,
downloadedOnlyMode: Boolean = false,
state: PagerState = rememberPagerState(), state: PagerState = rememberPagerState(),
scrollable: Boolean = false, scrollable: Boolean = false,
searchQueryAnime: String? = null, searchQueryAnime: String? = null,
onChangeSearchQueryAnime: (String?) -> Unit = {}, onChangeSearchQueryAnime: (String?) -> Unit = {},
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberPagerState()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(startIndex) { LaunchedEffect(startIndex) {
@ -65,9 +62,8 @@ fun TabbedScreen(
else -> onChangeSearchQueryAnime else -> onChangeSearchQueryAnime
} }
val appBarTitleText = if (tab.numberTitle == 0) stringResource(titleRes) else tab.numberTitle.toString()
SearchToolbar( SearchToolbar(
titleContent = { AppBarTitle(appBarTitleText) }, titleContent = { AppBarTitle(stringResource(titleRes), null, tab.numberTitle) },
searchEnabled = searchEnabled, searchEnabled = searchEnabled,
searchQuery = if (searchEnabled) actualQuery else null, searchQuery = if (searchEnabled) actualQuery else null,
onChangeSearchQuery = actualOnChange, onChangeSearchQuery = actualOnChange,

View file

@ -2,55 +2,32 @@ package eu.kanade.presentation.history
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteSweep
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.history.components.HistoryContent import eu.kanade.presentation.history.components.HistoryContent
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel import eu.kanade.tachiyomi.ui.history.manga.HistoryState
import eu.kanade.tachiyomi.ui.history.HistoryState import eu.kanade.tachiyomi.ui.history.manga.MangaHistoryScreenModel
import java.util.Date import java.util.Date
@Composable @Composable
fun HistoryScreen( fun HistoryScreen(
state: HistoryState, state: HistoryState,
contentPadding: PaddingValues,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
onSearchQueryChange: (String?) -> Unit,
onClickCover: (mangaId: Long) -> Unit, onClickCover: (mangaId: Long) -> Unit,
onClickResume: (mangaId: Long, chapterId: Long) -> Unit, onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit, onDialogChange: (MangaHistoryScreenModel.Dialog?) -> Unit,
) { ) {
Scaffold( Scaffold(
topBar = { scrollBehavior ->
SearchToolbar(
titleContent = { AppBarTitle(stringResource(R.string.history)) },
searchQuery = state.searchQuery,
onChangeSearchQuery = onSearchQueryChange,
actions = {
IconButton(onClick = { onDialogChange(HistoryScreenModel.Dialog.DeleteAll) }) {
Icon(
Icons.Outlined.DeleteSweep,
contentDescription = stringResource(R.string.pref_clear_history),
)
}
},
scrollBehavior = scrollBehavior,
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding -> ) { _ ->
state.list.let { state.list.let {
if (it == null) { if (it == null) {
LoadingScreen(modifier = Modifier.padding(contentPadding)) LoadingScreen(modifier = Modifier.padding(contentPadding))
@ -70,7 +47,7 @@ fun HistoryScreen(
contentPadding = contentPadding, contentPadding = contentPadding,
onClickCover = { history -> onClickCover(history.mangaId) }, onClickCover = { history -> onClickCover(history.mangaId) },
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) }, onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) }, onClickDelete = { item -> onDialogChange(MangaHistoryScreenModel.Dialog.Delete(item)) },
) )
} }
} }

View file

@ -102,7 +102,7 @@ fun AnimeHistoryItem(
modifier = modifier modifier = modifier
.clickable(onClick = onClickResume) .clickable(onClick = onClickResume)
.height(HISTORY_ITEM_HEIGHT) .height(HISTORY_ITEM_HEIGHT)
.padding(horizontal = horizontalPadding, vertical = 8.dp), .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
MangaCover.Book( MangaCover.Book(
@ -113,7 +113,7 @@ fun AnimeHistoryItem(
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(start = horizontalPadding, end = 8.dp), .padding(start = MaterialTheme.padding.medium, end = MaterialTheme.padding.small),
) { ) {
val textStyle = MaterialTheme.typography.bodyMedium val textStyle = MaterialTheme.typography.bodyMedium
Text( Text(

View file

@ -26,6 +26,7 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
@ -33,9 +34,9 @@ import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.more.AnimeDownloadQueueState
import eu.kanade.tachiyomi.ui.more.DownloadQueueState import eu.kanade.tachiyomi.ui.more.DownloadQueueState
import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.Constants
import uy.kohesive.injekt.injectLazy
@Composable @Composable
fun MoreScreen( fun MoreScreen(
@ -45,9 +46,8 @@ fun MoreScreen(
incognitoMode: Boolean, incognitoMode: Boolean,
onIncognitoModeChange: (Boolean) -> Unit, onIncognitoModeChange: (Boolean) -> Unit,
isFDroid: Boolean, isFDroid: Boolean,
onClickHistory: () -> Unit, onClickAlt: () -> Unit,
onClickDownloadQueue: () -> Unit, onClickDownloadQueue: () -> Unit,
onClickAnimeCategories: () -> Unit,
onClickCategories: () -> Unit, onClickCategories: () -> Unit,
onClickStats: () -> Unit, onClickStats: () -> Unit,
onClickBackupAndRestore: () -> Unit, onClickBackupAndRestore: () -> Unit,
@ -101,22 +101,24 @@ fun MoreScreen(
item { Divider() } item { Divider() }
val libraryPreferences: LibraryPreferences by injectLazy()
item { item {
val bottomNavStyle = libraryPreferences.bottomNavStyle().get() val bottomNavStyle = libraryPreferences.bottomNavStyle().get()
val titleRes = when (bottomNavStyle) { val titleRes = when (bottomNavStyle) {
0 -> R.string.label_recent_manga
1 -> R.string.label_recent_updates 1 -> R.string.label_recent_updates
2 -> R.string.label_manga else -> R.string.label_manga
else -> R.string.label_recent_manga
} }
val icon = when (bottomNavStyle) { val icon = when (bottomNavStyle) {
0 -> Icons.Outlined.History
1 -> ImageVector.vectorResource(id = R.drawable.ic_updates_outline_24dp) 1 -> ImageVector.vectorResource(id = R.drawable.ic_updates_outline_24dp)
2 -> Icons.Outlined.CollectionsBookmark else -> Icons.Outlined.CollectionsBookmark
else -> Icons.Outlined.History
} }
TextPreferenceWidget( TextPreferenceWidget(
title = stringResource(titleRes), title = stringResource(titleRes),
icon = icon, icon = icon,
onPreferenceClick = onClickHistory, onPreferenceClick = onClickAlt,
) )
} }
@ -151,7 +153,7 @@ fun MoreScreen(
} }
item { item {
TextPreferenceWidget( TextPreferenceWidget(
title = stringResource(R.string.categories), title = stringResource(R.string.general_categories),
icon = Icons.Outlined.Label, icon = Icons.Outlined.Label,
onPreferenceClick = onClickCategories, onPreferenceClick = onClickCategories,
) )

View file

@ -199,8 +199,10 @@ object SettingsAdvancedScreen : SearchableSettings {
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_invalidate_download_cache), title = stringResource(R.string.pref_invalidate_download_cache),
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary), subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
onClick = { Injekt.get<DownloadCache>().invalidateCache() onClick = {
Injekt.get<AnimeDownloadCache>().invalidateCache() }, Injekt.get<DownloadCache>().invalidateCache()
Injekt.get<AnimeDownloadCache>().invalidateCache()
},
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_clear_database), title = stringResource(R.string.pref_clear_database),

View file

@ -51,6 +51,15 @@ object SettingsBrowseScreen : SearchableSettings {
), ),
), ),
), ),
Preference.PreferenceGroup(
title = stringResource(R.string.action_global_search),
preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.searchPinnedAnimeSourcesOnly(),
title = stringResource(R.string.pref_search_pinned_sources_only),
),
),
),
Preference.PreferenceGroup( Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_nsfw_content), title = stringResource(R.string.pref_category_nsfw_content),
preferenceItems = listOf( preferenceItems = listOf(

View file

@ -103,6 +103,7 @@ object SettingsDownloadScreen : SearchableSettings {
} }
val defaultDirPair = rememberDefaultDownloadDir() val defaultDirPair = rememberDefaultDownloadDir()
val externalDownloaderDirPair = rememberExternalDownloaderDownloadDir()
val customDirEntryKey = currentDir.takeIf { it != defaultDirPair.first } ?: "custom" val customDirEntryKey = currentDir.takeIf { it != defaultDirPair.first } ?: "custom"
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
@ -115,6 +116,7 @@ object SettingsDownloadScreen : SearchableSettings {
}, },
entries = mapOf( entries = mapOf(
defaultDirPair, defaultDirPair,
externalDownloaderDirPair,
customDirEntryKey to stringResource(R.string.custom_dir), customDirEntryKey to stringResource(R.string.custom_dir),
), ),
onValueChanged = { onValueChanged = {
@ -129,6 +131,20 @@ object SettingsDownloadScreen : SearchableSettings {
@Composable @Composable
private fun rememberDefaultDownloadDir(): Pair<String, String> { private fun rememberDefaultDownloadDir(): Pair<String, String> {
val appName = stringResource(R.string.app_name)
return remember {
val file = UniFile.fromFile(
File(
"${Environment.getExternalStorageDirectory().absolutePath}${File.separator}$appName",
"downloads",
),
)!!
file.uri.toString() + "\n" to file.filePath!!
}
}
@Composable
private fun rememberExternalDownloaderDownloadDir(): Pair<String, String> {
val appName = stringResource(R.string.app_name) val appName = stringResource(R.string.app_name)
return remember { return remember {
val file = UniFile.fromFile( val file = UniFile.fromFile(

View file

@ -35,8 +35,8 @@ import androidx.core.content.ContextCompat
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.category.interactor.GetAnimeCategories
import com.commandiron.wheel_picker_compose.WheelPicker import com.commandiron.wheel_picker_compose.WheelPicker
import eu.kanade.domain.category.interactor.GetAnimeCategories
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.ResetCategoryFlags import eu.kanade.domain.category.interactor.ResetCategoryFlags
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
@ -55,8 +55,7 @@ import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen import eu.kanade.tachiyomi.ui.category.CategoriesTab
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -155,7 +154,7 @@ object SettingsLibraryScreen : SearchableSettings {
count = userAnimeCategoriesCount, count = userAnimeCategoriesCount,
userAnimeCategoriesCount, userAnimeCategoriesCount,
), ),
onClick = { navigator.push(AnimeCategoryScreen()) }, onClick = { navigator.push(CategoriesTab(false)) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultAnimeCategory(), pref = libraryPreferences.defaultAnimeCategory(),
@ -170,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings {
count = userCategoriesCount, count = userCategoriesCount,
userCategoriesCount, userCategoriesCount,
), ),
onClick = { navigator.push(CategoryScreen()) }, onClick = { navigator.push(CategoriesTab(true)) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultCategory(), pref = libraryPreferences.defaultCategory(),

View file

@ -6,10 +6,7 @@ import android.os.Build
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -23,14 +20,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.chargemap.compose.numberpicker.NumberPicker import com.commandiron.wheel_picker_compose.WheelTextPicker
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.util.preference.asState import eu.kanade.tachiyomi.util.preference.asState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -124,7 +120,7 @@ object SettingsPlayerScreen : SearchableSettings {
@Composable @Composable
private fun getInternalPlayerGroup(playerPreferences: PlayerPreferences, basePreferences: BasePreferences): Preference.PreferenceGroup { private fun getInternalPlayerGroup(playerPreferences: PlayerPreferences, basePreferences: BasePreferences): Preference.PreferenceGroup {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val defaultSkipIntroLength by playerPreferences.skipLengthPreference().stateIn(scope).collectAsState() val defaultSkipIntroLength by playerPreferences.defaultIntroLength().stateIn(scope).collectAsState()
val skipLengthPreference = playerPreferences.skipLengthPreference() val skipLengthPreference = playerPreferences.skipLengthPreference()
val playerSmoothSeek = playerPreferences.playerSmoothSeek() val playerSmoothSeek = playerPreferences.playerSmoothSeek()
val playerFullscreen = playerPreferences.playerFullscreen() val playerFullscreen = playerPreferences.playerFullscreen()
@ -142,7 +138,7 @@ object SettingsPlayerScreen : SearchableSettings {
initialSkipIntroLength = defaultSkipIntroLength, initialSkipIntroLength = defaultSkipIntroLength,
onDismissRequest = { showDialog = false }, onDismissRequest = { showDialog = false },
onValueChanged = { skipIntroLength -> onValueChanged = { skipIntroLength ->
playerPreferences.skipLengthPreference().set(skipIntroLength) playerPreferences.defaultIntroLength().set(skipIntroLength)
showDialog = false showDialog = false
}, },
) )
@ -290,15 +286,14 @@ object SettingsPlayerScreen : SearchableSettings {
) )
} }
// TODO: chnage to new wheel Picker
@Composable @Composable
private fun SkipIntroLengthDialog( private fun SkipIntroLengthDialog(
initialSkipIntroLength: Int, initialSkipIntroLength: Int,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onValueChanged: (skipIntroLength: Int) -> Unit, onValueChanged: (skipIntroLength: Int) -> Unit,
) { ) {
var skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) } val skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) }
var newLength = 0
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(R.string.pref_intro_length)) }, title = { Text(text = stringResource(R.string.pref_intro_length)) },
@ -308,16 +303,13 @@ object SettingsPlayerScreen : SearchableSettings {
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
NumberPicker( WheelTextPicker(
modifier = Modifier texts = remember { 1..255 }.map { "$it" },
.fillMaxWidth() onScrollFinished = {
.clipToBounds(), newLength = it
value = skipIntroLengthValue, null
onValueChange = { skipIntroLengthValue = it }, },
range = 1..255, startIndex = skipIntroLengthValue,
label = { it.toString() },
dividersColor = MaterialTheme.colorScheme.primary,
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface),
) )
} }
} }
@ -328,7 +320,7 @@ object SettingsPlayerScreen : SearchableSettings {
} }
}, },
confirmButton = { confirmButton = {
TextButton(onClick = { onValueChanged(skipIntroLengthValue) }) { TextButton(onClick = { onValueChanged(newLength) }) {
Text(text = stringResource(android.R.string.ok)) Text(text = stringResource(android.R.string.ok))
} }
}, },

View file

@ -0,0 +1,159 @@
package eu.kanade.presentation.more.stats
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CollectionsBookmark
import androidx.compose.material.icons.outlined.LocalLibrary
import androidx.compose.material.icons.outlined.Schedule
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import eu.kanade.core.util.toDurationString
import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.more.stats.components.StatsItem
import eu.kanade.presentation.more.stats.components.StatsOverviewItem
import eu.kanade.presentation.more.stats.components.StatsSection
import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R
import java.util.Locale
import kotlin.time.DurationUnit
import kotlin.time.toDuration
@Composable
fun AnimeStatsScreenContent(
state: StatsScreenState.SuccessAnime,
paddingValues: PaddingValues,
) {
val statListState = rememberLazyListState()
LazyColumn(
state = statListState,
contentPadding = paddingValues,
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) {
item {
OverviewSection(state.overview)
}
item {
TitlesStats(state.titles)
}
item {
EpisodeStats(state.episodes)
}
item {
TrackerStats(state.trackers)
}
}
}
@Composable
private fun OverviewSection(
data: StatsData.AnimeOverview,
) {
val none = stringResource(R.string.none)
val context = LocalContext.current
val readDurationString = remember(data.totalSeenDuration) {
data.totalSeenDuration
.toDuration(DurationUnit.MILLISECONDS)
.toDurationString(context, fallback = none)
}
StatsSection(R.string.label_overview_section) {
Row {
StatsOverviewItem(
title = data.libraryAnimeCount.toString(),
subtitle = stringResource(R.string.in_library),
icon = Icons.Outlined.CollectionsBookmark,
)
StatsOverviewItem(
title = data.completedAnimeCount.toString(),
subtitle = stringResource(R.string.label_completed_titles),
icon = Icons.Outlined.LocalLibrary,
)
StatsOverviewItem(
title = readDurationString,
subtitle = stringResource(R.string.label_watched_duration),
icon = Icons.Outlined.Schedule,
)
}
}
}
@Composable
private fun TitlesStats(
data: StatsData.AnimeTitles,
) {
StatsSection(R.string.label_titles_section) {
Row {
StatsItem(
data.globalUpdateItemCount.toString(),
stringResource(R.string.label_titles_in_global_update),
)
StatsItem(
data.startedAnimeCount.toString(),
stringResource(R.string.label_started),
)
StatsItem(
data.localAnimeCount.toString(),
stringResource(R.string.label_local),
)
}
}
}
@Composable
private fun EpisodeStats(
data: StatsData.Episodes,
) {
StatsSection(R.string.episodes) {
Row {
StatsItem(
data.totalEpisodeCount.toString(),
stringResource(R.string.label_total_chapters),
)
StatsItem(
data.readEpisodeCount.toString(),
stringResource(R.string.label_watched_episodes),
)
StatsItem(
data.downloadCount.toString(),
stringResource(R.string.label_downloaded),
)
}
}
}
@Composable
private fun TrackerStats(
data: StatsData.Trackers,
) {
val notApplicable = stringResource(R.string.not_applicable)
val meanScoreStr = remember(data.trackedTitleCount, data.meanScore) {
if (data.trackedTitleCount > 0 && !data.meanScore.isNaN()) {
// All other numbers are localized in English
String.format(Locale.ENGLISH, "%.2f ★", data.meanScore)
} else {
notApplicable
}
}
StatsSection(R.string.label_tracker_section) {
Row {
StatsItem(
data.trackedTitleCount.toString(),
stringResource(R.string.label_tracked_titles),
)
StatsItem(
meanScoreStr,
stringResource(R.string.label_mean_score),
)
StatsItem(
data.trackerCount.toString(),
stringResource(R.string.label_used),
)
}
}
}

View file

@ -26,8 +26,8 @@ import kotlin.time.DurationUnit
import kotlin.time.toDuration import kotlin.time.toDuration
@Composable @Composable
fun StatsScreenContent( fun MangaStatsScreenContent(
state: StatsScreenState.Success, state: StatsScreenState.SuccessManga,
paddingValues: PaddingValues, paddingValues: PaddingValues,
) { ) {
val statListState = rememberLazyListState() val statListState = rememberLazyListState()
@ -53,7 +53,7 @@ fun StatsScreenContent(
@Composable @Composable
private fun OverviewSection( private fun OverviewSection(
data: StatsData.Overview, data: StatsData.MangaOverview,
) { ) {
val none = stringResource(R.string.none) val none = stringResource(R.string.none)
val context = LocalContext.current val context = LocalContext.current
@ -85,7 +85,7 @@ private fun OverviewSection(
@Composable @Composable
private fun TitlesStats( private fun TitlesStats(
data: StatsData.Titles, data: StatsData.MangaTitles,
) { ) {
StatsSection(R.string.label_titles_section) { StatsSection(R.string.label_titles_section) {
Row { Row {

View file

@ -8,10 +8,18 @@ sealed class StatsScreenState {
object Loading : StatsScreenState() object Loading : StatsScreenState()
@Immutable @Immutable
data class Success( data class SuccessManga(
val overview: StatsData.Overview, val overview: StatsData.MangaOverview,
val titles: StatsData.Titles, val titles: StatsData.MangaTitles,
val chapters: StatsData.Chapters, val chapters: StatsData.Chapters,
val trackers: StatsData.Trackers, val trackers: StatsData.Trackers,
) : StatsScreenState() ) : StatsScreenState()
@Immutable
data class SuccessAnime(
val overview: StatsData.AnimeOverview,
val titles: StatsData.AnimeTitles,
val episodes: StatsData.Episodes,
val trackers: StatsData.Trackers,
) : StatsScreenState()
} }

View file

@ -2,24 +2,42 @@ package eu.kanade.presentation.more.stats.data
sealed class StatsData { sealed class StatsData {
data class Overview( data class MangaOverview(
val libraryMangaCount: Int, val libraryMangaCount: Int,
val completedMangaCount: Int, val completedMangaCount: Int,
val totalReadDuration: Long, val totalReadDuration: Long,
) : StatsData() ) : StatsData()
data class Titles( data class AnimeOverview(
val libraryAnimeCount: Int,
val completedAnimeCount: Int,
val totalSeenDuration: Long,
) : StatsData()
data class MangaTitles(
val globalUpdateItemCount: Int, val globalUpdateItemCount: Int,
val startedMangaCount: Int, val startedMangaCount: Int,
val localMangaCount: Int, val localMangaCount: Int,
) : StatsData() ) : StatsData()
data class AnimeTitles(
val globalUpdateItemCount: Int,
val startedAnimeCount: Int,
val localAnimeCount: Int,
) : StatsData()
data class Chapters( data class Chapters(
val totalChapterCount: Int, val totalChapterCount: Int,
val readChapterCount: Int, val readChapterCount: Int,
val downloadCount: Int, val downloadCount: Int,
) : StatsData() ) : StatsData()
data class Episodes(
val totalEpisodeCount: Int,
val readEpisodeCount: Int,
val downloadCount: Int,
) : StatsData()
data class Trackers( data class Trackers(
val trackedTitleCount: Int, val trackedTitleCount: Int,
val meanScore: Double, val meanScore: Double,

View file

@ -1,17 +1,11 @@
package eu.kanade.presentation.updates package eu.kanade.presentation.updates
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -31,8 +25,8 @@ import eu.kanade.presentation.components.PullRefresh
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem import eu.kanade.tachiyomi.ui.updates.manga.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.UpdatesState import eu.kanade.tachiyomi.ui.updates.manga.UpdatesState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -41,6 +35,7 @@ import kotlin.time.Duration.Companion.seconds
fun UpdateScreen( fun UpdateScreen(
state: UpdatesState, state: UpdatesState,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
contentPadding: PaddingValues,
lastUpdated: Long, lastUpdated: Long,
relativeTime: Int, relativeTime: Int,
onClickCover: (UpdatesItem) -> Unit, onClickCover: (UpdatesItem) -> Unit,
@ -69,7 +64,7 @@ fun UpdateScreen(
) )
}, },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding -> ) {
when { when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.items.isEmpty() -> EmptyScreen( state.items.isEmpty() -> EmptyScreen(

View file

@ -43,7 +43,7 @@ import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.selectedBackground import eu.kanade.presentation.util.selectedBackground
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem import eu.kanade.tachiyomi.ui.updates.manga.UpdatesItem
import java.util.Date import java.util.Date
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes

View file

@ -50,7 +50,8 @@ import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences import eu.kanade.tachiyomi.ui.player.ExternalIntents
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.system.isDevFlavor import eu.kanade.tachiyomi.util.system.isDevFlavor
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
@ -194,6 +195,8 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { ImageSaver(app) } addSingletonFactory { ImageSaver(app) }
addSingletonFactory { ExternalIntents() }
// Asynchronously init expensive components for a faster cold start // Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute { ContextCompat.getMainExecutor(app).execute {
get<NetworkHelper>() get<NetworkHelper>()

View file

@ -22,7 +22,7 @@ import eu.kanade.tachiyomi.data.updater.AppUpdateJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.preference.minusAssign import eu.kanade.tachiyomi.util.preference.minusAssign

View file

@ -60,7 +60,6 @@ class AnimeDownloadCache(
.onStart { emit(Unit) } .onStart { emit(Unit) }
.shareIn(scope, SharingStarted.Eagerly, 1) .shareIn(scope, SharingStarted.Eagerly, 1)
/** /**
* The interval after which this cache should be invalidated. 1 hour shouldn't cause major * The interval after which this cache should be invalidated. 1 hour shouldn't cause major
* issues, as the cache is only used for UI feedback. * issues, as the cache is only used for UI feedback.

View file

@ -93,7 +93,6 @@ class AnimeDownloadManager(
return queue.find { it.episode.id == episodeId } return queue.find { it.episode.id == episodeId }
} }
fun startDownloadNow(episodeId: Long?) { fun startDownloadNow(episodeId: Long?) {
if (episodeId == null) return if (episodeId == null) return
val download = getQueuedDownloadOrNull(episodeId) val download = getQueuedDownloadOrNull(episodeId)

View file

@ -33,7 +33,6 @@ import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.storage.toFFmpegString import eu.kanade.tachiyomi.util.storage.toFFmpegString
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
@ -346,7 +345,7 @@ class AnimeDownloader(
val videoObservable = if (download.video == null) { val videoObservable = if (download.video == null) {
// Pull video from network and add them to download object // Pull video from network and add them to download object
download.source.fetchVideoList(download.episode).map { it.first() } download.source.fetchVideoList(download.episode.toSEpisode()).map { it.first() }
.doOnNext { video -> .doOnNext { video ->
if (video == null) { if (video == null) {
throw Exception(context.getString(R.string.video_list_empty_error)) throw Exception(context.getString(R.string.video_list_empty_error))
@ -458,13 +457,13 @@ class AnimeDownloader(
video.videoUrl = file.uri.path video.videoUrl = file.uri.path
video.progress = 100 video.progress = 100
download.downloadedImages++ download.downloadedImages++
video.status = Video.READY video.status = Video.State.READY
} }
.map { video } .map { video }
// Mark this video as error and allow to download the remaining // Mark this video as error and allow to download the remaining
.onErrorReturn { .onErrorReturn {
video.progress = 0 video.progress = 0
video.status = Video.ERROR video.status = Video.State.ERROR
notifier.onError(it.message, download.episode.name, download.anime.title) notifier.onError(it.message, download.episode.name, download.anime.title)
video video
} }
@ -479,7 +478,7 @@ class AnimeDownloader(
* @param filename the filename of the video. * @param filename the filename of the video.
*/ */
private fun downloadVideo(video: Video, download: AnimeDownload, tmpDir: UniFile, filename: String): Observable<UniFile> { private fun downloadVideo(video: Video, download: AnimeDownload, tmpDir: UniFile, filename: String): Observable<UniFile> {
video.status = Video.DOWNLOAD_IMAGE video.status = Video.State.DOWNLOAD_IMAGE
video.progress = 0 video.progress = 0
var tries = 0 var tries = 0
return newObservable(video, download, tmpDir, filename) return newObservable(video, download, tmpDir, filename)
@ -632,7 +631,7 @@ class AnimeDownloader(
* @param filename the filename of the video. * @param filename the filename of the video.
*/ */
private fun downloadVideoExternal(video: Video, source: AnimeHttpSource, tmpDir: UniFile, filename: String): Observable<UniFile> { private fun downloadVideoExternal(video: Video, source: AnimeHttpSource, tmpDir: UniFile, filename: String): Observable<UniFile> {
video.status = Video.DOWNLOAD_IMAGE video.status = Video.State.DOWNLOAD_IMAGE
video.progress = 0 video.progress = 0
return Observable.just(tmpDir.createFile("$filename.mp4")).map { return Observable.just(tmpDir.createFile("$filename.mp4")).map {
try { try {

View file

@ -4,7 +4,7 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import okio.buffer import okio.buffer

View file

@ -35,27 +35,27 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_PREFS
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_PREFS_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_PREFS_MASK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.full.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnime import eu.kanade.tachiyomi.data.backup.models.BackupAnime
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeHistory import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeSource import eu.kanade.tachiyomi.data.backup.models.BackupAnimeSource
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.full.models.BooleanPreferenceValue import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.FloatPreferenceValue import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.IntPreferenceValue import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.LongPreferenceValue import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.StringPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.StringSetPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.backupAnimeTrackMapper import eu.kanade.tachiyomi.data.backup.models.backupAnimeTrackMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupCategoryMapper import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupChapterMapper import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupEpisodeMapper import eu.kanade.tachiyomi.data.backup.models.backupEpisodeMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
import eu.kanade.tachiyomi.data.database.models.Anime import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter

View file

@ -4,21 +4,21 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnime import eu.kanade.tachiyomi.data.backup.models.BackupAnime
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeHistory import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeSource import eu.kanade.tachiyomi.data.backup.models.BackupAnimeSource
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.full.models.BooleanPreferenceValue import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.FloatPreferenceValue import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.IntPreferenceValue import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.LongPreferenceValue import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.StringPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.StringSetPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.database.models.Anime import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeImpl import eu.kanade.tachiyomi.data.database.models.AnimeImpl

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSource
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.AnimeTrackImpl import eu.kanade.tachiyomi.data.database.models.AnimeTrackImpl

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.ChapterImpl import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.EpisodeImpl import eu.kanade.tachiyomi.data.database.models.EpisodeImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.ChapterImpl import eu.kanade.tachiyomi.data.database.models.ChapterImpl

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializer import kotlinx.serialization.Serializer

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.full.models package eu.kanade.tachiyomi.data.backup.models
import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -29,7 +29,6 @@ interface Anime : SAnime {
var skipIntroLength: Int var skipIntroLength: Int
get() = viewer_flags and 0x000000FF get() = viewer_flags and 0x000000FF
set(skipIntro) = setViewerFlags(skipIntro, 0x000000FF) set(skipIntro) = setViewerFlags(skipIntro, 0x000000FF)
} }
fun Anime.toDomainAnime(): DomainAnime? { fun Anime.toDomainAnime(): DomainAnime? {

View file

@ -4,10 +4,6 @@ import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.animetrack.model.AnimeTrack import eu.kanade.domain.animetrack.model.AnimeTrack
import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.track.model.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
/** /**
* An Enhanced Track Service will never prompt the user to match a manga with the remote. * An Enhanced Track Service will never prompt the user to match a manga with the remote.

View file

@ -134,5 +134,4 @@ interface MangaTrackService {
} }
} }
} }
} }

View file

@ -15,8 +15,8 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.domain.track.model.Track as DomainTrack
import eu.kanade.domain.animetrack.model.AnimeTrack as DomainAnimeTrack import eu.kanade.domain.animetrack.model.AnimeTrack as DomainAnimeTrack
import eu.kanade.domain.track.model.Track as DomainTrack
class Anilist(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { class Anilist(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService {

View file

@ -173,6 +173,7 @@ class KitsuLibAnime(obj: JsonObject, anime: JsonObject) {
else -> throw Exception("Unknown status") else -> throw Exception("Unknown status")
} }
} }
@Serializable @Serializable
data class OAuth( data class OAuth(
val access_token: String, val access_token: String,

View file

@ -47,8 +47,8 @@ import eu.kanade.data.AnimeDatabaseHandler
import eu.kanade.domain.anime.model.AnimeCover import eu.kanade.domain.anime.model.AnimeCover
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.Constants
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.Constants
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope

View file

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.ui
import androidx.compose.runtime.getValue
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryPresenter
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.history.HistoryPresenter
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class HistoryTabsPresenter(
preferences: BasePreferences = Injekt.get(),
) : BasePresenter<HistoryTabsController>() {
val animeHistoryPresenter = AnimeHistoryPresenter(presenterScope, view)
val historyPresenter = HistoryPresenter(presenterScope, view)
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
fun resumeLastChapterRead() {
historyPresenter.resumeLastChapterRead()
}
fun resumeLastEpisodeSeen() {
animeHistoryPresenter.resumeLastEpisodeSeen()
}
}

View file

@ -1,63 +0,0 @@
package eu.kanade.tachiyomi.ui
import android.Manifest
import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.presentation.components.PagerState
import eu.kanade.presentation.components.TabbedScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.animeupdates.animeUpdatesTab
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.ui.download.anime.AnimeDownloadController
import eu.kanade.tachiyomi.ui.download.manga.DownloadController
import eu.kanade.tachiyomi.ui.main.MainActivity
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class UpdatesTabsController : FullComposeController<UpdatesTabsPresenter>(), RootController {
override fun createPresenter() = UpdatesTabsPresenter()
private val state = PagerState(currentPage = TAB_ANIME)
@Composable
override fun ComposeContent() {
val libraryPreferences: LibraryPreferences = Injekt.get()
val fromMore = libraryPreferences.bottomNavStyle().get() == 1
TabbedScreen(
titleRes = R.string.label_recent_updates,
tabs = listOf(
animeUpdatesTab(router, presenter.animeUpdatesPresenter, activity, fromMore),
updatesTab(router, presenter.updatesPresenter, activity, fromMore),
),
incognitoMode = presenter.isIncognitoMode,
downloadedOnlyMode = presenter.isDownloadOnly,
state = state,
)
LaunchedEffect(Unit) {
(activity as? MainActivity)?.ready = true
}
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
requestPermissionsSafe(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 301)
}
fun openDownloadQueue() {
if (state.currentPage == TAB_MANGA) {
router.pushController(DownloadController())
} else {
router.pushController(AnimeDownloadController())
}
}
}
private const val TAB_ANIME = 0
private const val TAB_MANGA = 1

View file

@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.ui
import android.os.Bundle
import androidx.compose.runtime.getValue
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesPresenter
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.updates.UpdatesPresenter
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class UpdatesTabsPresenter(
preferences: BasePreferences = Injekt.get(),
) : BasePresenter<UpdatesTabsController>() {
val animeUpdatesPresenter = AnimeUpdatesPresenter(presenterScope, view)
val updatesPresenter = UpdatesPresenter(presenterScope, view)
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
animeUpdatesPresenter.onCreate()
updatesPresenter.onCreate()
}
}

View file

@ -18,7 +18,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.core.net.toUri import androidx.core.net.toUri
import cafe.adriel.voyager.core.model.coroutineScope
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey
@ -27,35 +26,35 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import com.commandiron.wheel_picker_compose.WheelTextPicker import com.commandiron.wheel_picker_compose.WheelTextPicker
import eu.kanade.domain.anime.interactor.SetAnimeViewerFlags import eu.kanade.domain.anime.interactor.SetAnimeViewerFlags
import eu.kanade.domain.episode.model.Episode
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.hasCustomCover import eu.kanade.domain.anime.model.hasCustomCover
import eu.kanade.domain.episode.model.Episode
import eu.kanade.presentation.anime.AnimeScreen
import eu.kanade.presentation.anime.EpisodeSettingsDialog
import eu.kanade.presentation.anime.components.AnimeCoverDialog
import eu.kanade.presentation.anime.components.DeleteEpisodesDialog
import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.DuplicateAnimeDialog import eu.kanade.presentation.components.DuplicateAnimeDialog
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.NavigatorAdaptiveSheet import eu.kanade.presentation.components.NavigatorAdaptiveSheet
import eu.kanade.presentation.anime.EpisodeSettingsDialog
import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.presentation.anime.AnimeScreen
import eu.kanade.presentation.anime.components.DeleteEpisodesDialog
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.presentation.anime.components.AnimeCoverDialog
import eu.kanade.presentation.manga.BaseSelector import eu.kanade.presentation.manga.BaseSelector
import eu.kanade.presentation.manga.TrackChapterSelector import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.isTabletUi import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.isLocalOrStub import eu.kanade.tachiyomi.animesource.isLocalOrStub
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.ui.anime.track.AnimeTrackInfoDialogHomeScreen
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchScreen import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchScreen
import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.ui.category.CategoriesTab
import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.anime.track.AnimeTrackInfoDialogHomeScreen import eu.kanade.tachiyomi.ui.player.ExternalIntents
import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -64,10 +63,7 @@ import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class AnimeScreen( class AnimeScreen(
@ -116,7 +112,12 @@ class AnimeScreen(
snackbarHostState = screenModel.snackbarHostState, snackbarHostState = screenModel.snackbarHostState,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
onBackClicked = navigator::pop, onBackClicked = navigator::pop,
onEpisodeClicked = { episode, alt -> openEpisode(context, episode, alt) }, onEpisodeClicked = { episode, alt ->
scope.launchIO {
openEpisode(context, episode, alt)
}
Unit
},
onDownloadEpisode = screenModel::runEpisodeDownloadActions.takeIf { !successState.source.isLocalOrStub() }, onDownloadEpisode = screenModel::runEpisodeDownloadActions.takeIf { !successState.source.isLocalOrStub() },
onAddToLibraryClicked = { onAddToLibraryClicked = {
screenModel.toggleFavorite() screenModel.toggleFavorite()
@ -128,7 +129,12 @@ class AnimeScreen(
onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } }, onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
onFilterButtonClicked = screenModel::showSettingsDialog, onFilterButtonClicked = screenModel::showSettingsDialog,
onRefresh = screenModel::fetchAllFromSource, onRefresh = screenModel::fetchAllFromSource,
onContinueWatching = { continueWatching(context, screenModel.getNextUnseenEpisode()) }, onContinueWatching = {
scope.launchIO {
continueWatching(context, screenModel.getNextUnseenEpisode())
}
Unit
},
onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } }, onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } },
onCoverClicked = screenModel::showCoverDialog, onCoverClicked = screenModel::showCoverDialog,
onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource }, onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource },
@ -152,7 +158,7 @@ class AnimeScreen(
ChangeCategoryDialog( ChangeCategoryDialog(
initialSelection = dialog.initialSelection, initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onEditCategories = { navigator.push(AnimeCategoryScreen()) }, onEditCategories = { navigator.push(CategoriesTab(false)) },
onConfirm = { include, _ -> onConfirm = { include, _ ->
screenModel.moveAnimeToCategoriesAndAddToLibrary(dialog.anime, include) screenModel.moveAnimeToCategoriesAndAddToLibrary(dialog.anime, include)
}, },
@ -242,13 +248,24 @@ class AnimeScreen(
} }
} }
private fun continueWatching(context: Context, unseenEpisode: Episode?) { private suspend fun continueWatching(context: Context, unseenEpisode: Episode?) {
if (unseenEpisode != null) openEpisode(context, unseenEpisode) if (unseenEpisode != null) openEpisode(context, unseenEpisode)
} }
private fun openEpisodeInternal(context: Context, animeId: Long, episodeId: Long) {
context.startActivity(PlayerActivity.newIntent(context, animeId, episodeId))
}
// TODO: External Player support private suspend fun openEpisodeExternal(context: Context, animeId: Long, episodeId: Long) {
private fun openEpisode(context: Context, episode: Episode, alt: Boolean = false) { context.startActivity(ExternalIntents.newIntent(context, animeId, episodeId))
context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id)) }
private suspend fun openEpisode(context: Context, episode: Episode, altPlayer: Boolean = false) {
val playerPreferences: PlayerPreferences by injectLazy()
if (playerPreferences.alwaysUseExternalPlayer().get() != altPlayer) {
openEpisodeExternal(context, episode.animeId, episode.id)
} else {
openEpisodeInternal(context, episode.animeId, episode.id)
}
} }
private fun getAnimeUrl(anime_: Anime?, source_: AnimeSource?): String? { private fun getAnimeUrl(anime_: Anime?, source_: AnimeSource?): String? {
@ -347,6 +364,7 @@ fun ChangeIntroLength(
anime: Anime, anime: Anime,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
val scope = rememberCoroutineScope()
val setAnimeViewerFlags: SetAnimeViewerFlags by injectLazy() val setAnimeViewerFlags: SetAnimeViewerFlags by injectLazy()
val titleText = R.string.action_change_intro_length val titleText = R.string.action_change_intro_length
var newLength = 0 var newLength = 0
@ -364,10 +382,12 @@ fun ChangeIntroLength(
) )
}, },
onConfirm = { onConfirm = {
launchIO { scope.launchIO {
setAnimeViewerFlags.awaitSetSkipIntroLength(anime.id, newLength.toLong()) setAnimeViewerFlags.awaitSetSkipIntroLength(anime.id, newLength.toLong())
onDismissRequest() onDismissRequest()
}}, }
Unit
},
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) )
} }

View file

@ -15,7 +15,6 @@ import eu.kanade.domain.anime.interactor.GetDuplicateAnimelibAnime
import eu.kanade.domain.anime.interactor.SetAnimeEpisodeFlags import eu.kanade.domain.anime.interactor.SetAnimeEpisodeFlags
import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.interactor.UpdateAnime
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.anime.model.isLocal import eu.kanade.domain.anime.model.isLocal
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
import eu.kanade.domain.animetrack.model.toDbTrack import eu.kanade.domain.animetrack.model.toDbTrack
@ -30,6 +29,7 @@ import eu.kanade.domain.episode.interactor.UpdateEpisode
import eu.kanade.domain.episode.model.Episode import eu.kanade.domain.episode.model.Episode
import eu.kanade.domain.episode.model.EpisodeUpdate import eu.kanade.domain.episode.model.EpisodeUpdate
import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.EpisodeDownloadAction import eu.kanade.presentation.components.EpisodeDownloadAction
@ -41,8 +41,8 @@ import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadService import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadService
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.AnimeTrackService import eu.kanade.tachiyomi.data.track.AnimeTrackService
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.HttpException
@ -57,7 +57,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -123,6 +122,7 @@ class AnimeInfoScreenModel(
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
private val selectedEpisodeIds: HashSet<Long> = HashSet() private val selectedEpisodeIds: HashSet<Long> = HashSet()
/** /**
* Helper function to update the UI state only if it's currently in success state * Helper function to update the UI state only if it's currently in success state
*/ */
@ -252,11 +252,10 @@ class AnimeInfoScreenModel(
if (trackPreferences.trackOnAddingToLibrary().get() && loggedServices.isNotEmpty()) { if (trackPreferences.trackOnAddingToLibrary().get() && loggedServices.isNotEmpty()) {
showTrackDialog() showTrackDialog()
} }
} },
) )
} }
/** /**
* Update favorite status of anime, (removes / adds) anime (to / from) library. * Update favorite status of anime, (removes / adds) anime (to / from) library.
*/ */
@ -610,7 +609,6 @@ class AnimeInfoScreenModel(
} }
} }
fun runEpisodeDownloadActions( fun runEpisodeDownloadActions(
items: List<EpisodeItem>, items: List<EpisodeItem>,
action: EpisodeDownloadAction, action: EpisodeDownloadAction,
@ -829,7 +827,6 @@ class AnimeInfoScreenModel(
} }
} }
fun toggleSelection( fun toggleSelection(
item: EpisodeItem, item: EpisodeItem,
selected: Boolean, selected: Boolean,

View file

@ -37,7 +37,6 @@ import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.anime.interactor.GetAnimeWithEpisodes import eu.kanade.domain.anime.interactor.GetAnimeWithEpisodes
import eu.kanade.domain.animetrack.interactor.DeleteAnimeTrack import eu.kanade.domain.animetrack.interactor.DeleteAnimeTrack
@ -45,20 +44,21 @@ import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
import eu.kanade.domain.animetrack.model.toDbTrack import eu.kanade.domain.animetrack.model.toDbTrack
import eu.kanade.domain.animetrack.model.toDomainTrack import eu.kanade.domain.animetrack.model.toDomainTrack
import eu.kanade.domain.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.anime.AnimeTrackInfoDialogHome import eu.kanade.presentation.anime.AnimeTrackInfoDialogHome
import eu.kanade.presentation.anime.AnimeTrackServiceSearch
import eu.kanade.presentation.components.AlertDialogContent import eu.kanade.presentation.components.AlertDialogContent
import eu.kanade.presentation.manga.TrackChapterSelector import eu.kanade.presentation.manga.TrackChapterSelector
import eu.kanade.presentation.manga.TrackDateSelector import eu.kanade.presentation.manga.TrackDateSelector
import eu.kanade.presentation.manga.TrackScoreSelector import eu.kanade.presentation.manga.TrackScoreSelector
import eu.kanade.presentation.anime.AnimeTrackServiceSearch
import eu.kanade.presentation.manga.TrackStatusSelector import eu.kanade.presentation.manga.TrackStatusSelector
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext

View file

@ -1,82 +0,0 @@
package eu.kanade.tachiyomi.ui.animecategory
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.category.AnimeCategoryScreen
import eu.kanade.presentation.category.components.CategoryCreateDialog
import eu.kanade.presentation.category.components.CategoryDeleteDialog
import eu.kanade.presentation.category.components.CategoryRenameDialog
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
class AnimeCategoryScreen : Screen {
override val key = uniqueScreenKey
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { AnimeCategoryScreenModel() }
val state by screenModel.state.collectAsState()
if (state is AnimeCategoryScreenState.Loading) {
LoadingScreen()
return
}
val successState = state as AnimeCategoryScreenState.Success
AnimeCategoryScreen(
state = successState,
onClickCreate = { screenModel.showDialog(AnimeCategoryDialog.Create) },
onClickRename = { screenModel.showDialog(AnimeCategoryDialog.Rename(it)) },
onClickDelete = { screenModel.showDialog(AnimeCategoryDialog.Delete(it)) },
onClickMoveUp = screenModel::moveUp,
onClickMoveDown = screenModel::moveDown,
navigateUp = navigator::pop,
)
when (val dialog = successState.dialog) {
null -> {}
AnimeCategoryDialog.Create -> {
CategoryCreateDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createCategory(it) },
)
}
is AnimeCategoryDialog.Rename -> {
CategoryRenameDialog(
onDismissRequest = screenModel::dismissDialog,
onRename = { screenModel.renameCategory(dialog.category, it) },
category = dialog.category,
)
}
is AnimeCategoryDialog.Delete -> {
CategoryDeleteDialog(
onDismissRequest = screenModel::dismissDialog,
onDelete = { screenModel.deleteCategory(dialog.category.id) },
category = dialog.category,
)
}
}
LaunchedEffect(Unit) {
screenModel.events.collectLatest { event ->
if (event is AnimeCategoryEvent.LocalizedMessage) {
context.toast(event.stringRes)
}
}
}
}
}

View file

@ -1,132 +0,0 @@
package eu.kanade.tachiyomi.ui.animehistory
import android.content.Context
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
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.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions
import eu.kanade.domain.episode.model.Episode
import eu.kanade.presentation.animehistory.AnimeHistoryScreen
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
import eu.kanade.presentation.animehistory.components.AnimeHistoryDeleteDialog
import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.consumeAsFlow
import uy.kohesive.injekt.injectLazy
object AnimeHistoryTab : Tab {
private val snackbarHostState = SnackbarHostState()
private val resumeLastEpisodeReadEvent = Channel<Unit>()
private val playerPreferences: PlayerPreferences by injectLazy()
override val options: TabOptions
@Composable
get() {
val isSelected = LocalTabNavigator.current.current.key == key
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter)
return TabOptions(
index = 2u,
title = stringResource(R.string.label_recent_manga),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
override suspend fun onReselect(navigator: Navigator) {
resumeLastEpisodeReadEvent.send(Unit)
}
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val context = LocalContext.current
val screenModel = rememberScreenModel { AnimeHistoryScreenModel() }
val state by screenModel.state.collectAsState()
AnimeHistoryScreen(
state = state,
snackbarHostState = snackbarHostState,
onSearchQueryChange = screenModel::updateSearchQuery,
onClickCover = { navigator.push(AnimeScreen(it)) },
onClickResume = screenModel::getNextEpisodeForAnime,
onDialogChange = screenModel::setDialog,
)
val onDismissRequest = { screenModel.setDialog(null) }
when (val dialog = state.dialog) {
is AnimeHistoryScreenModel.Dialog.Delete -> {
AnimeHistoryDeleteDialog(
onDismissRequest = onDismissRequest,
onDelete = { all ->
if (all) {
screenModel.removeAllFromHistory(dialog.history.animeId)
} else {
screenModel.removeFromHistory(dialog.history)
}
},
)
}
is AnimeHistoryScreenModel.Dialog.DeleteAll -> {
HistoryDeleteAllDialog(
onDismissRequest = onDismissRequest,
onDelete = screenModel::removeAllHistory,
)
}
null -> {}
}
LaunchedEffect(state.list) {
if (state.list != null) {
(context as? MainActivity)?.ready = true
}
}
LaunchedEffect(Unit) {
screenModel.events.collectLatest { e ->
when (e) {
AnimeHistoryScreenModel.Event.InternalError ->
snackbarHostState.showSnackbar(context.getString(R.string.internal_error))
AnimeHistoryScreenModel.Event.HistoryCleared ->
snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed))
is AnimeHistoryScreenModel.Event.OpenEpisode -> openEpisode(context, e.episode)
}
}
}
LaunchedEffect(Unit) {
resumeLastEpisodeReadEvent.consumeAsFlow().collectLatest {
openEpisode(context, screenModel.getNextEpisode())
}
}
}
// TODO: Add external player setting
suspend fun openEpisode(context: Context, episode: Episode?) {
if (episode != null) {
val intent = PlayerActivity.newIntent(context, episode.animeId, episode.id)
context.startActivity(intent)
} else {
snackbarHostState.showSnackbar(context.getString(R.string.no_next_episode))
}
}
}

View file

@ -15,33 +15,34 @@ import eu.kanade.core.util.fastFilter
import eu.kanade.core.util.fastFilterNot import eu.kanade.core.util.fastFilterNot
import eu.kanade.core.util.fastMapNotNull import eu.kanade.core.util.fastMapNotNull
import eu.kanade.core.util.fastPartition import eu.kanade.core.util.fastPartition
import eu.kanade.domain.anime.interactor.GetAnimelibAnime
import eu.kanade.domain.anime.interactor.UpdateAnime
import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.AnimeUpdate
import eu.kanade.domain.anime.model.isLocal
import eu.kanade.domain.animehistory.interactor.GetNextEpisodes
import eu.kanade.domain.animelib.model.AnimelibAnime
import eu.kanade.domain.animetrack.interactor.GetTracksPerAnime
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.category.interactor.GetAnimeCategories import eu.kanade.domain.category.interactor.GetAnimeCategories
import eu.kanade.domain.category.interactor.SetAnimeCategories import eu.kanade.domain.category.interactor.SetAnimeCategories
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId
import eu.kanade.domain.episode.interactor.SetSeenStatus import eu.kanade.domain.episode.interactor.SetSeenStatus
import eu.kanade.domain.animehistory.interactor.GetNextEpisodes import eu.kanade.domain.episode.model.Episode
import eu.kanade.domain.library.model.LibrarySort import eu.kanade.domain.library.model.LibrarySort
import eu.kanade.domain.library.model.sort import eu.kanade.domain.library.model.sort
import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.anime.interactor.GetAnimelibAnime
import eu.kanade.domain.anime.interactor.UpdateAnime
import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.AnimeUpdate
import eu.kanade.domain.anime.model.isLocal
import eu.kanade.domain.animelib.model.AnimelibAnime
import eu.kanade.domain.animetrack.interactor.GetTracksPerAnime
import eu.kanade.domain.episode.model.Episode
import eu.kanade.presentation.library.components.LibraryToolbarTitle import eu.kanade.presentation.library.components.LibraryToolbarTitle
import eu.kanade.presentation.manga.DownloadAction import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.track.AnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.util.episode.getNextUnseen import eu.kanade.tachiyomi.util.episode.getNextUnseen
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
@ -337,11 +338,11 @@ class AnimelibScreenModel(
libraryPreferences.languageBadge().changes(), libraryPreferences.languageBadge().changes(),
preferences.downloadedOnly().changes(), preferences.downloadedOnly().changes(),
libraryPreferences.filterDownloaded().changes(), libraryPreferences.filterDownloadedAnime().changes(),
libraryPreferences.filterUnread().changes(), libraryPreferences.filterUnseen().changes(),
libraryPreferences.filterStarted().changes(), libraryPreferences.filterStartedAnime().changes(),
libraryPreferences.filterBookmarked().changes(), libraryPreferences.filterBookmarkedAnime().changes(),
libraryPreferences.filterCompleted().changes(), libraryPreferences.filterCompletedAnime().changes(),
transform = { transform = {
ItemPreferences( ItemPreferences(
downloadBadge = it[0] as Boolean, downloadBadge = it[0] as Boolean,
@ -406,10 +407,10 @@ class AnimelibScreenModel(
* @return map of track id with the filter value * @return map of track id with the filter value
*/ */
private fun getTrackingFilterFlow(): Flow<Map<Long, Int>> { private fun getTrackingFilterFlow(): Flow<Map<Long, Int>> {
val loggedServices = trackManager.services.filter { it.isLogged } val loggedServices = trackManager.services.filter { it.isLogged && it is AnimeTrackService }
return if (loggedServices.isNotEmpty()) { return if (loggedServices.isNotEmpty()) {
val prefFlows = loggedServices val prefFlows = loggedServices
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() } .map { libraryPreferences.filterTrackingAnime(it.id.toInt()).changes() }
.toTypedArray() .toTypedArray()
combine(*prefFlows) { combine(*prefFlows) {
loggedServices loggedServices

View file

@ -126,15 +126,15 @@ class AnimelibSettingsSheet(
downloaded.state = State.INCLUDE.value downloaded.state = State.INCLUDE.value
downloaded.enabled = false downloaded.enabled = false
} else { } else {
downloaded.state = libraryPreferences.filterDownloaded().get() downloaded.state = libraryPreferences.filterDownloadedAnime().get()
} }
unseen.state = libraryPreferences.filterUnread().get() unseen.state = libraryPreferences.filterUnseen().get()
started.state = libraryPreferences.filterStarted().get() started.state = libraryPreferences.filterStartedAnime().get()
bookmarked.state = libraryPreferences.filterBookmarked().get() bookmarked.state = libraryPreferences.filterBookmarkedAnime().get()
completed.state = libraryPreferences.filterCompleted().get() completed.state = libraryPreferences.filterCompletedAnime().get()
trackFilters.forEach { trackFilter -> trackFilters.forEach { trackFilter ->
trackFilter.value.state = libraryPreferences.filterTracking(trackFilter.key.toInt()).get() trackFilter.value.state = libraryPreferences.filterTrackingAnime(trackFilter.key.toInt()).get()
} }
} }
@ -148,15 +148,15 @@ class AnimelibSettingsSheet(
} }
item.state = newState item.state = newState
when (item) { when (item) {
downloaded -> libraryPreferences.filterDownloaded().set(newState) downloaded -> libraryPreferences.filterDownloadedAnime().set(newState)
unseen -> libraryPreferences.filterUnread().set(newState) unseen -> libraryPreferences.filterUnseen().set(newState)
started -> libraryPreferences.filterStarted().set(newState) started -> libraryPreferences.filterStartedAnime().set(newState)
bookmarked -> libraryPreferences.filterBookmarked().set(newState) bookmarked -> libraryPreferences.filterBookmarkedAnime().set(newState)
completed -> libraryPreferences.filterCompleted().set(newState) completed -> libraryPreferences.filterCompletedAnime().set(newState)
else -> { else -> {
trackFilters.forEach { trackFilter -> trackFilters.forEach { trackFilter ->
if (trackFilter.value == item) { if (trackFilter.value == item) {
libraryPreferences.filterTracking(trackFilter.key.toInt()).set(newState) libraryPreferences.filterTrackingAnime(trackFilter.key.toInt()).set(newState)
} }
} }
} }

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.animelib package eu.kanade.tachiyomi.ui.animelib
import android.content.Context
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
@ -28,31 +29,34 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.animelib.model.AnimelibAnime
import eu.kanade.domain.library.model.display
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.isLocal import eu.kanade.domain.anime.model.isLocal
import eu.kanade.domain.animelib.model.AnimelibAnime
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.episode.model.Episode
import eu.kanade.domain.library.model.display
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.presentation.animelib.components.AnimelibContent
import eu.kanade.presentation.components.AnimelibBottomActionMenu
import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.DeleteAnimelibAnimeDialog import eu.kanade.presentation.components.DeleteAnimelibAnimeDialog
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.EmptyScreenAction import eu.kanade.presentation.components.EmptyScreenAction
import eu.kanade.presentation.components.AnimelibBottomActionMenu
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.animelib.components.AnimelibContent
import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.presentation.util.Tab import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService
import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchScreen import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchScreen
import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.category.CategoriesTab
import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.player.ExternalIntents
import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -72,7 +76,7 @@ object AnimelibTab : Tab {
R.string.label_animelib R.string.label_animelib
} }
val isSelected = LocalTabNavigator.current.current.key == key val isSelected = LocalTabNavigator.current.current.key == key
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter) val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_animelibrary_leave)
return TabOptions( return TabOptions(
index = 0u, index = 0u,
title = stringResource(title), title = stringResource(title),
@ -108,6 +112,23 @@ object AnimelibTab : Tab {
scope.launch { sendSettingsSheetIntent(state.categories[screenModel.activeCategoryIndex]) } scope.launch { sendSettingsSheetIntent(state.categories[screenModel.activeCategoryIndex]) }
} }
fun openEpisodeInternal(context: Context, animeId: Long, episodeId: Long) {
context.startActivity(PlayerActivity.newIntent(context, animeId, episodeId))
}
suspend fun openEpisodeExternal(context: Context, animeId: Long, episodeId: Long) {
context.startActivity(ExternalIntents.newIntent(context, animeId, episodeId))
}
suspend fun openEpisode(episode: Episode) {
val playerPreferences: PlayerPreferences by injectLazy()
if (playerPreferences.alwaysUseExternalPlayer().get()) {
openEpisodeExternal(context, episode.animeId, episode.id)
} else {
openEpisodeInternal(context, episode.animeId, episode.id)
}
}
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
val title = state.getToolbarTitle( val title = state.getToolbarTitle(
@ -183,13 +204,8 @@ object AnimelibTab : Tab {
onContinueWatchingClicked = { it: AnimelibAnime -> onContinueWatchingClicked = { it: AnimelibAnime ->
scope.launchIO { scope.launchIO {
val episode = screenModel.getNextUnseenEpisode(it.anime) val episode = screenModel.getNextUnseenEpisode(it.anime)
if (episode != null) { if (episode != null) openEpisode(episode)
context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id))
} else {
snackbarHostState.showSnackbar(context.getString(R.string.no_next_episode))
} }
}
// TODO: External Intent
Unit Unit
}.takeIf { state.showAnimeContinueButton }, }.takeIf { state.showAnimeContinueButton },
onToggleSelection = { screenModel.toggleSelection(it) }, onToggleSelection = { screenModel.toggleSelection(it) },
@ -217,7 +233,7 @@ object AnimelibTab : Tab {
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onEditCategories = { onEditCategories = {
screenModel.clearSelection() screenModel.clearSelection()
navigator.push(CategoryScreen()) navigator.push(CategoriesTab(false))
}, },
onConfirm = { include, exclude -> onConfirm = { include, exclude ->
screenModel.clearSelection() screenModel.clearSelection()

View file

@ -1,116 +0,0 @@
package eu.kanade.tachiyomi.ui.animeupdates
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions
import eu.kanade.presentation.animeupdates.AnimeUpdateScreen
import eu.kanade.presentation.animeupdates.AnimeUpdatesDeleteConfirmationDialog
import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.download.anime.AnimeDownloadQueueScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesScreenModel.Event
import kotlinx.coroutines.flow.collectLatest
object AnimeUpdatesTab : Tab {
override val options: TabOptions
@Composable
get() {
val isSelected = LocalTabNavigator.current.current.key == key
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter)
return TabOptions(
index = 1u,
title = stringResource(R.string.label_recent_updates),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
override suspend fun onReselect(navigator: Navigator) {
navigator.push(AnimeDownloadQueueScreen)
}
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { AnimeUpdatesScreenModel() }
val state by screenModel.state.collectAsState()
AnimeUpdateScreen(
state = state,
snackbarHostState = screenModel.snackbarHostState,
lastUpdated = screenModel.lastUpdated,
relativeTime = screenModel.relativeTime,
onClickCover = { item -> navigator.push(AnimeScreen(item.update.animeId)) },
onSelectAll = screenModel::toggleAllSelection,
onInvertSelection = screenModel::invertSelection,
onUpdateLibrary = screenModel::updateLibrary,
onDownloadEpisode = screenModel::downloadEpisodes,
onMultiBookmarkClicked = screenModel::bookmarkUpdates,
onMultiMarkAsReadClicked = screenModel::markUpdatesSeen,
onMultiDeleteClicked = screenModel::showConfirmDeleteEpisodes,
onUpdateSelected = screenModel::toggleSelection,
onOpenEpisode = screenModel::openEpisode,
)
val onDismissDialog = { screenModel.setDialog(null) }
when (val dialog = state.dialog) {
is AnimeUpdatesScreenModel.Dialog.DeleteConfirmation -> {
AnimeUpdatesDeleteConfirmationDialog(
onDismissRequest = onDismissDialog,
onConfirm = { screenModel.deleteEpisodes(dialog.toDelete) },
)
}
null -> {}
}
LaunchedEffect(Unit) {
screenModel.events.collectLatest { event ->
when (event) {
Event.InternalError -> screenModel.snackbarHostState.showSnackbar(context.getString(R.string.internal_error))
is Event.LibraryUpdateTriggered -> {
val msg = if (event.started) {
R.string.updating_library
} else {
R.string.update_already_running
}
screenModel.snackbarHostState.showSnackbar(context.getString(msg))
}
}
}
}
LaunchedEffect(state.selectionMode) {
HomeScreen.showBottomNav(!state.selectionMode)
}
LaunchedEffect(state.isLoading) {
if (!state.isLoading) {
(context as? MainActivity)?.ready = true
}
}
DisposableEffect(Unit) {
screenModel.resetNewUpdatesCount()
onDispose {
screenModel.resetNewUpdatesCount()
}
}
}
}

View file

@ -36,7 +36,7 @@ data class BrowseTab(
val isSelected = LocalTabNavigator.current.current.key == key val isSelected = LocalTabNavigator.current.current.key == key
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter) val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter)
return TabOptions( return TabOptions(
index = 6u, index = 3u,
title = stringResource(R.string.browse), title = stringResource(R.string.browse),
icon = rememberAnimatedVectorPainter(image, isSelected), icon = rememberAnimatedVectorPainter(image, isSelected),
) )
@ -68,6 +68,7 @@ data class BrowseTab(
onChangeSearchQuery = extensionsScreenModel::search, onChangeSearchQuery = extensionsScreenModel::search,
searchQueryAnime = animeExtensionsQuery, searchQueryAnime = animeExtensionsQuery,
onChangeSearchQueryAnime = animeExtensionsScreenModel::search, onChangeSearchQueryAnime = animeExtensionsScreenModel::search,
scrollable = true,
) )
// For local source // For local source

View file

@ -220,7 +220,6 @@ data class AnimeExtensionsState(
val isEmpty = items.isEmpty() val isEmpty = items.isEmpty()
} }
sealed interface AnimeExtensionUiModel { sealed interface AnimeExtensionUiModel {
sealed interface Header : AnimeExtensionUiModel { sealed interface Header : AnimeExtensionUiModel {
data class Resource(@StringRes val textRes: Int) : Header data class Resource(@StringRes val textRes: Int) : Header

View file

@ -21,189 +21,7 @@ data class AnimeExtensionDetailsScreen(
@Composable @Composable
override fun Content() { override fun Content() {
val context = LocalContext.current val context = LocalContext.current
val screenModel = rememberScreenModel { AnimeEpackage eu.kanade.tachiyomi.ui.browse.extension.details val screenModel = rememberScreenModel { AnimeExtensionDetailsScreenModel(pkgName = pkgName, context = context) }
import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import androidx.appcompat.view.ContextThemeWrapper
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.preference.DialogPreference
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.preference.forEach
import androidx.preference.getOnBindEditTextListener
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.getPreferenceKey
import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SourcePreferencesScreen(val sourceId: Long) : Screen {
override val key = uniqueScreenKey
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = Injekt.get<SourceManager>().get(sourceId)!!.toString()) },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.Outlined.ArrowBack,
contentDescription = stringResource(R.string.abc_action_bar_up_description),
)
}
},
scrollBehavior = it,
)
},
) { contentPadding ->
FragmentContainer(
fragmentManager = (context as FragmentActivity).supportFragmentManager,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding),
) {
val fragment = SourcePreferencesFragment.getInstance(sourceId)
add(it, fragment, null)
}
}
}
/**
* From https://stackoverflow.com/questions/60520145/fragment-container-in-jetpack-compose/70817794#70817794
*/
@Composable
private fun FragmentContainer(
fragmentManager: FragmentManager,
modifier: Modifier = Modifier,
commit: FragmentTransaction.(containerId: Int) -> Unit,
) {
val containerId by rememberSaveable {
mutableStateOf(View.generateViewId())
}
var initialized by rememberSaveable { mutableStateOf(false) }
AndroidView(
modifier = modifier,
factory = { context ->
FragmentContainerView(context)
.apply { id = containerId }
},
update = { view ->
if (!initialized) {
fragmentManager.commit { commit(view.id) }
initialized = true
} else {
fragmentManager.onContainerAvailable(view)
}
},
)
}
/** Access to package-private method in FragmentManager through reflection */
private fun FragmentManager.onContainerAvailable(view: FragmentContainerView) {
val method = FragmentManager::class.java.getDeclaredMethod(
"onContainerAvailable",
FragmentContainerView::class.java,
)
method.isAccessible = true
method.invoke(this, view)
}
}
class SourcePreferencesFragment : PreferenceFragmentCompat() {
override fun getContext(): Context? {
val superCtx = super.getContext() ?: return null
val tv = TypedValue()
superCtx.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
return ContextThemeWrapper(superCtx, tv.resourceId)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceScreen = populateScreen()
}
private fun populateScreen(): PreferenceScreen {
val sourceId = requireArguments().getLong(SOURCE_ID)
val source = Injekt.get<SourceManager>().get(sourceId)!!
check(source is ConfigurableSource)
val sharedPreferences = requireContext().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE)
val dataStore = SharedPreferencesDataStore(sharedPreferences)
preferenceManager.preferenceDataStore = dataStore
val sourceScreen = preferenceManager.createPreferenceScreen(requireContext())
source.setupPreferenceScreen(sourceScreen)
sourceScreen.forEach { pref ->
pref.isIconSpaceReserved = false
if (pref is DialogPreference) {
pref.dialogTitle = pref.title
}
// Apply incognito IME for EditTextPreference
if (pref is EditTextPreference) {
val setListener = pref.getOnBindEditTextListener()
pref.setOnBindEditTextListener {
setListener?.onBindEditText(it)
it.setIncognito(lifecycleScope)
}
}
}
return sourceScreen
}
companion object {
private const val SOURCE_ID = "source_id"
fun getInstance(sourceId: Long): SourcePreferencesFragment {
val fragment = SourcePreferencesFragment()
fragment.arguments = bundleOf(SOURCE_ID to sourceId)
return fragment
}
}
}xtensionDetailsScreenModel(pkgName = pkgName, context = context) }
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
if (state.isLoading) { if (state.isLoading) {

View file

@ -41,9 +41,10 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.getPreferenceKey
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
import eu.kanade.tachiyomi.source.getPreferenceKey import eu.kanade.tachiyomi.source.getPreferenceKey
import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt

View file

@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.animesource
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.animesource.interactor.GetLanguagesWithAnimeSources import eu.kanade.domain.animesource.interactor.GetLanguagesWithAnimeSources
import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.animesource.interactor.ToggleAnimeSource import eu.kanade.domain.animesource.interactor.ToggleAnimeSource
import eu.kanade.domain.animesource.model.AnimeSource import eu.kanade.domain.animesource.model.AnimeSource
import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -27,7 +27,7 @@ class AnimeSourcesFilterScreenModel(
combine( combine(
getLanguagesWithSources.subscribe(), getLanguagesWithSources.subscribe(),
preferences.enabledLanguages().changes(), preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(), preferences.disabledAnimeSources().changes(),
) { a, b, c -> Triple(a, b, c) } ) { a, b, c -> Triple(a, b, c) }
.catch { throwable -> .catch { throwable ->
mutableState.update { mutableState.update {

View file

@ -50,9 +50,9 @@ import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.LocalAnimeSource import eu.kanade.tachiyomi.animesource.LocalAnimeSource
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen
import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.category.CategoriesTab
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.Constants
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
@ -242,7 +242,7 @@ data class BrowseAnimeSourceScreen(
ChangeCategoryDialog( ChangeCategoryDialog(
initialSelection = dialog.initialSelection, initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onEditCategories = { navigator.push(AnimeCategoryScreen()) }, onEditCategories = { navigator.push(CategoriesTab(false)) },
onConfirm = { include, _ -> onConfirm = { include, _ ->
screenModel.changeAnimeFavorite(dialog.anime) screenModel.changeAnimeFavorite(dialog.anime)
screenModel.moveAnimeToCategories(dialog.anime, include) screenModel.moveAnimeToCategories(dialog.anime, include)

View file

@ -21,8 +21,8 @@ import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.anime.interactor.GetDuplicateAnimelibAnime import eu.kanade.domain.anime.interactor.GetDuplicateAnimelibAnime
import eu.kanade.domain.anime.interactor.NetworkToLocalAnime import eu.kanade.domain.anime.interactor.NetworkToLocalAnime
import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.interactor.UpdateAnime
import eu.kanade.domain.anime.model.toAnimeUpdate
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.toAnimeUpdate
import eu.kanade.domain.anime.model.toDomainAnime import eu.kanade.domain.anime.model.toDomainAnime
import eu.kanade.domain.animesource.interactor.GetRemoteAnime import eu.kanade.domain.animesource.interactor.GetRemoteAnime
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
@ -299,10 +299,12 @@ class BrowseAnimeSourceScreenModel(
// Choose a category // Choose a category
else -> { else -> {
val preselectedIds = getCategories.await(anime.id).map { it.id } val preselectedIds = getCategories.await(anime.id).map { it.id }
setDialog(Dialog.ChangeAnimeCategory( setDialog(
Dialog.ChangeAnimeCategory(
anime, anime,
categories.mapAsCheckboxState { it.id in preselectedIds }, categories.mapAsCheckboxState { it.id in preselectedIds },
)) ),
)
} }
} }
} }

View file

@ -9,8 +9,8 @@ import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.anime.interactor.NetworkToLocalAnime import eu.kanade.domain.anime.interactor.NetworkToLocalAnime
import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.interactor.UpdateAnime
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.toDomainAnime
import eu.kanade.domain.anime.model.toAnimeUpdate import eu.kanade.domain.anime.model.toAnimeUpdate
import eu.kanade.domain.anime.model.toDomainAnime
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.animeextension.AnimeExtensionManager import eu.kanade.tachiyomi.animeextension.AnimeExtensionManager
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
@ -42,7 +42,7 @@ abstract class AnimeSearchScreenModel<T>(
protected lateinit var extensionFilter: String protected lateinit var extensionFilter: String
private val sources by lazy { getSelectedSources() } private val sources by lazy { getSelectedSources() }
private val pinnedSources by lazy { sourcePreferences.pinnedSources().get() } private val pinnedSources by lazy { sourcePreferences.pinnedAnimeSources().get() }
private val sortComparator = { map: Map<AnimeCatalogueSource, AnimeSearchItemResult> -> private val sortComparator = { map: Map<AnimeCatalogueSource, AnimeSearchItemResult> ->
compareBy<AnimeCatalogueSource>( compareBy<AnimeCatalogueSource>(
@ -95,8 +95,8 @@ abstract class AnimeSearchScreenModel<T>(
val enabledSources = getEnabledSources() val enabledSources = getEnabledSources()
if (filter.isEmpty()) { if (filter.isEmpty()) {
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get() val shouldSearchPinnedOnly = sourcePreferences.searchPinnedAnimeSourcesOnly().get()
val pinnedSources = sourcePreferences.pinnedSources().get() val pinnedSources = sourcePreferences.pinnedAnimeSources().get()
return enabledSources.filter { return enabledSources.filter {
if (shouldSearchPinnedOnly) { if (shouldSearchPinnedOnly) {

View file

@ -9,8 +9,8 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.animebrowse.GlobalAnimeSearchScreen import eu.kanade.presentation.animebrowse.GlobalAnimeSearchScreen
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen
import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen
class GlobalAnimeSearchScreen( class GlobalAnimeSearchScreen(
val searchQuery: String = "", val searchQuery: String = "",

View file

@ -18,7 +18,7 @@ class GlobalAnimeSearchScreenModel(
) : AnimeSearchScreenModel<GlobalAnimeSearchState>(GlobalAnimeSearchState(searchQuery = initialQuery)) { ) : AnimeSearchScreenModel<GlobalAnimeSearchState>(GlobalAnimeSearchState(searchQuery = initialQuery)) {
val incognitoMode = preferences.incognitoMode() val incognitoMode = preferences.incognitoMode()
val lastUsedSourceId = sourcePreferences.lastUsedSource() val lastUsedSourceId = sourcePreferences.lastUsedAnimeSource()
init { init {
extensionFilter = initialExtensionFilter extensionFilter = initialExtensionFilter

View file

@ -12,8 +12,8 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.animebrowse.MigrateAnimeScreen import eu.kanade.presentation.animebrowse.MigrateAnimeScreen
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -38,7 +38,7 @@ data class MigrationAnimeScreen(
navigateUp = navigator::pop, navigateUp = navigator::pop,
title = state.source!!.name, title = state.source!!.name,
state = state, state = state,
onClickItem = { navigator.push(MigrateSearchScreen(it.id)) }, onClickItem = { navigator.push(MigrateAnimeSearchScreen(it.id)) },
onClickCover = { navigator.push(AnimeScreen(it.id)) }, onClickCover = { navigator.push(AnimeScreen(it.id)) },
) )

View file

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.browse.migration.animesources
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.animesource.interactor.GetAnimeSourcesWithFavoriteCount import eu.kanade.domain.animesource.interactor.GetAnimeSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.animesource.model.AnimeSource import eu.kanade.domain.animesource.model.AnimeSource
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat

View file

@ -30,9 +30,9 @@ import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.LocalAnimeSource import eu.kanade.tachiyomi.animesource.LocalAnimeSource
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel
import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.Constants
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -27,30 +27,30 @@ import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetAnimeCategories
import eu.kanade.domain.episode.interactor.UpdateEpisode
import eu.kanade.domain.episode.model.toEpisodeUpdate
import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.interactor.UpdateAnime
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.anime.model.AnimeUpdate import eu.kanade.domain.anime.model.AnimeUpdate
import eu.kanade.domain.anime.model.hasCustomCover import eu.kanade.domain.anime.model.hasCustomCover
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetAnimeCategories
import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId
import eu.kanade.domain.episode.interactor.SyncEpisodesWithSource import eu.kanade.domain.episode.interactor.SyncEpisodesWithSource
import eu.kanade.domain.episode.interactor.UpdateEpisode
import eu.kanade.domain.episode.model.toEpisodeUpdate
import eu.kanade.presentation.animebrowse.MigrateAnimeSearchScreen import eu.kanade.presentation.animebrowse.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.preference.Preference
import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.core.preference.Preference
import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.ui.browse.migration.AnimeMigrationFlags import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.browse.migration.AnimeMigrationFlags
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt

View file

@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager
@ -38,7 +38,7 @@ class MigrateAnimeSearchScreenModel(
} }
val incognitoMode = preferences.incognitoMode() val incognitoMode = preferences.incognitoMode()
val lastUsedSourceId = sourcePreferences.lastUsedSource() val lastUsedSourceId = sourcePreferences.lastUsedAnimeSource()
override fun getEnabledSources(): List<AnimeCatalogueSource> { override fun getEnabledSources(): List<AnimeCatalogueSource> {
val enabledLanguages = sourcePreferences.enabledLanguages().get() val enabledLanguages = sourcePreferences.enabledLanguages().get()

View file

@ -25,7 +25,7 @@ fun Screen.migrateSourceTab(): TabContent {
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
return TabContent( return TabContent(
titleRes = R.string.label_migration_manga, titleRes = R.string.label_migration,
actions = listOf( actions = listOf(
AppBar.Action( AppBar.Action(
title = stringResource(R.string.migration_help_guide), title = stringResource(R.string.migration_help_guide),

View file

@ -51,7 +51,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.category.CategoryScreen import eu.kanade.tachiyomi.ui.category.CategoriesTab
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.Constants
@ -242,7 +242,7 @@ data class BrowseSourceScreen(
ChangeCategoryDialog( ChangeCategoryDialog(
initialSelection = dialog.initialSelection, initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
onEditCategories = { navigator.push(CategoryScreen()) }, onEditCategories = { navigator.push(CategoriesTab(true)) },
onConfirm = { include, _ -> onConfirm = { include, _ ->
screenModel.changeMangaFavorite(dialog.manga) screenModel.changeMangaFavorite(dialog.manga)
screenModel.moveMangaToCategories(dialog.manga, include) screenModel.moveMangaToCategories(dialog.manga, include)

View file

@ -0,0 +1,52 @@
package eu.kanade.tachiyomi.ui.category
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions
import eu.kanade.presentation.components.TabbedScreen
import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.category.anime.animeCategoryTab
import eu.kanade.tachiyomi.ui.category.manga.mangaCategoryTab
import eu.kanade.tachiyomi.ui.main.MainActivity
data class CategoriesTab(
private val isManga: Boolean = false,
) : Tab {
override val options: TabOptions
@Composable
get() {
val isSelected = LocalTabNavigator.current.current.key == key
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter)
return TabOptions(
index = 7u,
title = stringResource(R.string.general_categories),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
@Composable
override fun Content() {
val context = LocalContext.current
TabbedScreen(
titleRes = R.string.general_categories,
tabs = listOf(
animeCategoryTab(),
mangaCategoryTab(),
),
startIndex = 1.takeIf { isManga },
)
LaunchedEffect(Unit) {
(context as? MainActivity)?.ready = true
}
}
}

Some files were not shown because too many files have changed in this diff Show more