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>>> {
return combine(
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.disabledAnimeSources().changes(),
repository.getOnlineSources(),
) { enabledLanguage, disabledSource, onlineSources ->
val sortedSources = onlineSources.sortedWith(

View file

@ -1,8 +1,8 @@
package eu.kanade.domain.episode.model
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.manga.model.TriStateFilter
import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
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 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
// region Badges

View file

@ -42,5 +42,5 @@ class SourcePreferences(
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.WorkManager
import androidx.work.WorkerParameters
import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
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.ui.anime.AnimeScreenState
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.api.get
@ -252,6 +252,7 @@ private fun AnimeScreenSmallImpl(
onClickDownload = onDownloadActionClicked,
onClickEditCategory = onEditCategoryClicked,
onClickMigrate = onMigrateClicked,
changeAnimeSkipIntro = changeAnimeSkipIntro,
actionModeCounter = episodes.count { it.selected },
onSelectAll = { onAllEpisodeSelected(true) },
onInvertSelection = { onInvertSelection() },

View file

@ -2,7 +2,6 @@ package eu.kanade.presentation.anime
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
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.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
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.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.TrackLogoIcon
import eu.kanade.presentation.components.VerticalDivider
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.slideInVertically
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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.more.settings.widget.SwitchPreferenceWidget
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
@Composable
fun AnimeExtensionFilterScreen(
navigateUp: () -> Unit,
state: AnimeExtensionFilterState.Success,
state: ExtensionFilterState.Success,
onClickToggle: (String) -> Unit,
) {
Scaffold(
@ -31,13 +31,13 @@ fun AnimeExtensionFilterScreen(
)
},
) { contentPadding ->
if (state.isEmpty) {
EmptyScreen(
textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding),
)
return@Scaffold
}
if (state.isEmpty) {
EmptyScreen(
textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding),
)
return@Scaffold
}
AnimeExtensionFilterContent(
contentPadding = contentPadding,
state = state,
@ -49,7 +49,7 @@ fun AnimeExtensionFilterScreen(
@Composable
private fun AnimeExtensionFilterContent(
contentPadding: PaddingValues,
state: AnimeExtensionFilterState.Success,
state: ExtensionFilterState.Success,
onClickLang: (String) -> Unit,
) {
val context = LocalContext.current

View file

@ -83,7 +83,7 @@ private fun AnimeSourcesFilterContent(
AnimeSourcesFilterItem(
modifier = Modifier.animateItemPlacement(),
source = source,
enabled = "${source.id}" !in state.disabledSources,
isEnabled = "${source.id}" !in state.disabledSources,
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.browse.BrowseAnimeSourceScreenModel.Listing
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@Composable
fun AnimeSourcesScreen(

View file

@ -19,8 +19,8 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R
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.GlobalAnimeSearchState
import eu.kanade.tachiyomi.util.system.LocaleHelper
@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.EmptyScreen
import eu.kanade.presentation.components.FastScrollLazyColumn
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
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.Scaffold
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.migration.search.MigrateAnimeSearchState
import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable

View file

@ -39,12 +39,12 @@ import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.secondaryItemAlpha
import eu.kanade.presentation.util.topSmallPaddingValues
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
@Composable
fun MigrateAnimeSourceScreen(
state: MigrateAnimeSourcesState,
state: MigrateAnimeSourceState,
contentPadding: PaddingValues,
onClickItem: (AnimeSource) -> Unit,
onToggleSortingDirection: () -> Unit,

View file

@ -13,8 +13,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import eu.kanade.domain.anime.model.Anime
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.components.BrowseSourceLoadingItem
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaComfortableGridItem
import eu.kanade.presentation.util.plus

View file

@ -13,8 +13,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import eu.kanade.domain.anime.model.Anime
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.components.BrowseSourceLoadingItem
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.MangaCompactGridItem
import eu.kanade.presentation.util.plus

View file

@ -10,8 +10,8 @@ import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
import eu.kanade.domain.anime.model.Anime
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.components.BrowseSourceLoadingItem
import eu.kanade.presentation.components.CommonMangaItemDefaults
import eu.kanade.presentation.components.LazyColumn
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.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.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.animehistory.model.AnimeHistoryWithRelations
import eu.kanade.presentation.animehistory.components.AnimeHistoryContent
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryScreenModel
import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryState
import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryScreenModel
import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryState
import java.util.Date
@Composable
fun AnimeHistoryScreen(
state: AnimeHistoryState,
contentPadding: PaddingValues,
snackbarHostState: SnackbarHostState,
onSearchQueryChange: (String?) -> Unit,
onClickCover: (animeId: Long) -> Unit,
onClickResume: (animeId: Long, episodeId: Long) -> Unit,
onDialogChange: (AnimeHistoryScreenModel.Dialog?) -> Unit,
) {
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) },
) { contentPadding ->
) { _ ->
state.list.let {
if (it == null) {
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.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember

View file

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

View file

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

View file

@ -1,18 +1,11 @@
package eu.kanade.presentation.animeupdates
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
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.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.PullRefresh
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.updates.updatesLastUpdatedItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesItem
import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesState
import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem
import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
@ -46,6 +38,7 @@ import kotlin.time.Duration.Companion.seconds
fun AnimeUpdateScreen(
state: AnimeUpdatesState,
snackbarHostState: SnackbarHostState,
contentPadding: PaddingValues,
lastUpdated: Long,
relativeTime: Int,
onClickCover: (AnimeUpdatesItem) -> Unit,
@ -54,10 +47,10 @@ fun AnimeUpdateScreen(
onUpdateLibrary: () -> Boolean,
onDownloadEpisode: (List<AnimeUpdatesItem>, EpisodeDownloadAction) -> Unit,
onMultiBookmarkClicked: (List<AnimeUpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<AnimeUpdatesItem>, read: Boolean) -> Unit,
onMultiMarkAsSeenClicked: (List<AnimeUpdatesItem>, seen: Boolean) -> Unit,
onMultiDeleteClicked: (List<AnimeUpdatesItem>) -> 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) })
@ -69,13 +62,13 @@ fun AnimeUpdateScreen(
selected = state.selected,
onDownloadEpisode = onDownloadEpisode,
onMultiBookmarkClicked = onMultiBookmarkClicked,
onMultiMarkAsSeenClicked = onMultiMarkAsReadClicked,
onMultiMarkAsSeenClicked = onMultiMarkAsSeenClicked,
onMultiDeleteClicked = onMultiDeleteClicked,
onOpenEpisode = onOpenEpisode,
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding ->
) {
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.items.isEmpty() -> EmptyScreen(
@ -105,14 +98,14 @@ fun AnimeUpdateScreen(
contentPadding = contentPadding,
) {
if (lastUpdated > 0L) {
updatesLastUpdatedItem(lastUpdated)
animeupdatesLastUpdatedItem(lastUpdated)
}
animeupdatesUiItems(
uiModels = state.getUiModel(context, relativeTime),
selectionMode = state.selectionMode,
onUpdateSelected = onUpdateSelected,
onClickCover = onClickCover,
onClickUpdate = { onOpenEpisode },
onClickUpdate = onOpenEpisode,
onDownloadEpisode = onDownloadEpisode,
)
}
@ -129,10 +122,9 @@ private fun AnimeUpdatesBottomBar(
onMultiBookmarkClicked: (List<AnimeUpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsSeenClicked: (List<AnimeUpdatesItem>, seen: Boolean) -> Unit,
onMultiDeleteClicked: (List<AnimeUpdatesItem>) -> Unit,
onOpenEpisode: (List<AnimeUpdatesItem>, context: Context, altPlayer: Boolean) -> Unit,
onOpenEpisode: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit,
) {
val playerPreferences: PlayerPreferences = Injekt.get()
val context = LocalContext.current
AnimeBottomActionMenu(
visible = selected.isNotEmpty(),
modifier = Modifier.fillMaxWidth(),
@ -157,10 +149,10 @@ private fun AnimeUpdatesBottomBar(
onMultiDeleteClicked(selected)
}.takeIf { selected.fastAny { it.downloadStateProvider() == AnimeDownload.State.DOWNLOADED } },
onExternalClicked = {
onOpenEpisode(selected, context, true)
onOpenEpisode(selected[0], true)
}.takeIf { !playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 },
onInternalClicked = {
onOpenEpisode(selected, context,false)
onOpenEpisode(selected[0], true)
}.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.tachiyomi.R
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 kotlin.time.Duration.Companion.minutes
@ -82,7 +82,7 @@ fun LazyListScope.animeupdatesUiItems(
selectionMode: Boolean,
onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onClickCover: (AnimeUpdatesItem) -> Unit,
onClickUpdate: (AnimeUpdatesItem) -> Unit,
onClickUpdate: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit,
onDownloadEpisode: (List<AnimeUpdatesItem>, EpisodeDownloadAction) -> Unit,
) {
items(
@ -119,7 +119,7 @@ fun LazyListScope.animeupdatesUiItems(
onClick = {
when {
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
else -> onClickUpdate(updatesItem)
else -> onClickUpdate(updatesItem, false)
}
},
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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.components.CategoryContent
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.plus
import eu.kanade.presentation.util.topSmallPaddingValues
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreenState
import eu.kanade.tachiyomi.ui.category.anime.AnimeCategoryScreenState
@Composable
fun AnimeCategoryScreen(
state: AnimeCategoryScreenState.Success,
contentPadding: PaddingValues,
onClickCreate: () -> Unit,
onClickRename: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit,
navigateUp: () -> Unit,
) {
val lazyListState = rememberLazyListState()
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.action_edit_categories),
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
floatingActionButton = {
CategoryFloatingActionButton(
lazyListState = lazyListState,
onCreate = onClickCreate,
)
},
) { paddingValues ->
) {
if (state.isEmpty) {
EmptyScreen(
textResource = R.string.information_empty_category,
modifier = Modifier.padding(paddingValues),
modifier = Modifier.padding(contentPadding),
)
return@Scaffold
}
@ -56,7 +47,7 @@ fun AnimeCategoryScreen(
CategoryContent(
categories = state.categories,
lazyListState = lazyListState,
paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename,
onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp,

View file

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

View file

@ -1,9 +1,12 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@ -30,6 +33,7 @@ import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@ -149,22 +153,41 @@ fun AppBar(
fun AppBarTitle(
title: String?,
subtitle: String? = null,
count: Int = 0,
) {
Column {
title?.let {
if (count > 0) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = it,
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,
)
}
subtitle?.let {
Text(
text = it,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
} else {
Column {
title?.let {
Text(
text = it,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
subtitle?.let {
Text(
text = it,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
@ -317,8 +340,6 @@ fun SearchToolbar(
key("actions") { actions() }
},
isActionMode = false,
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
onCancelActionMode = cancelAction,
)

View file

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

View file

@ -2,55 +2,32 @@ package eu.kanade.presentation.history
import androidx.compose.foundation.layout.PaddingValues
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.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.history.components.HistoryContent
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import eu.kanade.tachiyomi.ui.history.HistoryState
import eu.kanade.tachiyomi.ui.history.manga.HistoryState
import eu.kanade.tachiyomi.ui.history.manga.MangaHistoryScreenModel
import java.util.Date
@Composable
fun HistoryScreen(
state: HistoryState,
contentPadding: PaddingValues,
snackbarHostState: SnackbarHostState,
onSearchQueryChange: (String?) -> Unit,
onClickCover: (mangaId: Long) -> Unit,
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
onDialogChange: (MangaHistoryScreenModel.Dialog?) -> Unit,
) {
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) },
) { contentPadding ->
) { _ ->
state.list.let {
if (it == null) {
LoadingScreen(modifier = Modifier.padding(contentPadding))
@ -70,7 +47,7 @@ fun HistoryScreen(
contentPadding = contentPadding,
onClickCover = { history -> onClickCover(history.mangaId) },
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
.clickable(onClick = onClickResume)
.height(HISTORY_ITEM_HEIGHT)
.padding(horizontal = horizontalPadding, vertical = 8.dp),
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
verticalAlignment = Alignment.CenterVertically,
) {
MangaCover.Book(
@ -113,7 +113,7 @@ fun AnimeHistoryItem(
Column(
modifier = Modifier
.weight(1f)
.padding(start = horizontalPadding, end = 8.dp),
.padding(start = MaterialTheme.padding.medium, end = MaterialTheme.padding.small),
) {
val textStyle = MaterialTheme.typography.bodyMedium
Text(

View file

@ -98,7 +98,7 @@ fun TrackChapterSelector(
onDismissRequest: () -> Unit,
isAnime: Boolean,
) {
val titleText = when(isAnime) {
val titleText = when (isAnime) {
true -> R.string.chapters
false -> R.string.episodes
}

View file

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

View file

@ -199,8 +199,10 @@ object SettingsAdvancedScreen : SearchableSettings {
Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_invalidate_download_cache),
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
onClick = { Injekt.get<DownloadCache>().invalidateCache()
Injekt.get<AnimeDownloadCache>().invalidateCache() },
onClick = {
Injekt.get<DownloadCache>().invalidateCache()
Injekt.get<AnimeDownloadCache>().invalidateCache()
},
),
Preference.PreferenceItem.TextPreference(
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(
title = stringResource(R.string.pref_category_nsfw_content),
preferenceItems = listOf(

View file

@ -103,6 +103,7 @@ object SettingsDownloadScreen : SearchableSettings {
}
val defaultDirPair = rememberDefaultDownloadDir()
val externalDownloaderDirPair = rememberExternalDownloaderDownloadDir()
val customDirEntryKey = currentDir.takeIf { it != defaultDirPair.first } ?: "custom"
return Preference.PreferenceItem.ListPreference(
@ -115,6 +116,7 @@ object SettingsDownloadScreen : SearchableSettings {
},
entries = mapOf(
defaultDirPair,
externalDownloaderDirPair,
customDirEntryKey to stringResource(R.string.custom_dir),
),
onValueChanged = {
@ -129,6 +131,20 @@ object SettingsDownloadScreen : SearchableSettings {
@Composable
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)
return remember {
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.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.category.interactor.GetAnimeCategories
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.ResetCategoryFlags
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_READ
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.ui.category.CategoriesTab
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
@ -155,7 +154,7 @@ object SettingsLibraryScreen : SearchableSettings {
count = userAnimeCategoriesCount,
userAnimeCategoriesCount,
),
onClick = { navigator.push(AnimeCategoryScreen()) },
onClick = { navigator.push(CategoriesTab(false)) },
),
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultAnimeCategory(),
@ -170,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings {
count = userCategoriesCount,
userCategoriesCount,
),
onClick = { navigator.push(CategoryScreen()) },
onClick = { navigator.push(CategoriesTab(true)) },
),
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultCategory(),

View file

@ -6,10 +6,7 @@ import android.os.Build
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@ -23,14 +20,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
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.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
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 uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -124,7 +120,7 @@ object SettingsPlayerScreen : SearchableSettings {
@Composable
private fun getInternalPlayerGroup(playerPreferences: PlayerPreferences, basePreferences: BasePreferences): Preference.PreferenceGroup {
val scope = rememberCoroutineScope()
val defaultSkipIntroLength by playerPreferences.skipLengthPreference().stateIn(scope).collectAsState()
val defaultSkipIntroLength by playerPreferences.defaultIntroLength().stateIn(scope).collectAsState()
val skipLengthPreference = playerPreferences.skipLengthPreference()
val playerSmoothSeek = playerPreferences.playerSmoothSeek()
val playerFullscreen = playerPreferences.playerFullscreen()
@ -142,7 +138,7 @@ object SettingsPlayerScreen : SearchableSettings {
initialSkipIntroLength = defaultSkipIntroLength,
onDismissRequest = { showDialog = false },
onValueChanged = { skipIntroLength ->
playerPreferences.skipLengthPreference().set(skipIntroLength)
playerPreferences.defaultIntroLength().set(skipIntroLength)
showDialog = false
},
)
@ -290,15 +286,14 @@ object SettingsPlayerScreen : SearchableSettings {
)
}
// TODO: chnage to new wheel Picker
@Composable
private fun SkipIntroLengthDialog(
initialSkipIntroLength: Int,
onDismissRequest: () -> Unit,
onValueChanged: (skipIntroLength: Int) -> Unit,
) {
var skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) }
val skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) }
var newLength = 0
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(R.string.pref_intro_length)) },
@ -308,16 +303,13 @@ object SettingsPlayerScreen : SearchableSettings {
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
NumberPicker(
modifier = Modifier
.fillMaxWidth()
.clipToBounds(),
value = skipIntroLengthValue,
onValueChange = { skipIntroLengthValue = it },
range = 1..255,
label = { it.toString() },
dividersColor = MaterialTheme.colorScheme.primary,
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface),
WheelTextPicker(
texts = remember { 1..255 }.map { "$it" },
onScrollFinished = {
newLength = it
null
},
startIndex = skipIntroLengthValue,
)
}
}
@ -328,7 +320,7 @@ object SettingsPlayerScreen : SearchableSettings {
}
},
confirmButton = {
TextButton(onClick = { onValueChanged(skipIntroLengthValue) }) {
TextButton(onClick = { onValueChanged(newLength) }) {
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
@Composable
fun StatsScreenContent(
state: StatsScreenState.Success,
fun MangaStatsScreenContent(
state: StatsScreenState.SuccessManga,
paddingValues: PaddingValues,
) {
val statListState = rememberLazyListState()
@ -53,7 +53,7 @@ fun StatsScreenContent(
@Composable
private fun OverviewSection(
data: StatsData.Overview,
data: StatsData.MangaOverview,
) {
val none = stringResource(R.string.none)
val context = LocalContext.current
@ -85,7 +85,7 @@ private fun OverviewSection(
@Composable
private fun TitlesStats(
data: StatsData.Titles,
data: StatsData.MangaTitles,
) {
StatsSection(R.string.label_titles_section) {
Row {

View file

@ -8,10 +8,18 @@ sealed class StatsScreenState {
object Loading : StatsScreenState()
@Immutable
data class Success(
val overview: StatsData.Overview,
val titles: StatsData.Titles,
data class SuccessManga(
val overview: StatsData.MangaOverview,
val titles: StatsData.MangaTitles,
val chapters: StatsData.Chapters,
val trackers: StatsData.Trackers,
) : 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 {
data class Overview(
data class MangaOverview(
val libraryMangaCount: Int,
val completedMangaCount: Int,
val totalReadDuration: Long,
) : StatsData()
data class Titles(
data class AnimeOverview(
val libraryAnimeCount: Int,
val completedAnimeCount: Int,
val totalSeenDuration: Long,
) : StatsData()
data class MangaTitles(
val globalUpdateItemCount: Int,
val startedMangaCount: Int,
val localMangaCount: Int,
) : StatsData()
data class AnimeTitles(
val globalUpdateItemCount: Int,
val startedAnimeCount: Int,
val localAnimeCount: Int,
) : StatsData()
data class Chapters(
val totalChapterCount: Int,
val readChapterCount: Int,
val downloadCount: Int,
) : StatsData()
data class Episodes(
val totalEpisodeCount: Int,
val readEpisodeCount: Int,
val downloadCount: Int,
) : StatsData()
data class Trackers(
val trackedTitleCount: Int,
val meanScore: Double,

View file

@ -1,17 +1,11 @@
package eu.kanade.presentation.updates
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
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.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -31,8 +25,8 @@ import eu.kanade.presentation.components.PullRefresh
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.UpdatesState
import eu.kanade.tachiyomi.ui.updates.manga.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.manga.UpdatesState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
@ -41,6 +35,7 @@ import kotlin.time.Duration.Companion.seconds
fun UpdateScreen(
state: UpdatesState,
snackbarHostState: SnackbarHostState,
contentPadding: PaddingValues,
lastUpdated: Long,
relativeTime: Int,
onClickCover: (UpdatesItem) -> Unit,
@ -69,7 +64,7 @@ fun UpdateScreen(
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding ->
) {
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
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.tachiyomi.R
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 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.NetworkPreferences
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.util.system.isDevFlavor
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
@ -194,6 +195,8 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { ImageSaver(app) }
addSingletonFactory { ExternalIntents() }
// Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute {
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.network.NetworkPreferences
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.ReaderPreferences
import eu.kanade.tachiyomi.util.preference.minusAssign

View file

@ -60,7 +60,6 @@ class AnimeDownloadCache(
.onStart { emit(Unit) }
.shareIn(scope, SharingStarted.Eagerly, 1)
/**
* 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.
@ -272,38 +271,38 @@ class AnimeDownloadCache(
}?.id
}
sourceDirs.values
.map { sourceDir ->
async {
val animeDirs = sourceDir.dir.listFiles().orEmpty()
.filterNot { it.name.isNullOrBlank() }
.associate { it.name!! to AnimeDirectory(it) }
sourceDirs.values
.map { sourceDir ->
async {
val animeDirs = sourceDir.dir.listFiles().orEmpty()
.filterNot { it.name.isNullOrBlank() }
.associate { it.name!! to AnimeDirectory(it) }
sourceDir.animeDirs = ConcurrentHashMap(animeDirs)
sourceDir.animeDirs = ConcurrentHashMap(animeDirs)
animeDirs.values.forEach { animeDir ->
val episodeDirs = animeDir.dir.listFiles().orEmpty()
.mapNotNull {
when {
// Ignore incomplete downloads
it.name?.endsWith(AnimeDownloader.TMP_DIR_SUFFIX) == true -> null
// Folder of images
it.isDirectory -> it.name
// MP4 files
it.isFile && it.name?.endsWith(".mp4") == true -> it.name!!.substringBeforeLast(".mp4")
// MKV files
it.isFile && it.name?.endsWith(".mkv") == true -> it.name!!.substringBeforeLast(".mkv")
// Anything else is irrelevant
else -> null
}
animeDirs.values.forEach { animeDir ->
val episodeDirs = animeDir.dir.listFiles().orEmpty()
.mapNotNull {
when {
// Ignore incomplete downloads
it.name?.endsWith(AnimeDownloader.TMP_DIR_SUFFIX) == true -> null
// Folder of images
it.isDirectory -> it.name
// MP4 files
it.isFile && it.name?.endsWith(".mp4") == true -> it.name!!.substringBeforeLast(".mp4")
// MKV files
it.isFile && it.name?.endsWith(".mkv") == true -> it.name!!.substringBeforeLast(".mkv")
// Anything else is irrelevant
else -> null
}
.toMutableSet()
}
.toMutableSet()
animeDir.episodeDirs = episodeDirs
}
animeDir.episodeDirs = episodeDirs
}
}
.awaitAll()
}
.awaitAll()
}.also {
it.invokeOnCompletion(onCancelling = true) { exception ->
if (exception != null && exception !is CancellationException) {

View file

@ -93,7 +93,6 @@ class AnimeDownloadManager(
return queue.find { it.episode.id == episodeId }
}
fun startDownloadNow(episodeId: Long?) {
if (episodeId == null) return
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.withUIContext
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.toFFmpegString
import eu.kanade.tachiyomi.util.system.ImageUtil
@ -346,7 +345,7 @@ class AnimeDownloader(
val videoObservable = if (download.video == null) {
// 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 ->
if (video == null) {
throw Exception(context.getString(R.string.video_list_empty_error))
@ -458,13 +457,13 @@ class AnimeDownloader(
video.videoUrl = file.uri.path
video.progress = 100
download.downloadedImages++
video.status = Video.READY
video.status = Video.State.READY
}
.map { video }
// Mark this video as error and allow to download the remaining
.onErrorReturn {
video.progress = 0
video.status = Video.ERROR
video.status = Video.State.ERROR
notifier.onError(it.message, download.episode.name, download.anime.title)
video
}
@ -479,7 +478,7 @@ class AnimeDownloader(
* @param filename the filename of the video.
*/
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
var tries = 0
return newObservable(video, download, tmpDir, filename)
@ -632,7 +631,7 @@ class AnimeDownloader(
* @param filename the filename of the video.
*/
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
return Observable.just(tmpDir.createFile("$filename.mp4")).map {
try {

View file

@ -4,7 +4,7 @@ import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
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.source.SourceManager
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_TRACK
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.full.models.BackupAnime
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeSource
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
import eu.kanade.tachiyomi.data.backup.full.models.BooleanPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.FloatPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.IntPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.backup.full.models.backupAnimeTrackMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupCategoryMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupChapterMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupEpisodeMapper
import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupAnime
import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory
import eu.kanade.tachiyomi.data.backup.models.BackupAnimeSource
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
import eu.kanade.tachiyomi.data.backup.models.backupAnimeTrackMapper
import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper
import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
import eu.kanade.tachiyomi.data.backup.models.backupEpisodeMapper
import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Chapter

View file

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

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 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 kotlinx.serialization.Serializable

View file

@ -29,7 +29,6 @@ interface Anime : SAnime {
var skipIntroLength: Int
get() = viewer_flags and 0x000000FF
set(skipIntro) = setViewerFlags(skipIntro, 0x000000FF)
}
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.tachiyomi.animesource.AnimeSource
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.

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.json.Json
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.track.model.Track as DomainTrack
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")
}
}
@Serializable
data class OAuth(
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.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.Constants
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.system.dpToPx
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.res.stringResource
import androidx.core.net.toUri
import cafe.adriel.voyager.core.model.coroutineScope
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.uniqueScreenKey
@ -27,35 +26,35 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.commandiron.wheel_picker_compose.WheelTextPicker
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.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.DuplicateAnimeDialog
import eu.kanade.presentation.components.LoadingScreen
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.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.isTabletUi
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.isLocalOrStub
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.ui.anime.track.AnimeTrackInfoDialogHomeScreen
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen
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.anime.track.AnimeTrackInfoDialogHomeScreen
import eu.kanade.tachiyomi.ui.player.ExternalIntents
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.util.lang.launchIO
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.toast
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimeScreen(
@ -116,7 +112,12 @@ class AnimeScreen(
snackbarHostState = screenModel.snackbarHostState,
isTabletUi = isTabletUi(),
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() },
onAddToLibraryClicked = {
screenModel.toggleFavorite()
@ -128,14 +129,19 @@ class AnimeScreen(
onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
onFilterButtonClicked = screenModel::showSettingsDialog,
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) } },
onCoverClicked = screenModel::showCoverDialog,
onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource },
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
onEditCategoryClicked = screenModel::promptChangeCategories.takeIf { successState.anime.favorite },
onMigrateClicked = { navigator.push(MigrateAnimeSearchScreen(successState.anime.id)) }.takeIf { successState.anime.favorite },
changeAnimeSkipIntro = {screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite }},
changeAnimeSkipIntro = { screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite } },
onMultiBookmarkClicked = screenModel::bookmarkEpisodes,
onMultiMarkAsSeenClicked = screenModel::markEpisodesSeen,
onMarkPreviousAsSeenClicked = screenModel::markPreviousEpisodeSeen,
@ -152,7 +158,7 @@ class AnimeScreen(
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = { navigator.push(AnimeCategoryScreen()) },
onEditCategories = { navigator.push(CategoriesTab(false)) },
onConfirm = { 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)
}
private fun openEpisodeInternal(context: Context, animeId: Long, episodeId: Long) {
context.startActivity(PlayerActivity.newIntent(context, animeId, episodeId))
}
// TODO: External Player support
private fun openEpisode(context: Context, episode: Episode, alt: Boolean = false) {
context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id))
private suspend fun openEpisodeExternal(context: Context, animeId: Long, episodeId: Long) {
context.startActivity(ExternalIntents.newIntent(context, animeId, episodeId))
}
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? {
@ -347,6 +364,7 @@ fun ChangeIntroLength(
anime: Anime,
onDismissRequest: () -> Unit,
) {
val scope = rememberCoroutineScope()
val setAnimeViewerFlags: SetAnimeViewerFlags by injectLazy()
val titleText = R.string.action_change_intro_length
var newLength = 0
@ -364,10 +382,12 @@ fun ChangeIntroLength(
)
},
onConfirm = {
launchIO {
scope.launchIO {
setAnimeViewerFlags.awaitSetSkipIntroLength(anime.id, newLength.toLong())
onDismissRequest()
}},
}
Unit
},
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.UpdateAnime
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.animetrack.interactor.GetAnimeTracks
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.EpisodeUpdate
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.ui.UiPreferences
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.AnimeDownloadService
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.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
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.shouldDownloadNewEpisodes
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
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 selectedEpisodeIds: HashSet<Long> = HashSet()
/**
* 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()) {
showTrackDialog()
}
}
},
)
}
/**
* Update favorite status of anime, (removes / adds) anime (to / from) library.
*/
@ -610,7 +609,6 @@ class AnimeInfoScreenModel(
}
}
fun runEpisodeDownloadActions(
items: List<EpisodeItem>,
action: EpisodeDownloadAction,
@ -789,7 +787,7 @@ class AnimeInfoScreenModel(
TriStateFilter.ENABLED_NOT -> Anime.EPISODE_SHOW_NOT_BOOKMARKED
}
coroutineScope.launchNonCancellable {
coroutineScope.launchNonCancellable {
setAnimeEpisodeFlags.awaitSetBookmarkFilter(anime, flag)
}
}
@ -829,7 +827,6 @@ class AnimeInfoScreenModel(
}
}
fun toggleSelection(
item: EpisodeItem,
selected: Boolean,
@ -841,7 +838,7 @@ class AnimeInfoScreenModel(
val selectedIndex = successState.processedEpisodes.indexOfFirst { it.episode.id == item.episode.id }
if (selectedIndex < 0) return@apply
val selectedItem = get(selectedIndex )
val selectedItem = get(selectedIndex)
if ((selectedItem.selected && selected) || (!selectedItem.selected && !selected)) return@apply
val firstSelection = none { it.selected }
@ -855,10 +852,10 @@ class AnimeInfoScreenModel(
} else {
// Try to select the items in-between when possible
val range: IntRange
if (selectedIndex < selectedPositions[0]) {
range = selectedIndex + 1 until selectedPositions[0]
if (selectedIndex < selectedPositions[0]) {
range = selectedIndex + 1 until selectedPositions[0]
selectedPositions[0] = selectedIndex
} else if (selectedIndex > selectedPositions[1]) {
} else if (selectedIndex > selectedPositions[1]) {
range = (selectedPositions[1] + 1) until selectedIndex
selectedPositions[1] = selectedIndex
} else {
@ -876,15 +873,15 @@ class AnimeInfoScreenModel(
}
} else if (userSelected && !fromLongPress) {
if (!selected) {
if (selectedIndex == selectedPositions[0]) {
if (selectedIndex == selectedPositions[0]) {
selectedPositions[0] = indexOfFirst { it.selected }
} else if (selectedIndex == selectedPositions[1]) {
} else if (selectedIndex == selectedPositions[1]) {
selectedPositions[1] = indexOfLast { it.selected }
}
} else {
if (selectedIndex < selectedPositions[0]) {
if (selectedIndex < selectedPositions[0]) {
selectedPositions[0] = selectedIndex
} else if (selectedIndex > selectedPositions[1]) {
} else if (selectedIndex > selectedPositions[1]) {
selectedPositions[1] = selectedIndex
}
}

View file

@ -37,7 +37,6 @@ import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
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.GetAnimeWithEpisodes
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.model.toDbTrack
import eu.kanade.domain.animetrack.model.toDomainTrack
import eu.kanade.domain.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.anime.AnimeTrackInfoDialogHome
import eu.kanade.presentation.anime.AnimeTrackServiceSearch
import eu.kanade.presentation.components.AlertDialogContent
import eu.kanade.presentation.manga.TrackChapterSelector
import eu.kanade.presentation.manga.TrackDateSelector
import eu.kanade.presentation.manga.TrackScoreSelector
import eu.kanade.presentation.anime.AnimeTrackServiceSearch
import eu.kanade.presentation.manga.TrackStatusSelector
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
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.util.lang.launchNonCancellable
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.fastMapNotNull
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.category.interactor.GetAnimeCategories
import eu.kanade.domain.category.interactor.SetAnimeCategories
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId
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.sort
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.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.model.SAnime
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.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
@ -337,11 +338,11 @@ class AnimelibScreenModel(
libraryPreferences.languageBadge().changes(),
preferences.downloadedOnly().changes(),
libraryPreferences.filterDownloaded().changes(),
libraryPreferences.filterUnread().changes(),
libraryPreferences.filterStarted().changes(),
libraryPreferences.filterBookmarked().changes(),
libraryPreferences.filterCompleted().changes(),
libraryPreferences.filterDownloadedAnime().changes(),
libraryPreferences.filterUnseen().changes(),
libraryPreferences.filterStartedAnime().changes(),
libraryPreferences.filterBookmarkedAnime().changes(),
libraryPreferences.filterCompletedAnime().changes(),
transform = {
ItemPreferences(
downloadBadge = it[0] as Boolean,
@ -406,10 +407,10 @@ class AnimelibScreenModel(
* @return map of track id with the filter value
*/
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()) {
val prefFlows = loggedServices
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
.map { libraryPreferences.filterTrackingAnime(it.id.toInt()).changes() }
.toTypedArray()
combine(*prefFlows) {
loggedServices

View file

@ -126,15 +126,15 @@ class AnimelibSettingsSheet(
downloaded.state = State.INCLUDE.value
downloaded.enabled = false
} else {
downloaded.state = libraryPreferences.filterDownloaded().get()
downloaded.state = libraryPreferences.filterDownloadedAnime().get()
}
unseen.state = libraryPreferences.filterUnread().get()
started.state = libraryPreferences.filterStarted().get()
bookmarked.state = libraryPreferences.filterBookmarked().get()
completed.state = libraryPreferences.filterCompleted().get()
unseen.state = libraryPreferences.filterUnseen().get()
started.state = libraryPreferences.filterStartedAnime().get()
bookmarked.state = libraryPreferences.filterBookmarkedAnime().get()
completed.state = libraryPreferences.filterCompletedAnime().get()
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
when (item) {
downloaded -> libraryPreferences.filterDownloaded().set(newState)
unseen -> libraryPreferences.filterUnread().set(newState)
started -> libraryPreferences.filterStarted().set(newState)
bookmarked -> libraryPreferences.filterBookmarked().set(newState)
completed -> libraryPreferences.filterCompleted().set(newState)
downloaded -> libraryPreferences.filterDownloadedAnime().set(newState)
unseen -> libraryPreferences.filterUnseen().set(newState)
started -> libraryPreferences.filterStartedAnime().set(newState)
bookmarked -> libraryPreferences.filterBookmarkedAnime().set(newState)
completed -> libraryPreferences.filterCompletedAnime().set(newState)
else -> {
trackFilters.forEach { trackFilter ->
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
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.animation.graphics.res.animatedVectorResource
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.tab.LocalTabNavigator
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.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.DeleteAnimelibAnimeDialog
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.EmptyScreenAction
import eu.kanade.presentation.components.AnimelibBottomActionMenu
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.animelib.components.AnimelibContent
import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R
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.category.CategoryScreen
import eu.kanade.tachiyomi.ui.category.CategoriesTab
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.player.ExternalIntents
import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
@ -72,7 +76,7 @@ object AnimelibTab : Tab {
R.string.label_animelib
}
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(
index = 0u,
title = stringResource(title),
@ -108,6 +112,23 @@ object AnimelibTab : Tab {
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(
topBar = { scrollBehavior ->
val title = state.getToolbarTitle(
@ -183,13 +204,8 @@ object AnimelibTab : Tab {
onContinueWatchingClicked = { it: AnimelibAnime ->
scope.launchIO {
val episode = screenModel.getNextUnseenEpisode(it.anime)
if (episode != null) {
context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id))
} else {
snackbarHostState.showSnackbar(context.getString(R.string.no_next_episode))
}
if (episode != null) openEpisode(episode)
}
// TODO: External Intent
Unit
}.takeIf { state.showAnimeContinueButton },
onToggleSelection = { screenModel.toggleSelection(it) },
@ -217,7 +233,7 @@ object AnimelibTab : Tab {
onDismissRequest = onDismissRequest,
onEditCategories = {
screenModel.clearSelection()
navigator.push(CategoryScreen())
navigator.push(CategoriesTab(false))
},
onConfirm = { include, exclude ->
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 image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter)
return TabOptions(
index = 6u,
index = 3u,
title = stringResource(R.string.browse),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
@ -68,6 +68,7 @@ data class BrowseTab(
onChangeSearchQuery = extensionsScreenModel::search,
searchQueryAnime = animeExtensionsQuery,
onChangeSearchQueryAnime = animeExtensionsScreenModel::search,
scrollable = true,
)
// For local source

View file

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

View file

@ -21,189 +21,7 @@ data class AnimeExtensionDetailsScreen(
@Composable
override fun Content() {
val context = LocalContext.current
val screenModel = rememberScreenModel { AnimeEpackage eu.kanade.tachiyomi.ui.browse.extension.details
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 screenModel = rememberScreenModel { AnimeExtensionDetailsScreenModel(pkgName = pkgName, context = context) }
val state by screenModel.state.collectAsState()
if (state.isLoading) {

View file

@ -41,9 +41,10 @@ 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.animesource.ConfigurableAnimeSource
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.widget.TachiyomiTextInputEditText.Companion.setIncognito
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.coroutineScope
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.model.AnimeSource
import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.service.SourcePreferences
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
@ -27,7 +27,7 @@ class AnimeSourcesFilterScreenModel(
combine(
getLanguagesWithSources.subscribe(),
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.disabledAnimeSources().changes(),
) { a, b, c -> Triple(a, b, c) }
.catch { throwable ->
mutableState.update {

View file

@ -50,9 +50,9 @@ import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.LocalAnimeSource
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.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.category.CategoriesTab
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.Constants
import eu.kanade.tachiyomi.util.lang.launchIO
@ -242,7 +242,7 @@ data class BrowseAnimeSourceScreen(
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = { navigator.push(AnimeCategoryScreen()) },
onEditCategories = { navigator.push(CategoriesTab(false)) },
onConfirm = { include, _ ->
screenModel.changeAnimeFavorite(dialog.anime)
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.NetworkToLocalAnime
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.toAnimeUpdate
import eu.kanade.domain.anime.model.toDomainAnime
import eu.kanade.domain.animesource.interactor.GetRemoteAnime
import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack
@ -299,10 +299,12 @@ class BrowseAnimeSourceScreenModel(
// Choose a category
else -> {
val preselectedIds = getCategories.await(anime.id).map { it.id }
setDialog(Dialog.ChangeAnimeCategory(
anime,
categories.mapAsCheckboxState { it.id in preselectedIds },
))
setDialog(
Dialog.ChangeAnimeCategory(
anime,
categories.mapAsCheckboxState { it.id in preselectedIds },
),
)
}
}
}
@ -320,7 +322,7 @@ class BrowseAnimeSourceScreenModel(
insertTrack.await(track.toDomainTrack()!!)
val chapters = getEpisodeByAnimeId.await(anime.id)
syncEpisodesWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service.animeService )
syncEpisodesWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service.animeService)
}
} catch (e: Exception) {
logcat(

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.UpdateAnime
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.toDomainAnime
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.animeextension.AnimeExtensionManager
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
@ -42,7 +42,7 @@ abstract class AnimeSearchScreenModel<T>(
protected lateinit var extensionFilter: String
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> ->
compareBy<AnimeCatalogueSource>(
@ -95,8 +95,8 @@ abstract class AnimeSearchScreenModel<T>(
val enabledSources = getEnabledSources()
if (filter.isEmpty()) {
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get()
val pinnedSources = sourcePreferences.pinnedSources().get()
val shouldSearchPinnedOnly = sourcePreferences.searchPinnedAnimeSourcesOnly().get()
val pinnedSources = sourcePreferences.pinnedAnimeSources().get()
return enabledSources.filter {
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.currentOrThrow
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.browse.animesource.browse.BrowseAnimeSourceScreen
class GlobalAnimeSearchScreen(
val searchQuery: String = "",

View file

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

View file

@ -12,8 +12,8 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.animebrowse.MigrateAnimeScreen
import eu.kanade.presentation.components.LoadingScreen
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.browse.migration.search.MigrateAnimeSearchScreen
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
@ -38,7 +38,7 @@ data class MigrationAnimeScreen(
navigateUp = navigator::pop,
title = state.source!!.name,
state = state,
onClickItem = { navigator.push(MigrateSearchScreen(it.id)) },
onClickItem = { navigator.push(MigrateAnimeSearchScreen(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.coroutineScope
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.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.util.lang.launchIO
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.animesource.LocalAnimeSource
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.home.HomeScreen
import eu.kanade.tachiyomi.ui.anime.AnimeScreen
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.Constants
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.navigator.LocalNavigator
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.model.Anime
import eu.kanade.domain.anime.model.AnimeUpdate
import eu.kanade.domain.anime.model.hasCustomCover
import eu.kanade.domain.animetrack.interactor.GetAnimeTracks
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.SyncEpisodesWithSource
import eu.kanade.domain.episode.interactor.UpdateEpisode
import eu.kanade.domain.episode.model.toEpisodeUpdate
import eu.kanade.presentation.animebrowse.MigrateAnimeSearchScreen
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.AnimeSourceManager
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.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.browse.migration.AnimeMigrationFlags
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
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 cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.anime.interactor.GetAnime
import eu.kanade.domain.anime.model.Anime
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
@ -38,7 +38,7 @@ class MigrateAnimeSearchScreenModel(
}
val incognitoMode = preferences.incognitoMode()
val lastUsedSourceId = sourcePreferences.lastUsedSource()
val lastUsedSourceId = sourcePreferences.lastUsedAnimeSource()
override fun getEnabledSources(): List<AnimeCatalogueSource> {
val enabledLanguages = sourcePreferences.enabledLanguages().get()

View file

@ -25,7 +25,7 @@ fun Screen.migrateSourceTab(): TabContent {
val state by screenModel.state.collectAsState()
return TabContent(
titleRes = R.string.label_migration_manga,
titleRes = R.string.label_migration,
actions = listOf(
AppBar.Action(
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.online.HttpSource
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.webview.WebViewActivity
import eu.kanade.tachiyomi.util.Constants
@ -242,7 +242,7 @@ data class BrowseSourceScreen(
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = { navigator.push(CategoryScreen()) },
onEditCategories = { navigator.push(CategoriesTab(true)) },
onConfirm = { include, _ ->
screenModel.changeMangaFavorite(dialog.manga)
screenModel.moveMangaToCategories(dialog.manga, include)

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