mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 12:17:12 +03:00
parent
5f782440c6
commit
c6f81b7fda
114 changed files with 1946 additions and 1175 deletions
|
@ -40,7 +40,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||
|
||||
* Include version (More → About → Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Preview version is equal to the number of commits as seen on the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
|
|
|
@ -207,10 +207,6 @@
|
|||
android:name=".data.download.anime.AnimeDownloadService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.updater.AppUpdateService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".extension.manga.util.MangaExtensionInstallService"
|
||||
android:exported="false" />
|
||||
|
|
|
@ -85,10 +85,12 @@ import tachiyomi.domain.entries.anime.interactor.GetAnimeFavorites
|
|||
import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes
|
||||
import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.GetMangaByUrlAndSourceId
|
||||
import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags
|
||||
import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags
|
||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
||||
import tachiyomi.domain.entries.manga.interactor.GetAnimeByUrlAndSourceId
|
||||
import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.entries.manga.interactor.GetManga
|
||||
|
@ -112,12 +114,14 @@ import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory
|
|||
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapter
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByUrlAndMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisode
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByUrlAndAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags
|
||||
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||
|
@ -186,6 +190,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetAnimeFavorites(get()) }
|
||||
addFactory { GetLibraryAnime(get()) }
|
||||
addFactory { GetAnimeWithEpisodes(get(), get()) }
|
||||
addFactory { GetAnimeByUrlAndSourceId(get()) }
|
||||
addFactory { GetAnime(get()) }
|
||||
addFactory { GetNextEpisodes(get(), get(), get()) }
|
||||
addFactory { ResetAnimeViewerFlags(get()) }
|
||||
|
@ -202,6 +207,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetMangaFavorites(get()) }
|
||||
addFactory { GetLibraryManga(get()) }
|
||||
addFactory { GetMangaWithChapters(get(), get()) }
|
||||
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||
addFactory { GetManga(get()) }
|
||||
addFactory { GetNextChapters(get(), get(), get()) }
|
||||
addFactory { ResetMangaViewerFlags(get()) }
|
||||
|
@ -245,6 +251,7 @@ class DomainModule : InjektModule {
|
|||
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
||||
addFactory { GetEpisode(get()) }
|
||||
addFactory { GetEpisodeByAnimeId(get()) }
|
||||
addFactory { GetEpisodeByUrlAndAnimeId(get()) }
|
||||
addFactory { UpdateEpisode(get()) }
|
||||
addFactory { SetSeenStatus(get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbEpisode() }
|
||||
|
@ -253,6 +260,7 @@ class DomainModule : InjektModule {
|
|||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChapterByMangaId(get()) }
|
||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
|
|
|
@ -54,7 +54,7 @@ fun GlobalSearchResultItem(
|
|||
Text(text = subtitle)
|
||||
}
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
||||
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
||||
}
|
||||
}
|
||||
content()
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
|
@ -92,7 +91,7 @@ fun AnimeExtensionDetailsScreen(
|
|||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_faq_and_guides),
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onClickReadme,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
|
@ -80,7 +79,7 @@ fun BrowseAnimeSourceContent(
|
|||
listOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.local_source_help_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onLocalAnimeSourceHelpClick,
|
||||
),
|
||||
)
|
||||
|
@ -98,7 +97,7 @@ fun BrowseAnimeSourceContent(
|
|||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.label_help,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -56,7 +56,7 @@ fun BrowseAnimeSourceToolbar(
|
|||
actions = listOfNotNull(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.AutoMirrored.Filled.ViewList else Icons.Filled.ViewModule,
|
||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
||||
onClick = { selectingDisplayMode = true },
|
||||
),
|
||||
if (isLocalSource) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
|
@ -80,7 +79,7 @@ fun BrowseSourceContent(
|
|||
listOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.local_source_help_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onLocalSourceHelpClick,
|
||||
),
|
||||
)
|
||||
|
@ -98,7 +97,7 @@ fun BrowseSourceContent(
|
|||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.label_help,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
|
@ -93,7 +92,7 @@ fun ExtensionDetailsScreen(
|
|||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_faq_and_guides),
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = onClickReadme,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -56,7 +56,7 @@ fun BrowseMangaSourceToolbar(
|
|||
actions = listOfNotNull(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.AutoMirrored.Filled.ViewList else Icons.Filled.ViewModule,
|
||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
||||
onClick = { selectingDisplayMode = true },
|
||||
),
|
||||
if (isLocalSource) {
|
||||
|
|
|
@ -58,7 +58,7 @@ fun GlobalMangaSearchToolbar(
|
|||
)
|
||||
if (progress in 1..<total) {
|
||||
LinearProgressIndicator(
|
||||
progress = { progress / total.toFloat() },
|
||||
progress = progress / total.toFloat(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.fillMaxWidth(),
|
||||
|
|
|
@ -52,7 +52,7 @@ fun CategoryListItem(
|
|||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = "")
|
||||
Icon(imageVector = Icons.Outlined.Label, contentDescription = "")
|
||||
Text(
|
||||
text = category.name,
|
||||
modifier = Modifier
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.compose.foundation.text.KeyboardActions
|
|||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
|
@ -401,7 +400,7 @@ fun SearchToolbar(
|
|||
@Composable
|
||||
fun UpIcon(navigationIcon: ImageVector? = null) {
|
||||
val icon = navigationIcon
|
||||
?: Icons.AutoMirrored.Outlined.ArrowBack
|
||||
?: Icons.Outlined.ArrowBack
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||
|
|
|
@ -3,7 +3,7 @@ package eu.kanade.presentation.components
|
|||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
||||
import androidx.compose.material.icons.outlined.ArrowLeft
|
||||
import androidx.compose.material.icons.outlined.ArrowRight
|
||||
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
||||
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||
|
@ -84,7 +84,7 @@ fun NestedMenuItem(
|
|||
onClick = { nestedExpanded = true },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowRight,
|
||||
imageVector = if (isLtr) Icons.Outlined.ArrowRight else Icons.Outlined.ArrowLeft,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
import androidx.compose.material3.Surface
|
||||
|
@ -39,7 +38,7 @@ private fun WithActionPreview() {
|
|||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.getting_started_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -14,8 +14,8 @@ import androidx.compose.material3.HorizontalDivider
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -60,7 +60,7 @@ fun TabbedDialog(
|
|||
|
||||
Column {
|
||||
Row {
|
||||
PrimaryTabRow(
|
||||
TabRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
indicator = {
|
||||
|
|
|
@ -10,8 +10,8 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Tab
|
||||
|
@ -142,7 +142,7 @@ private fun FlexibleTabRow(
|
|||
block: @Composable () -> Unit,
|
||||
) {
|
||||
return if (scrollable) {
|
||||
PrimaryScrollableTabRow(
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
indicator = indicator,
|
||||
edgePadding = 13.dp,
|
||||
|
@ -150,7 +150,7 @@ private fun FlexibleTabRow(
|
|||
block()
|
||||
}
|
||||
} else {
|
||||
PrimaryTabRow(
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
indicator = indicator,
|
||||
) {
|
||||
|
|
|
@ -296,7 +296,7 @@ fun LibraryBottomActionMenu(
|
|||
) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_move_category),
|
||||
icon = Icons.AutoMirrored.Outlined.Label,
|
||||
icon = Icons.Outlined.Label,
|
||||
toConfirm = confirm[0],
|
||||
onLongClick = { onLongClickItem(0) },
|
||||
onClick = onChangeCategoryClicked,
|
||||
|
|
|
@ -145,7 +145,7 @@ private fun DownloadingIndicator(
|
|||
MaterialTheme.colorScheme.background
|
||||
}
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
progress = animatedProgress,
|
||||
modifier = IndicatorModifier,
|
||||
color = strokeColor,
|
||||
strokeWidth = IndicatorSize / 2,
|
||||
|
|
|
@ -144,7 +144,7 @@ private fun DownloadingIndicator(
|
|||
MaterialTheme.colorScheme.background
|
||||
}
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
progress = animatedProgress,
|
||||
modifier = IndicatorModifier,
|
||||
color = strokeColor,
|
||||
strokeWidth = IndicatorSize / 2,
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package eu.kanade.presentation.extensions
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
|
||||
/**
|
||||
* Launches request for [Manifest.permission.WRITE_EXTERNAL_STORAGE] permission
|
||||
*/
|
||||
@Composable
|
||||
fun DiskUtil.RequestStoragePermission() {
|
||||
val permissionState = rememberPermissionState(
|
||||
permission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
permissionState.launchPermissionRequest()
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -21,7 +21,7 @@ fun LibraryTabs(
|
|||
onTabItemClick: (Int) -> Unit,
|
||||
) {
|
||||
Column {
|
||||
PrimaryScrollableTabRow(
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
edgePadding = 0.dp,
|
||||
indicator = {
|
||||
|
|
|
@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||
import androidx.compose.material.icons.outlined.CloudOff
|
||||
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
||||
import androidx.compose.material.icons.outlined.GetApp
|
||||
|
@ -54,7 +52,7 @@ fun MoreScreen(
|
|||
onClickCategories: () -> Unit,
|
||||
onClickStats: () -> Unit,
|
||||
onClickStorage: () -> Unit,
|
||||
onClickBackupAndRestore: () -> Unit,
|
||||
onClickDataAndStorage: () -> Unit,
|
||||
onClickSettings: () -> Unit,
|
||||
onClickAbout: () -> Unit,
|
||||
) {
|
||||
|
@ -166,7 +164,7 @@ fun MoreScreen(
|
|||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.general_categories),
|
||||
icon = Icons.AutoMirrored.Outlined.Label,
|
||||
icon = Icons.Outlined.Label,
|
||||
onPreferenceClick = onClickCategories,
|
||||
)
|
||||
}
|
||||
|
@ -186,9 +184,9 @@ fun MoreScreen(
|
|||
}
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_backup),
|
||||
icon = Icons.Outlined.SettingsBackupRestore,
|
||||
onPreferenceClick = onClickBackupAndRestore,
|
||||
title = stringResource(R.string.label_data_storage),
|
||||
icon = Icons.Outlined.Storage,
|
||||
onPreferenceClick = onClickDataAndStorage,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -211,7 +209,7 @@ fun MoreScreen(
|
|||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_help),
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
|
||||
import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material.icons.outlined.NewReleases
|
||||
import androidx.compose.material.icons.outlined.OpenInNew
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -61,7 +61,7 @@ fun NewUpdateScreen(
|
|||
) {
|
||||
Text(text = stringResource(R.string.update_check_open))
|
||||
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = null)
|
||||
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,48 +195,12 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||
|
||||
@Composable
|
||||
private fun getDataGroup(): Preference.PreferenceGroup {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||
|
||||
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
||||
val episodeCache = remember { Injekt.get<EpisodeCache>() }
|
||||
var readableSizeSema by remember { mutableIntStateOf(0) }
|
||||
val readableSize = remember(readableSizeSema) { chapterCache.readableSize }
|
||||
val readableAnimeSize = remember(readableSizeSema) { episodeCache.readableSize }
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.label_data),
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.pref_clear_chapter_cache),
|
||||
subtitle = stringResource(
|
||||
R.string.used_cache_both,
|
||||
readableAnimeSize,
|
||||
readableSize,
|
||||
),
|
||||
onClick = {
|
||||
scope.launchNonCancellable {
|
||||
try {
|
||||
val deletedFiles = chapterCache.clear() + episodeCache.clear()
|
||||
withUIContext {
|
||||
context.toast(
|
||||
context.getString(R.string.cache_deleted, deletedFiles),
|
||||
)
|
||||
readableSizeSema++
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
withUIContext { context.toast(R.string.cache_delete_error) }
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = libraryPreferences.autoClearItemCache(),
|
||||
title = stringResource(R.string.pref_auto_clear_chapter_cache),
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.pref_invalidate_download_cache),
|
||||
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
|
||||
|
|
|
@ -31,21 +31,24 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.cache.EpisodeCache
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.util.lang.withUIContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.backup.service.BackupPreferences
|
||||
import tachiyomi.domain.backup.service.FLAG_CATEGORIES
|
||||
import tachiyomi.domain.backup.service.FLAG_CHAPTERS
|
||||
|
@ -54,6 +57,7 @@ import tachiyomi.domain.backup.service.FLAG_EXT_SETTINGS
|
|||
import tachiyomi.domain.backup.service.FLAG_HISTORY
|
||||
import tachiyomi.domain.backup.service.FLAG_SETTINGS
|
||||
import tachiyomi.domain.backup.service.FLAG_TRACK
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
|
@ -62,23 +66,63 @@ import tachiyomi.presentation.core.util.isScrolledToStart
|
|||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
object SettingsBackupScreen : SearchableSettings {
|
||||
object SettingsDataScreen : SearchableSettings {
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
@StringRes
|
||||
override fun getTitleRes() = R.string.label_backup
|
||||
override fun getTitleRes() = R.string.label_data_storage
|
||||
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val backupPreferences = Injekt.get<BackupPreferences>()
|
||||
|
||||
DiskUtil.RequestStoragePermission()
|
||||
PermissionRequestHelper.requestStoragePermission()
|
||||
|
||||
return listOf(
|
||||
getCreateBackupPref(),
|
||||
getRestoreBackupPref(),
|
||||
getAutomaticBackupGroup(backupPreferences = backupPreferences),
|
||||
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
|
||||
getDataGroup(backupPreferences = backupPreferences),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val backupIntervalPref = backupPreferences.backupInterval()
|
||||
val backupInterval by backupIntervalPref.collectAsState()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.label_backup),
|
||||
preferenceItems = listOf(
|
||||
// Manual actions
|
||||
getCreateBackupPref(),
|
||||
getRestoreBackupPref(),
|
||||
|
||||
// Automatic backups
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = backupIntervalPref,
|
||||
title = stringResource(R.string.pref_backup_interval),
|
||||
entries = mapOf(
|
||||
0 to stringResource(R.string.off),
|
||||
6 to stringResource(R.string.update_6hour),
|
||||
12 to stringResource(R.string.update_12hour),
|
||||
24 to stringResource(R.string.update_24hour),
|
||||
48 to stringResource(R.string.update_48hour),
|
||||
168 to stringResource(R.string.update_weekly),
|
||||
),
|
||||
onValueChanged = {
|
||||
BackupCreateJob.setupTask(context, it)
|
||||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = backupPreferences.numberOfBackups(),
|
||||
enabled = backupInterval != 0,
|
||||
title = stringResource(R.string.pref_backup_slots),
|
||||
entries = listOf(2, 3, 4, 5).associateWith { it.toString() },
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(R.string.backup_info)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -349,68 +393,48 @@ object SettingsBackupScreen : SearchableSettings {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun getAutomaticBackupGroup(
|
||||
backupPreferences: BackupPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
private fun getDataGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||
|
||||
val backupIntervalPref = backupPreferences.backupInterval()
|
||||
val backupInterval by backupIntervalPref.collectAsState()
|
||||
val backupDirPref = backupPreferences.backupsDirectory()
|
||||
val backupDir by backupDirPref.collectAsState()
|
||||
val pickBackupLocation = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
|
||||
val file = UniFile.fromUri(context, uri)
|
||||
backupDirPref.set(file.uri.toString())
|
||||
}
|
||||
}
|
||||
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
||||
val episodeCache = remember { Injekt.get<EpisodeCache>()}
|
||||
var readableSizeSema by remember { mutableIntStateOf(0) }
|
||||
val readableSize = remember(readableSizeSema) { chapterCache.readableSize }
|
||||
val readableAnimeSize = remember(readableSizeSema) { episodeCache.readableSize }
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.pref_backup_service_category),
|
||||
title = stringResource(R.string.label_data),
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = backupIntervalPref,
|
||||
title = stringResource(R.string.pref_backup_interval),
|
||||
entries = mapOf(
|
||||
0 to stringResource(R.string.off),
|
||||
6 to stringResource(R.string.update_6hour),
|
||||
12 to stringResource(R.string.update_12hour),
|
||||
24 to stringResource(R.string.update_24hour),
|
||||
48 to stringResource(R.string.update_48hour),
|
||||
168 to stringResource(R.string.update_weekly),
|
||||
),
|
||||
onValueChanged = {
|
||||
BackupCreateJob.setupTask(context, it)
|
||||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.pref_backup_directory),
|
||||
enabled = backupInterval != 0,
|
||||
subtitle = remember(backupDir) {
|
||||
(UniFile.fromUri(context, backupDir.toUri())?.filePath)?.let {
|
||||
"$it/automatic"
|
||||
}
|
||||
} ?: stringResource(R.string.invalid_location, backupDir),
|
||||
title = stringResource(R.string.pref_clear_chapter_cache),
|
||||
subtitle = stringResource(
|
||||
R.string.used_cache_both,
|
||||
readableAnimeSize,
|
||||
readableSize,
|
||||
),
|
||||
onClick = {
|
||||
try {
|
||||
pickBackupLocation.launch(null)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
context.toast(R.string.file_picker_error)
|
||||
scope.launchNonCancellable {
|
||||
try {
|
||||
val deletedFiles = chapterCache.clear() + episodeCache.clear()
|
||||
withUIContext {
|
||||
context.toast(context.getString(R.string.cache_deleted, deletedFiles))
|
||||
readableSizeSema++
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
withUIContext { context.toast(R.string.cache_delete_error) }
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = backupPreferences.numberOfBackups(),
|
||||
enabled = backupInterval != 0,
|
||||
title = stringResource(R.string.pref_backup_slots),
|
||||
entries = listOf(2, 3, 4, 5).associateWith { it.toString() },
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = libraryPreferences.autoClearItemCache(),
|
||||
title = stringResource(R.string.pref_auto_clear_chapter_cache),
|
||||
),
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
pref = backupPreferences.backupFlags(),
|
||||
|
@ -433,7 +457,6 @@ object SettingsBackupScreen : SearchableSettings {
|
|||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(R.string.backup_info)),
|
||||
),
|
||||
)
|
||||
}
|
|
@ -20,6 +20,7 @@ import androidx.compose.material.icons.outlined.PlayCircleOutline
|
|||
import androidx.compose.material.icons.outlined.Search
|
||||
import androidx.compose.material.icons.outlined.Security
|
||||
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
||||
import androidx.compose.material.icons.outlined.Storage
|
||||
import androidx.compose.material.icons.outlined.Sync
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -188,7 +189,7 @@ object SettingsMainScreen : Screen() {
|
|||
Item(
|
||||
titleRes = R.string.pref_category_reader,
|
||||
subtitleRes = R.string.pref_reader_summary,
|
||||
icon = Icons.AutoMirrored.Outlined.ChromeReaderMode,
|
||||
icon = Icons.Outlined.ChromeReaderMode,
|
||||
screen = SettingsReaderScreen,
|
||||
),
|
||||
Item(
|
||||
|
@ -216,10 +217,10 @@ object SettingsMainScreen : Screen() {
|
|||
screen = SettingsBrowseScreen,
|
||||
),
|
||||
Item(
|
||||
titleRes = R.string.label_backup,
|
||||
titleRes = R.string.label_data_storage,
|
||||
subtitleRes = R.string.pref_backup_summary,
|
||||
icon = Icons.Outlined.SettingsBackupRestore,
|
||||
screen = SettingsBackupScreen,
|
||||
icon = Icons.Outlined.Storage,
|
||||
screen = SettingsDataScreen,
|
||||
),
|
||||
Item(
|
||||
titleRes = R.string.pref_category_security,
|
||||
|
|
|
@ -301,7 +301,7 @@ private val settingScreens = listOf(
|
|||
SettingsDownloadScreen,
|
||||
SettingsTrackingScreen,
|
||||
SettingsBrowseScreen,
|
||||
SettingsBackupScreen,
|
||||
SettingsDataScreen,
|
||||
SettingsSecurityScreen,
|
||||
SettingsAdvancedScreen,
|
||||
AdvancedPlayerSettingsScreen,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.presentation.permissions
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
||||
|
||||
/**
|
||||
* Launches request for [Manifest.permission.WRITE_EXTERNAL_STORAGE] permission
|
||||
*/
|
||||
object PermissionRequestHelper {
|
||||
|
||||
@Composable
|
||||
fun requestStoragePermission() {
|
||||
val permissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
LaunchedEffect(Unit) {
|
||||
permissionState.launchPermissionRequest()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package eu.kanade.presentation.reader
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import kotlin.math.abs
|
||||
|
||||
@Composable
|
||||
fun BrightnessOverlay(
|
||||
value: Int,
|
||||
) {
|
||||
if (value >= 0) return
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
alpha = abs(value) / 100f
|
||||
},
|
||||
) {
|
||||
drawRect(Color.Black)
|
||||
}
|
||||
}
|
|
@ -15,11 +15,13 @@ import androidx.compose.ui.res.vectorResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.entries.manga.model.orientationType
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
|
||||
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
|
||||
|
||||
|
@ -37,22 +39,46 @@ fun OrientationModeSelectDialog(
|
|||
}
|
||||
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
Box(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||
SettingsIconGrid(R.string.rotation_type) {
|
||||
items(orientationTypeOptions) { (stringRes, mode) ->
|
||||
IconToggleButton(
|
||||
checked = mode == orientationType,
|
||||
onCheckedChange = {
|
||||
screenModel.onChangeOrientation(mode)
|
||||
onChange(stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||
title = stringResource(stringRes),
|
||||
)
|
||||
}
|
||||
DialogContent(
|
||||
orientationType = orientationType,
|
||||
onChangeOrientation = {
|
||||
screenModel.onChangeOrientation(it)
|
||||
onChange(it.stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogContent(
|
||||
orientationType: OrientationType,
|
||||
onChangeOrientation: (OrientationType) -> Unit,
|
||||
) {
|
||||
Box(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||
SettingsIconGrid(R.string.rotation_type) {
|
||||
items(OrientationType.entries) { mode ->
|
||||
IconToggleButton(
|
||||
checked = mode == orientationType,
|
||||
onCheckedChange = {
|
||||
onChangeOrientation(mode)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||
title = stringResource(mode.stringRes),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun DialogContentPreview() {
|
||||
TachiyomiTheme {
|
||||
DialogContent(
|
||||
orientationType = OrientationType.DEFAULT,
|
||||
onChangeOrientation = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,12 +15,14 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.res.vectorResource
|
||||
import eu.kanade.domain.entries.manga.model.readingModeType
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
|
||||
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
|
||||
|
||||
|
@ -38,22 +40,47 @@ fun ReadingModeSelectDialog(
|
|||
}
|
||||
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
Box(modifier = Modifier.padding(vertical = MaterialTheme.padding.medium)) {
|
||||
SettingsIconGrid(R.string.pref_category_reading_mode) {
|
||||
items(readingModeOptions) { (stringRes, mode) ->
|
||||
IconToggleButton(
|
||||
checked = mode == readingMode,
|
||||
onCheckedChange = {
|
||||
screenModel.onChangeReadingMode(mode)
|
||||
onChange(stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||
title = stringResource(stringRes),
|
||||
)
|
||||
}
|
||||
DialogContent(
|
||||
readingMode = readingMode,
|
||||
onChangeReadingMode = {
|
||||
screenModel.onChangeReadingMode(it)
|
||||
onChange(it.stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogContent(
|
||||
readingMode: ReadingModeType,
|
||||
onChangeReadingMode: (ReadingModeType) -> Unit,
|
||||
) {
|
||||
Box(modifier = Modifier.padding(vertical = MaterialTheme.padding.medium)) {
|
||||
SettingsIconGrid(R.string.pref_category_reading_mode) {
|
||||
items(ReadingModeType.entries) { mode ->
|
||||
IconToggleButton(
|
||||
checked = mode == readingMode,
|
||||
onCheckedChange = {
|
||||
onChangeReadingMode(mode)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||
title = stringResource(mode.stringRes),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun DialogContentPreview() {
|
||||
TachiyomiTheme {
|
||||
DialogContent(
|
||||
readingMode = ReadingModeType.DEFAULT,
|
||||
onChangeReadingMode = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Bookmark
|
||||
|
@ -69,8 +70,8 @@ fun ReaderAppBars(
|
|||
.surfaceColorAtElevation(3.dp)
|
||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||
|
||||
val appBarModifier = if (fullscreen) {
|
||||
Modifier.windowInsetsPadding(WindowInsets.systemBars)
|
||||
val modifierWithInsetsPadding = if (fullscreen) {
|
||||
Modifier.systemBarsPadding()
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ fun ReaderAppBars(
|
|||
),
|
||||
) {
|
||||
AppBar(
|
||||
modifier = appBarModifier
|
||||
modifier = modifierWithInsetsPadding
|
||||
.clickable(onClick = onClickTopAppBar),
|
||||
backgroundColor = backgroundColor,
|
||||
title = mangaTitle,
|
||||
|
@ -137,6 +138,7 @@ fun ReaderAppBars(
|
|||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = modifierWithInsetsPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
ChapterNavigator(
|
||||
|
|
|
@ -32,12 +32,14 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.WheelNumberPicker
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
|
@ -235,3 +237,27 @@ fun BaseSelector(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun TrackStatusSelectorPreviews() {
|
||||
TachiyomiTheme {
|
||||
TrackStatusSelector(
|
||||
selection = 1,
|
||||
onSelectionChange = {},
|
||||
selections = mapOf(
|
||||
// Anilist values
|
||||
1 to R.string.reading,
|
||||
2 to R.string.plan_to_read,
|
||||
3 to R.string.completed,
|
||||
4 to R.string.on_hold,
|
||||
5 to R.string.dropped,
|
||||
6 to R.string.repeating,
|
||||
7 to R.string.watching,
|
||||
8 to R.string.plan_to_watch,
|
||||
),
|
||||
onConfirm = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,10 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.track.anime.model.toDbTrack
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||
import eu.kanade.presentation.track.manga.TrackDetailsItem
|
||||
import eu.kanade.presentation.track.manga.TrackInfoItemMenu
|
||||
|
@ -41,6 +43,7 @@ import eu.kanade.tachiyomi.R
|
|||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import java.text.DateFormat
|
||||
|
||||
private const val UnsetStatusTextAlpha = 0.5F
|
||||
|
@ -161,6 +164,7 @@ private fun TrackInfoItem(
|
|||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
|
@ -245,3 +249,12 @@ private fun TrackInfoItemEmpty(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun TrackInfoDialogHomePreviews(
|
||||
@PreviewParameter(AnimeTrackInfoDialogHomePreviewProvider::class)
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
TachiyomiTheme { content() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package eu.kanade.presentation.track.anime
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import eu.kanade.tachiyomi.dev.preview.DummyTracker
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem
|
||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||
import java.text.DateFormat
|
||||
|
||||
internal class AnimeTrackInfoDialogHomePreviewProvider :
|
||||
PreviewParameterProvider<@Composable () -> Unit> {
|
||||
|
||||
private val aTrack = AnimeTrack(
|
||||
id = 1L,
|
||||
animeId = 2L,
|
||||
syncId = 3L,
|
||||
remoteId = 4L,
|
||||
libraryId = null,
|
||||
title = "Manage Name On Tracker Site",
|
||||
lastEpisodeSeen = 2.0,
|
||||
totalEpisodes = 12L,
|
||||
status = 1L,
|
||||
score = 2.0,
|
||||
remoteUrl = "https://example.com",
|
||||
startDate = 0L,
|
||||
finishDate = 0L,
|
||||
)
|
||||
private val trackItemWithoutTrack = AnimeTrackItem(
|
||||
track = null,
|
||||
tracker = DummyTracker(
|
||||
id = 1L,
|
||||
name = "Example Tracker",
|
||||
),
|
||||
)
|
||||
private val trackItemWithTrack = AnimeTrackItem(
|
||||
track = aTrack,
|
||||
tracker = DummyTracker(
|
||||
id = 2L,
|
||||
name = "Example Tracker 2",
|
||||
),
|
||||
)
|
||||
|
||||
private val trackersWithAndWithoutTrack = @Composable {
|
||||
AnimeTrackInfoDialogHome(
|
||||
trackItems = listOf(
|
||||
trackItemWithoutTrack,
|
||||
trackItemWithTrack,
|
||||
),
|
||||
dateFormat = DateFormat.getDateInstance(),
|
||||
onStatusClick = {},
|
||||
onEpisodeClick = {},
|
||||
onScoreClick = {},
|
||||
onStartDateEdit = {},
|
||||
onEndDateEdit = {},
|
||||
onNewSearch = {},
|
||||
onOpenInBrowser = {},
|
||||
onRemoved = {},
|
||||
)
|
||||
}
|
||||
|
||||
private val noTrackers = @Composable {
|
||||
AnimeTrackInfoDialogHome(
|
||||
trackItems = listOf(),
|
||||
dateFormat = DateFormat.getDateInstance(),
|
||||
onStatusClick = {},
|
||||
onEpisodeClick = {},
|
||||
onScoreClick = {},
|
||||
onStartDateEdit = {},
|
||||
onEndDateEdit = {},
|
||||
onNewSearch = {},
|
||||
onOpenInBrowser = {},
|
||||
onRemoved = {},
|
||||
)
|
||||
}
|
||||
|
||||
override val values: Sequence<@Composable () -> Unit>
|
||||
get() = sequenceOf(
|
||||
trackersWithAndWithoutTrack,
|
||||
noTrackers,
|
||||
)
|
||||
}
|
|
@ -18,9 +18,9 @@ import androidx.compose.foundation.text.BasicTextField
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
@ -42,7 +42,9 @@ import androidx.compose.ui.text.input.ImeAction
|
|||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.presentation.track.manga.SearchResultItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
|
@ -50,6 +52,7 @@ import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
|||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||
|
||||
|
@ -78,7 +81,7 @@ fun AnimeTrackerSearch(
|
|||
navigationIcon = {
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
@ -197,3 +200,12 @@ fun AnimeTrackerSearch(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun TrackerSearchPreviews(
|
||||
@PreviewParameter(AnimeTrackerSearchPreviewProvider::class)
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
TachiyomiTheme { content() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package eu.kanade.presentation.track.anime
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class AnimeTrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||
private val fullPageWithSecondSelected = @Composable {
|
||||
val items = someTrackSearches().take(30).toList()
|
||||
AnimeTrackerSearch(
|
||||
query = TextFieldValue(text = "search text"),
|
||||
onQueryChange = {},
|
||||
onDispatchQuery = {},
|
||||
queryResult = Result.success(items),
|
||||
selected = items[1],
|
||||
onSelectedChange = {},
|
||||
onConfirmSelection = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
private val fullPageWithoutSelected = @Composable {
|
||||
AnimeTrackerSearch(
|
||||
query = TextFieldValue(text = ""),
|
||||
onQueryChange = {},
|
||||
onDispatchQuery = {},
|
||||
queryResult = Result.success(someTrackSearches().take(30).toList()),
|
||||
selected = null,
|
||||
onSelectedChange = {},
|
||||
onConfirmSelection = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
private val loading = @Composable {
|
||||
AnimeTrackerSearch(
|
||||
query = TextFieldValue(),
|
||||
onQueryChange = {},
|
||||
onDispatchQuery = {},
|
||||
queryResult = null,
|
||||
selected = null,
|
||||
onSelectedChange = {},
|
||||
onConfirmSelection = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
override val values: Sequence<@Composable () -> Unit> = sequenceOf(
|
||||
fullPageWithSecondSelected,
|
||||
fullPageWithoutSelected,
|
||||
loading,
|
||||
)
|
||||
|
||||
private fun someTrackSearches(): Sequence<AnimeTrackSearch> = sequence {
|
||||
while (true) {
|
||||
yield(randTrackSearch())
|
||||
}
|
||||
}
|
||||
|
||||
private fun randTrackSearch() = AnimeTrackSearch().let {
|
||||
it.id = Random.nextLong()
|
||||
it.anime_id = Random.nextLong()
|
||||
it.sync_id = Random.nextInt()
|
||||
it.media_id = Random.nextLong()
|
||||
it.library_id = Random.nextLong()
|
||||
it.title = lorem((1..10).random()).joinToString()
|
||||
it.last_episode_seen = (0..100).random().toFloat()
|
||||
it.total_episodes = (100..1000).random()
|
||||
it.score = (0..10).random().toFloat()
|
||||
it.status = Random.nextInt()
|
||||
it.started_watching_date = 0L
|
||||
it.finished_watching_date = 0L
|
||||
it.tracking_url = "https://example.com/tracker-example"
|
||||
it.cover_url = "https://example.com/cover.png"
|
||||
it.start_date = Instant.now().minus((1L..365).random(), ChronoUnit.DAYS).toString()
|
||||
it.summary = lorem((0..40).random()).joinToString()
|
||||
it
|
||||
}
|
||||
|
||||
private fun lorem(words: Int): Sequence<String> =
|
||||
LoremIpsum(words).values
|
||||
}
|
|
@ -11,8 +11,11 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||
|
||||
@Composable
|
||||
|
@ -39,3 +42,17 @@ fun TrackLogoIcon(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun TrackLogoIconPreviews(
|
||||
@PreviewParameter(TrackLogoIconPreviewProvider::class)
|
||||
tracker: Tracker,
|
||||
) {
|
||||
TachiyomiTheme {
|
||||
TrackLogoIcon(
|
||||
tracker = tracker,
|
||||
onClick = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package eu.kanade.presentation.track.components
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.dev.preview.DummyTracker
|
||||
|
||||
|
||||
internal class TrackLogoIconPreviewProvider : PreviewParameterProvider<Tracker> {
|
||||
|
||||
override val values: Sequence<Tracker>
|
||||
get() = sequenceOf(
|
||||
DummyTracker(
|
||||
id = 1L,
|
||||
name = "Dummy Tracker",
|
||||
valLogoColor = Color.rgb(18, 25, 35),
|
||||
valLogo = R.drawable.ic_tracker_anilist,
|
||||
),
|
||||
)
|
||||
}
|
|
@ -44,14 +44,17 @@ 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.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.track.manga.model.toDbTrack
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.track.MangaTrackItem
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import java.text.DateFormat
|
||||
|
||||
private const val UnsetStatusTextAlpha = 0.5F
|
||||
|
@ -172,6 +175,7 @@ private fun TrackInfoItem(
|
|||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
VerticalDivider()
|
||||
|
@ -258,6 +262,7 @@ fun TrackDetailsItem(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -316,3 +321,12 @@ fun TrackInfoItemMenu(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun TrackInfoDialogHomePreviews(
|
||||
@PreviewParameter(MangaTrackInfoDialogHomePreviewProvider::class)
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
TachiyomiTheme { content() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package eu.kanade.presentation.track.manga
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import eu.kanade.tachiyomi.dev.preview.DummyTracker
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.track.MangaTrackItem
|
||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
||||
import java.text.DateFormat
|
||||
|
||||
internal class MangaTrackInfoDialogHomePreviewProvider :
|
||||
PreviewParameterProvider<@Composable () -> Unit> {
|
||||
|
||||
private val aTrack = MangaTrack(
|
||||
id = 1L,
|
||||
mangaId = 2L,
|
||||
syncId = 3L,
|
||||
remoteId = 4L,
|
||||
libraryId = null,
|
||||
title = "Manage Name On Tracker Site",
|
||||
lastChapterRead = 2.0,
|
||||
totalChapters = 12L,
|
||||
status = 1L,
|
||||
score = 2.0,
|
||||
remoteUrl = "https://example.com",
|
||||
startDate = 0L,
|
||||
finishDate = 0L,
|
||||
)
|
||||
private val trackItemWithoutTrack = MangaTrackItem(
|
||||
track = null,
|
||||
tracker = DummyTracker(
|
||||
id = 1L,
|
||||
name = "Example Tracker",
|
||||
),
|
||||
)
|
||||
private val trackItemWithTrack = MangaTrackItem(
|
||||
track = aTrack,
|
||||
tracker = DummyTracker(
|
||||
id = 2L,
|
||||
name = "Example Tracker 2",
|
||||
),
|
||||
)
|
||||
|
||||
private val trackersWithAndWithoutTrack = @Composable {
|
||||
MangaTrackInfoDialogHome(
|
||||
trackItems = listOf(
|
||||
trackItemWithoutTrack,
|
||||
trackItemWithTrack,
|
||||
),
|
||||
dateFormat = DateFormat.getDateInstance(),
|
||||
onStatusClick = {},
|
||||
onChapterClick = {},
|
||||
onScoreClick = {},
|
||||
onStartDateEdit = {},
|
||||
onEndDateEdit = {},
|
||||
onNewSearch = {},
|
||||
onOpenInBrowser = {},
|
||||
onRemoved = {},
|
||||
)
|
||||
}
|
||||
|
||||
private val noTrackers = @Composable {
|
||||
MangaTrackInfoDialogHome(
|
||||
trackItems = listOf(),
|
||||
dateFormat = DateFormat.getDateInstance(),
|
||||
onStatusClick = {},
|
||||
onChapterClick = {},
|
||||
onScoreClick = {},
|
||||
onStartDateEdit = {},
|
||||
onEndDateEdit = {},
|
||||
onNewSearch = {},
|
||||
onOpenInBrowser = {},
|
||||
onRemoved = {},
|
||||
)
|
||||
}
|
||||
|
||||
override val values: Sequence<@Composable () -> Unit>
|
||||
get() = sequenceOf(
|
||||
trackersWithAndWithoutTrack,
|
||||
noTrackers,
|
||||
)
|
||||
}
|
|
@ -28,10 +28,10 @@ import androidx.compose.foundation.text.BasicTextField
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
@ -57,8 +57,10 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.entries.ItemCover
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
|
@ -66,6 +68,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.presentation.core.util.ThemePreviews
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
|
@ -95,7 +98,7 @@ fun MangaTrackerSearch(
|
|||
navigationIcon = {
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
@ -322,3 +325,12 @@ fun SearchResultItemDetails(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun TrackerSearchPreviews(
|
||||
@PreviewParameter(MangaTrackerSearchPreviewProvider::class)
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
TachiyomiTheme { content() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package eu.kanade.presentation.track.manga
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||
import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class MangaTrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||
private val fullPageWithSecondSelected = @Composable {
|
||||
val items = someTrackSearches().take(30).toList()
|
||||
MangaTrackerSearch(
|
||||
query = TextFieldValue(text = "search text"),
|
||||
onQueryChange = {},
|
||||
onDispatchQuery = {},
|
||||
queryResult = Result.success(items),
|
||||
selected = items[1],
|
||||
onSelectedChange = {},
|
||||
onConfirmSelection = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
private val fullPageWithoutSelected = @Composable {
|
||||
MangaTrackerSearch(
|
||||
query = TextFieldValue(text = ""),
|
||||
onQueryChange = {},
|
||||
onDispatchQuery = {},
|
||||
queryResult = Result.success(someTrackSearches().take(30).toList()),
|
||||
selected = null,
|
||||
onSelectedChange = {},
|
||||
onConfirmSelection = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
private val loading = @Composable {
|
||||
MangaTrackerSearch(
|
||||
query = TextFieldValue(),
|
||||
onQueryChange = {},
|
||||
onDispatchQuery = {},
|
||||
queryResult = null,
|
||||
selected = null,
|
||||
onSelectedChange = {},
|
||||
onConfirmSelection = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
override val values: Sequence<@Composable () -> Unit> = sequenceOf(
|
||||
fullPageWithSecondSelected,
|
||||
fullPageWithoutSelected,
|
||||
loading,
|
||||
)
|
||||
|
||||
private fun someTrackSearches(): Sequence<MangaTrackSearch> = sequence {
|
||||
while (true) {
|
||||
yield(randTrackSearch())
|
||||
}
|
||||
}
|
||||
|
||||
private fun randTrackSearch() = MangaTrackSearch().let {
|
||||
it.id = Random.nextLong()
|
||||
it.manga_id = Random.nextLong()
|
||||
it.sync_id = Random.nextInt()
|
||||
it.media_id = Random.nextLong()
|
||||
it.library_id = Random.nextLong()
|
||||
it.title = lorem((1..10).random()).joinToString()
|
||||
it.last_chapter_read = (0..100).random().toFloat()
|
||||
it.total_chapters = (100..1000).random()
|
||||
it.score = (0..10).random().toFloat()
|
||||
it.status = Random.nextInt()
|
||||
it.started_reading_date = 0L
|
||||
it.finished_reading_date = 0L
|
||||
it.tracking_url = "https://example.com/tracker-example"
|
||||
it.cover_url = "https://example.com/cover.png"
|
||||
it.start_date = Instant.now().minus((1L..365).random(), ChronoUnit.DAYS).toString()
|
||||
it.summary = lorem((0..40).random()).joinToString()
|
||||
it
|
||||
}
|
||||
|
||||
private fun lorem(words: Int): Sequence<String> =
|
||||
LoremIpsum(words).values
|
||||
}
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.ArrowForward
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
|
@ -130,7 +129,7 @@ fun WebViewScreenContent(
|
|||
),
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_webview_forward),
|
||||
icon = Icons.AutoMirrored.Outlined.ArrowForward,
|
||||
icon = Icons.Outlined.ArrowForward,
|
||||
onClick = {
|
||||
if (navigator.canGoForward) {
|
||||
navigator.navigateForward()
|
||||
|
@ -181,7 +180,7 @@ fun WebViewScreenContent(
|
|||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
is LoadingState.Loading -> LinearProgressIndicator(
|
||||
progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f },
|
||||
progress = (loadingState as? LoadingState.Loading)?.progress ?: 1f,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter),
|
||||
|
|
|
@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
|||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
||||
import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateDownloadJob
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
|
@ -108,6 +108,8 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||
// Cancel library update and dismiss notification
|
||||
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
|
||||
ACTION_CANCEL_ANIMELIB_UPDATE -> cancelAnimelibUpdate(context)
|
||||
// Start downloading app update
|
||||
ACTION_START_APP_UPDATE -> startDownloadAppUpdate(context, intent)
|
||||
// Cancel downloading app update
|
||||
ACTION_CANCEL_APP_UPDATE_DOWNLOAD -> cancelDownloadAppUpdate(context)
|
||||
// Open reader activity
|
||||
|
@ -301,20 +303,24 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||
MangaLibraryUpdateJob.stop(context)
|
||||
}
|
||||
|
||||
private fun cancelDownloadAppUpdate(context: Context) {
|
||||
AppUpdateService.stop(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when user wants to stop a library update
|
||||
*
|
||||
* @param context context of application
|
||||
* @param notificationId id of notification
|
||||
*/
|
||||
private fun cancelAnimelibUpdate(context: Context) {
|
||||
AnimeLibraryUpdateJob.stop(context)
|
||||
}
|
||||
|
||||
private fun startDownloadAppUpdate(context: Context, intent: Intent) {
|
||||
val url = intent.getStringExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_URL) ?: return
|
||||
AppUpdateDownloadJob.start(context, url)
|
||||
}
|
||||
|
||||
private fun cancelDownloadAppUpdate(context: Context) {
|
||||
AppUpdateDownloadJob.stop(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when user wants to mark manga chapters as read
|
||||
*
|
||||
|
@ -414,6 +420,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||
private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
|
||||
private const val ACTION_CANCEL_ANIMELIB_UPDATE = "$ID.$NAME.CANCEL_ANIMELIB_UPDATE"
|
||||
|
||||
private const val ACTION_START_APP_UPDATE = "$ID.$NAME.ACTION_START_APP_UPDATE"
|
||||
private const val ACTION_CANCEL_APP_UPDATE_DOWNLOAD = "$ID.$NAME.CANCEL_APP_UPDATE_DOWNLOAD"
|
||||
|
||||
private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
|
||||
|
@ -879,10 +886,25 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [PendingIntent] that starts the [AppUpdateDownloadJob] to download an app update.
|
||||
*
|
||||
* @param context context of application
|
||||
* @return [PendingIntent]
|
||||
*/
|
||||
internal fun downloadAppUpdatePendingBroadcast(context: Context, url: String, title: String? = null): PendingIntent {
|
||||
return Intent(context, NotificationReceiver::class.java).run {
|
||||
action = ACTION_START_APP_UPDATE
|
||||
putExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_URL, url)
|
||||
title?.let { putExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_TITLE, it) }
|
||||
PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
internal fun cancelUpdateDownloadPendingBroadcast(context: Context): PendingIntent {
|
||||
internal fun cancelDownloadAppUpdatePendingBroadcast(context: Context): PendingIntent {
|
||||
val intent = Intent(context, NotificationReceiver::class.java).apply {
|
||||
action = ACTION_CANCEL_APP_UPDATE_DOWNLOAD
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.io.ByteArrayInputStream
|
|||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.Date
|
||||
|
||||
class ImageSaver(
|
||||
val context: Context,
|
||||
|
@ -79,6 +80,7 @@ class ImageSaver(
|
|||
MediaStore.Images.Media.RELATIVE_PATH to relativePath,
|
||||
MediaStore.Images.Media.DISPLAY_NAME to image.name,
|
||||
MediaStore.Images.Media.MIME_TYPE to type.mime,
|
||||
MediaStore.Images.Media.DATE_MODIFIED to Date().time * 1000,
|
||||
)
|
||||
|
||||
val picture = findUriOrDefault(relativePath, filename) {
|
||||
|
|
|
@ -25,7 +25,8 @@ interface Tracker {
|
|||
|
||||
@StringRes
|
||||
fun getStatus(status: Int): Int?
|
||||
|
||||
fun getCompletionStatus(): Int
|
||||
fun getScoreList(): List<String>
|
||||
suspend fun login(username: String, password: String)
|
||||
|
||||
@CallSuper
|
||||
|
|
|
@ -28,7 +28,7 @@ class TrackerManager(context: Context) {
|
|||
val bangumi = Bangumi(5L)
|
||||
val komga = Komga(6L)
|
||||
val mangaUpdates = MangaUpdates(7L)
|
||||
val kavita = Kavita(context, KAVITA)
|
||||
val kavita = Kavita(KAVITA)
|
||||
val suwayomi = Suwayomi(9L)
|
||||
val simkl = Simkl(SIMKL)
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.data.track.DeletableMangaTracker
|
|||
import eu.kanade.tachiyomi.data.track.MangaTracker
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
@ -161,19 +160,19 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), AnimeTracker, MangaTracker, De
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
|
||||
override suspend fun bind(track: AnimeTrack, hasWatchedEpisodes: Boolean): AnimeTrack {
|
||||
val remoteTrack = api.findLibAnime(track, getUserId())
|
||||
return if (remoteTrack != null) {
|
||||
track.copyPersonalFrom(remoteTrack)
|
||||
track.media_id = remoteTrack.media_id
|
||||
|
||||
if (track.status != COMPLETED) {
|
||||
track.status = if (hasReadChapters) WATCHING else track.status
|
||||
track.status = if (hasWatchedEpisodes) WATCHING else track.status
|
||||
}
|
||||
|
||||
update(track)
|
||||
} else {
|
||||
track.status = if (hasReadChapters) WATCHING else PLAN_TO_WATCH
|
||||
track.status = if (hasWatchedEpisodes) WATCHING else PLAN_TO_WATCH
|
||||
track.score = 0F
|
||||
add(track)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.ProgressListener
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.newCachelessCallWithProgress
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import logcat.LogPriority
|
||||
import okhttp3.internal.http2.ErrorCode
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
private val notifier = AppUpdateNotifier(context)
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val url = inputData.getString(EXTRA_DOWNLOAD_URL)
|
||||
val title = inputData.getString(EXTRA_DOWNLOAD_TITLE) ?: context.getString(R.string.app_name)
|
||||
|
||||
if (url.isNullOrEmpty()) {
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
try {
|
||||
setForeground(getForegroundInfo())
|
||||
} catch (e: IllegalStateException) {
|
||||
logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" }
|
||||
}
|
||||
|
||||
withIOContext {
|
||||
downloadApk(title, url)
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
return ForegroundInfo(
|
||||
Notifications.ID_APP_UPDATER,
|
||||
notifier.onDownloadStarted().build(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to start downloading apk of new update
|
||||
*
|
||||
* @param url url location of file
|
||||
*/
|
||||
private suspend fun downloadApk(title: String, url: String) {
|
||||
// Show notification download starting.
|
||||
notifier.onDownloadStarted(title)
|
||||
|
||||
val progressListener = object : ProgressListener {
|
||||
// Progress of the download
|
||||
var savedProgress = 0
|
||||
|
||||
// Keep track of the last notification sent to avoid posting too many.
|
||||
var lastTick = 0L
|
||||
|
||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
val progress = (100 * (bytesRead.toFloat() / contentLength)).toInt()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (progress > savedProgress && currentTime - 200 > lastTick) {
|
||||
savedProgress = progress
|
||||
lastTick = currentTime
|
||||
notifier.onProgressChange(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Download the new update.
|
||||
val response = network.client.newCachelessCallWithProgress(GET(url), progressListener)
|
||||
.await()
|
||||
|
||||
// File where the apk will be saved.
|
||||
val apkFile = File(context.externalCacheDir, "update.apk")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body.source().saveTo(apkFile)
|
||||
} else {
|
||||
response.close()
|
||||
throw Exception("Unsuccessful response")
|
||||
}
|
||||
notifier.cancel()
|
||||
notifier.promptInstall(apkFile.getUriCompat(context))
|
||||
} catch (e: Exception) {
|
||||
val shouldCancel = e is CancellationException ||
|
||||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
|
||||
if (shouldCancel) {
|
||||
notifier.cancel()
|
||||
} else {
|
||||
notifier.onDownloadError(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppUpdateDownload"
|
||||
|
||||
const val EXTRA_DOWNLOAD_URL = "DOWNLOAD_URL"
|
||||
const val EXTRA_DOWNLOAD_TITLE = "DOWNLOAD_TITLE"
|
||||
|
||||
fun start(context: Context, url: String, title: String? = null) {
|
||||
val constraints = Constraints(
|
||||
requiredNetworkType = NetworkType.CONNECTED,
|
||||
)
|
||||
|
||||
val request = OneTimeWorkRequestBuilder<AppUpdateDownloadJob>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG)
|
||||
.setInputData(
|
||||
workDataOf(
|
||||
EXTRA_DOWNLOAD_URL to url,
|
||||
EXTRA_DOWNLOAD_TITLE to title,
|
||||
),
|
||||
)
|
||||
.build()
|
||||
|
||||
context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
context.workManager.cancelUniqueWork(TAG)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,16 +34,11 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||
|
||||
@SuppressLint("LaunchActivityFromNotification")
|
||||
fun promptUpdate(release: Release) {
|
||||
val updateIntent = Intent(context, AppUpdateService::class.java).run {
|
||||
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
|
||||
putExtra(AppUpdateService.EXTRA_DOWNLOAD_TITLE, release.version)
|
||||
PendingIntent.getService(
|
||||
context,
|
||||
0,
|
||||
this,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
}
|
||||
val updateIntent = NotificationReceiver.downloadAppUpdatePendingBroadcast(
|
||||
context,
|
||||
release.getDownloadLink(),
|
||||
release.version,
|
||||
)
|
||||
|
||||
val releaseIntent = Intent(Intent.ACTION_VIEW, release.releaseLink.toUri()).run {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
|
@ -94,7 +89,7 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.action_cancel),
|
||||
NotificationReceiver.cancelUpdateDownloadPendingBroadcast(context),
|
||||
NotificationReceiver.cancelDownloadAppUpdatePendingBroadcast(context),
|
||||
)
|
||||
}
|
||||
notificationBuilder.show()
|
||||
|
@ -184,7 +179,7 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||
addAction(
|
||||
R.drawable.ic_refresh_24dp,
|
||||
context.getString(R.string.action_retry),
|
||||
AppUpdateService.downloadApkPendingService(context, url),
|
||||
NotificationReceiver.downloadAppUpdatePendingBroadcast(context, url),
|
||||
)
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
|
|
|
@ -1,204 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.ProgressListener
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.newCachelessCallWithProgress
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.http2.ErrorCode
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
class AppUpdateService : Service() {
|
||||
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* Wake lock that will be held until the service is destroyed.
|
||||
*/
|
||||
private lateinit var wakeLock: PowerManager.WakeLock
|
||||
|
||||
private lateinit var notifier: AppUpdateNotifier
|
||||
|
||||
private val job = SupervisorJob()
|
||||
private val serviceScope = CoroutineScope(Dispatchers.IO + job)
|
||||
|
||||
override fun onCreate() {
|
||||
notifier = AppUpdateNotifier(this)
|
||||
wakeLock = acquireWakeLock(javaClass.name)
|
||||
|
||||
startForeground(Notifications.ID_APP_UPDATER, notifier.onDownloadStarted().build())
|
||||
}
|
||||
|
||||
/**
|
||||
* This method needs to be implemented, but it's not used/needed.
|
||||
*/
|
||||
override fun onBind(intent: Intent): IBinder? = null
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) return START_NOT_STICKY
|
||||
|
||||
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY
|
||||
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
|
||||
|
||||
serviceScope.launch {
|
||||
downloadApk(title, url)
|
||||
}
|
||||
|
||||
job.invokeOnCompletion { stopSelf(startId) }
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun stopService(name: Intent?): Boolean {
|
||||
destroyJob()
|
||||
return super.stopService(name)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
destroyJob()
|
||||
}
|
||||
|
||||
private fun destroyJob() {
|
||||
serviceScope.cancel()
|
||||
job.cancel()
|
||||
if (wakeLock.isHeld) {
|
||||
wakeLock.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to start downloading apk of new update
|
||||
*
|
||||
* @param url url location of file
|
||||
*/
|
||||
private suspend fun downloadApk(title: String, url: String) {
|
||||
// Show notification download starting.
|
||||
notifier.onDownloadStarted(title)
|
||||
|
||||
val progressListener = object : ProgressListener {
|
||||
// Progress of the download
|
||||
var savedProgress = 0
|
||||
|
||||
// Keep track of the last notification sent to avoid posting too many.
|
||||
var lastTick = 0L
|
||||
|
||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
val progress = (100 * (bytesRead.toFloat() / contentLength)).toInt()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (progress > savedProgress && currentTime - 200 > lastTick) {
|
||||
savedProgress = progress
|
||||
lastTick = currentTime
|
||||
notifier.onProgressChange(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Download the new update.
|
||||
val response = network.client.newCachelessCallWithProgress(GET(url), progressListener)
|
||||
.await()
|
||||
|
||||
// File where the apk will be saved.
|
||||
val apkFile = File(externalCacheDir, "update.apk")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body.source().saveTo(apkFile)
|
||||
} else {
|
||||
response.close()
|
||||
throw Exception("Unsuccessful response")
|
||||
}
|
||||
notifier.promptInstall(apkFile.getUriCompat(this))
|
||||
} catch (e: Exception) {
|
||||
val shouldCancel = e is CancellationException ||
|
||||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
|
||||
if (shouldCancel) {
|
||||
notifier.cancel()
|
||||
} else {
|
||||
notifier.onDownloadError(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL"
|
||||
internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE"
|
||||
|
||||
/**
|
||||
* Returns the status of the service.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @return true if the service is running, false otherwise.
|
||||
*/
|
||||
private fun isRunning(context: Context): Boolean =
|
||||
context.isServiceRunning(AppUpdateService::class.java)
|
||||
|
||||
/**
|
||||
* Downloads a new update and let the user install the new version from a notification.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param url the url to the new update.
|
||||
*/
|
||||
fun start(
|
||||
context: Context,
|
||||
url: String,
|
||||
title: String? = context.getString(R.string.app_name),
|
||||
) {
|
||||
if (isRunning(context)) return
|
||||
|
||||
Intent(context, AppUpdateService::class.java).apply {
|
||||
putExtra(EXTRA_DOWNLOAD_TITLE, title)
|
||||
putExtra(EXTRA_DOWNLOAD_URL, url)
|
||||
ContextCompat.startForegroundService(context, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service.
|
||||
*
|
||||
* @param context the application context
|
||||
*/
|
||||
fun stop(context: Context) {
|
||||
context.stopService(Intent(context, AppUpdateService::class.java))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [PendingIntent] that starts a service which downloads the apk specified in url.
|
||||
*
|
||||
* @param url the url to the new update.
|
||||
* @return [PendingIntent]
|
||||
*/
|
||||
internal fun downloadApkPendingService(context: Context, url: String): PendingIntent {
|
||||
return Intent(context, AppUpdateService::class.java).run {
|
||||
putExtra(EXTRA_DOWNLOAD_URL, url)
|
||||
PendingIntent.getService(
|
||||
context,
|
||||
0,
|
||||
this,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package eu.kanade.tachiyomi.dev.preview
|
||||
|
||||
import android.graphics.Color
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch
|
||||
import okhttp3.OkHttpClient
|
||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
||||
|
||||
data class DummyTracker(
|
||||
override val id: Long,
|
||||
override val name: String,
|
||||
override val supportsReadingDates: Boolean = false,
|
||||
override val isLoggedIn: Boolean = false,
|
||||
val valLogoColor: Int = Color.rgb(18, 25, 35),
|
||||
val valLogo: Int = R.drawable.ic_tracker_anilist,
|
||||
val valStatuses: List<Int> = (1..6).toList(),
|
||||
val valCompletionStatus: Int = 2,
|
||||
val valScoreList: List<String> = (0..10).map(Int::toString),
|
||||
val val10PointScore: Double = 5.4,
|
||||
val valMangaSearchResults: List<MangaTrackSearch> = listOf(),
|
||||
val valAnimeSearchResults: List<AnimeTrackSearch> = listOf(),
|
||||
) : Tracker {
|
||||
|
||||
override val client: OkHttpClient
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
override fun getLogoColor(): Int = valLogoColor
|
||||
|
||||
override fun getLogo(): Int = valLogo
|
||||
|
||||
override fun getStatus(status: Int): Int? = when (status) {
|
||||
1 -> R.string.reading
|
||||
2 -> R.string.plan_to_read
|
||||
3 -> R.string.completed
|
||||
4 -> R.string.on_hold
|
||||
5 -> R.string.dropped
|
||||
6 -> R.string.repeating
|
||||
7 -> R.string.watching
|
||||
8 -> R.string.plan_to_watch
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getCompletionStatus(): Int = valCompletionStatus
|
||||
|
||||
override fun getScoreList(): List<String> = valScoreList
|
||||
|
||||
override suspend fun login(username: String, password: String) = Unit
|
||||
|
||||
override fun logout() = Unit
|
||||
|
||||
override fun getUsername(): String = "username"
|
||||
|
||||
override fun getPassword(): String = "passw0rd"
|
||||
|
||||
override fun saveCredentials(username: String, password: String) = Unit
|
||||
}
|
|
@ -14,7 +14,7 @@ import cafe.adriel.voyager.navigator.Navigator
|
|||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import eu.kanade.presentation.components.TabbedScreen
|
||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
||||
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||
import eu.kanade.presentation.util.Tab
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionsScreenModel
|
||||
|
@ -27,7 +27,6 @@ import eu.kanade.tachiyomi.ui.browse.manga.extension.mangaExtensionsTab
|
|||
import eu.kanade.tachiyomi.ui.browse.manga.migration.sources.migrateMangaSourceTab
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.mangaSourcesTab
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
|
||||
data class BrowseTab(
|
||||
private val toExtensions: Boolean = false,
|
||||
|
@ -80,7 +79,7 @@ data class BrowseTab(
|
|||
)
|
||||
|
||||
// For local source
|
||||
DiskUtil.RequestStoragePermission()
|
||||
PermissionRequestHelper.requestStoragePermission()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
(context as? MainActivity)?.ready = true
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.anime.migration.sources
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
@ -30,7 +29,7 @@ fun Screen.migrateAnimeSourceTab(): TabContent {
|
|||
actions = listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.migration_help_guide),
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = {
|
||||
uriHandler.openUri("https://aniyomi.org/help/guides/source-migration/")
|
||||
},
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.tachiyomi.ui.browse.manga.migration.sources
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
@ -30,7 +29,7 @@ fun Screen.migrateMangaSourceTab(): TabContent {
|
|||
actions = listOf(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.migration_help_guide),
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = {
|
||||
uriHandler.openUri("https://aniyomi.org/help/guides/source-migration/")
|
||||
},
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
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
|
||||
|
@ -14,6 +15,7 @@ import eu.kanade.presentation.util.Screen
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.GlobalAnimeSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreen
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
|
||||
|
@ -23,6 +25,7 @@ class DeepLinkAnimeScreen(
|
|||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel {
|
||||
|
@ -46,12 +49,22 @@ class DeepLinkAnimeScreen(
|
|||
navigator.replace(GlobalAnimeSearchScreen(query))
|
||||
}
|
||||
is DeepLinkAnimeScreenModel.State.Result -> {
|
||||
navigator.replace(
|
||||
AnimeScreen(
|
||||
(state as DeepLinkAnimeScreenModel.State.Result).anime.id,
|
||||
true,
|
||||
),
|
||||
)
|
||||
val resultState = state as DeepLinkAnimeScreenModel.State.Result
|
||||
if (resultState.episodeId == null) {
|
||||
navigator.replace(
|
||||
AnimeScreen(
|
||||
resultState.anime.id,
|
||||
true,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
navigator.pop()
|
||||
PlayerActivity.newIntent(
|
||||
context,
|
||||
resultState.anime.id,
|
||||
resultState.episodeId,
|
||||
).also(context::startActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,20 @@ import androidx.compose.runtime.Immutable
|
|||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.domain.entries.anime.model.toDomainAnime
|
||||
import eu.kanade.domain.entries.anime.model.toSAnime
|
||||
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.animesource.online.ResolvableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.online.UriType
|
||||
import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.manga.interactor.GetAnimeByUrlAndSourceId
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByUrlAndAnimeId
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
@ -15,25 +25,59 @@ import uy.kohesive.injekt.api.get
|
|||
class DeepLinkAnimeScreenModel(
|
||||
query: String = "",
|
||||
private val sourceManager: AnimeSourceManager = Injekt.get(),
|
||||
private val networkToLocalAnime: NetworkToLocalAnime = Injekt.get(),
|
||||
private val getEpisodeByUrlAndAnimeId: GetEpisodeByUrlAndAnimeId = Injekt.get(),
|
||||
private val getAnimeByUrlAndSourceId: GetAnimeByUrlAndSourceId = Injekt.get(),
|
||||
private val syncEpisodesWithSource: SyncEpisodesWithSource = Injekt.get(),
|
||||
) : StateScreenModel<DeepLinkAnimeScreenModel.State>(State.Loading) {
|
||||
|
||||
init {
|
||||
coroutineScope.launchIO {
|
||||
val anime = sourceManager.getCatalogueSources()
|
||||
val source = sourceManager.getCatalogueSources()
|
||||
.filterIsInstance<ResolvableAnimeSource>()
|
||||
.filter { it.canResolveUri(query) }
|
||||
.firstNotNullOfOrNull { it.getAnime(query)?.toDomainAnime(it.id) }
|
||||
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
||||
|
||||
val anime = source?.getAnime(query)?.let {
|
||||
getAnimeFromSAnime(it, source.id)
|
||||
}
|
||||
|
||||
val episode = if (source?.getUriType(query) == UriType.Episode && anime != null) {
|
||||
source.getEpisode(query)?.let { getEpisodeFromSEpisode(it, anime, source) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
mutableState.update {
|
||||
if (anime == null) {
|
||||
State.NoResults
|
||||
} else {
|
||||
State.Result(anime)
|
||||
if (episode == null) {
|
||||
State.Result(anime)
|
||||
} else {
|
||||
State.Result(anime, episode.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEpisodeFromSEpisode(sEpisode: SEpisode, anime: Anime, source: AnimeSource): Episode? {
|
||||
val localEpisode = getEpisodeByUrlAndAnimeId.await(sEpisode.url, anime.id)
|
||||
|
||||
return if (localEpisode == null) {
|
||||
val sourceEpisodes = source.getEpisodeList(anime.toSAnime())
|
||||
val newEpisodes = syncEpisodesWithSource.await(sourceEpisodes, anime, source, false)
|
||||
newEpisodes.find { it.url == sEpisode.url }
|
||||
} else {
|
||||
localEpisode
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAnimeFromSAnime(sAnime: SAnime, sourceId: Long): Anime {
|
||||
return getAnimeByUrlAndSourceId.awaitAnime(sAnime.url, sourceId)
|
||||
?: networkToLocalAnime.await(sAnime.toDomainAnime(sourceId))
|
||||
}
|
||||
|
||||
sealed interface State {
|
||||
@Immutable
|
||||
data object Loading : State
|
||||
|
@ -42,6 +86,6 @@ class DeepLinkAnimeScreenModel(
|
|||
data object NoResults : State
|
||||
|
||||
@Immutable
|
||||
data class Result(val anime: Anime) : State
|
||||
data class Result(val anime: Anime, val episodeId: Long? = null) : State
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
|
@ -14,6 +16,7 @@ import eu.kanade.presentation.util.Screen
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.GlobalMangaSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
|
||||
|
@ -23,6 +26,7 @@ class DeepLinkMangaScreen(
|
|||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel {
|
||||
|
@ -46,12 +50,22 @@ class DeepLinkMangaScreen(
|
|||
navigator.replace(GlobalMangaSearchScreen(query))
|
||||
}
|
||||
is DeepLinkMangaScreenModel.State.Result -> {
|
||||
navigator.replace(
|
||||
MangaScreen(
|
||||
(state as DeepLinkMangaScreenModel.State.Result).manga.id,
|
||||
true,
|
||||
),
|
||||
)
|
||||
val resultState = state as DeepLinkMangaScreenModel.State.Result
|
||||
if (resultState.chapterId == null) {
|
||||
navigator.replace(
|
||||
MangaScreen(
|
||||
resultState.manga.id,
|
||||
true,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
navigator.pop()
|
||||
ReaderActivity.newIntent(
|
||||
context,
|
||||
resultState.manga.id,
|
||||
resultState.chapterId,
|
||||
).also(context::startActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,20 @@ import androidx.compose.runtime.Immutable
|
|||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.domain.entries.manga.model.toDomainManga
|
||||
import eu.kanade.domain.entries.manga.model.toSManga
|
||||
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ResolvableMangaSource
|
||||
import eu.kanade.tachiyomi.source.online.UriType
|
||||
import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.domain.entries.anime.interactor.GetMangaByUrlAndSourceId
|
||||
import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByUrlAndMangaId
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
@ -15,25 +25,59 @@ import uy.kohesive.injekt.api.get
|
|||
class DeepLinkMangaScreenModel(
|
||||
query: String = "",
|
||||
private val sourceManager: MangaSourceManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
||||
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
) : StateScreenModel<DeepLinkMangaScreenModel.State>(State.Loading) {
|
||||
|
||||
init {
|
||||
coroutineScope.launchIO {
|
||||
val manga = sourceManager.getCatalogueSources()
|
||||
val source = sourceManager.getCatalogueSources()
|
||||
.filterIsInstance<ResolvableMangaSource>()
|
||||
.filter { it.canResolveUri(query) }
|
||||
.firstNotNullOfOrNull { it.getManga(query)?.toDomainManga(it.id) }
|
||||
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
||||
|
||||
val manga = source?.getManga(query)?.let {
|
||||
getMangaFromSManga(it, source.id)
|
||||
}
|
||||
|
||||
val chapter = if (source?.getUriType(query) == UriType.Chapter && manga != null) {
|
||||
source.getChapter(query)?.let { getChapterFromSChapter(it, manga, source) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
mutableState.update {
|
||||
if (manga == null) {
|
||||
State.NoResults
|
||||
} else {
|
||||
State.Result(manga)
|
||||
if (chapter == null) {
|
||||
State.Result(manga)
|
||||
} else {
|
||||
State.Result(manga, chapter.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getChapterFromSChapter(sChapter: SChapter, manga: Manga, source: MangaSource): Chapter? {
|
||||
val localChapter = getChapterByUrlAndMangaId.await(sChapter.url, manga.id)
|
||||
|
||||
return if (localChapter == null) {
|
||||
val sourceChapters = source.getChapterList(manga.toSManga())
|
||||
val newChapters = syncChaptersWithSource.await(sourceChapters, manga, source, false)
|
||||
newChapters.find { it.url == sChapter.url }
|
||||
} else {
|
||||
localChapter
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMangaFromSManga(sManga: SManga, sourceId: Long): Manga {
|
||||
return getMangaByUrlAndSourceId.awaitManga(sManga.url, sourceId)
|
||||
?: networkToLocalManga.await(sManga.toDomainManga(sourceId))
|
||||
}
|
||||
|
||||
sealed interface State {
|
||||
@Immutable
|
||||
data object Loading : State
|
||||
|
@ -42,6 +86,6 @@ class DeepLinkMangaScreenModel(
|
|||
data object NoResults : State
|
||||
|
||||
@Immutable
|
||||
data class Result(val manga: Manga) : State
|
||||
data class Result(val manga: Manga, val chapterId: Long? = null) : State
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,12 @@ import androidx.compose.ui.res.stringResource
|
|||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import eu.kanade.presentation.components.TabbedScreen
|
||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
||||
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||
import eu.kanade.presentation.util.Tab
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.download.anime.animeDownloadTab
|
||||
import eu.kanade.tachiyomi.ui.download.manga.mangaDownloadTab
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
|
||||
data class DownloadsTab(
|
||||
private val isManga: Boolean = false,
|
||||
|
@ -51,6 +50,6 @@ data class DownloadsTab(
|
|||
}
|
||||
|
||||
// For local source
|
||||
DiskUtil.RequestStoragePermission()
|
||||
PermissionRequestHelper.requestStoragePermission()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
|||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.TabbedScreen
|
||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
||||
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||
import eu.kanade.presentation.util.Tab
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryScreenModel
|
||||
|
@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.ui.history.anime.resumeLastEpisodeSeenEvent
|
|||
import eu.kanade.tachiyomi.ui.history.manga.MangaHistoryScreenModel
|
||||
import eu.kanade.tachiyomi.ui.history.manga.mangaHistoryTab
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
|
||||
data class HistoriesTab(
|
||||
private val fromMore: Boolean,
|
||||
|
@ -75,7 +74,7 @@ data class HistoriesTab(
|
|||
}
|
||||
|
||||
// For local source
|
||||
DiskUtil.RequestStoragePermission()
|
||||
PermissionRequestHelper.requestStoragePermission()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ object AnimeLibraryTab : Tab() {
|
|||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.getting_started_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = {
|
||||
handler.openUri(
|
||||
"https://aniyomi.org/docs/guides/getting-started",
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
|||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
|
@ -191,7 +190,7 @@ object MangaLibraryTab : Tab() {
|
|||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.getting_started_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = {
|
||||
handler.openUri(
|
||||
"https://aniyomi.org/docs/guides/getting-started",
|
||||
|
|
|
@ -81,7 +81,7 @@ object MoreTab : Tab() {
|
|||
onClickCategories = { navigator.push(CategoriesTab()) },
|
||||
onClickStats = { navigator.push(StatsTab()) },
|
||||
onClickStorage = { navigator.push(StorageTab()) },
|
||||
onClickBackupAndRestore = { navigator.push(SettingsScreen.toBackupScreen()) },
|
||||
onClickDataAndStorage = { navigator.push(SettingsScreen.toDataAndStorageScreen()) },
|
||||
onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) },
|
||||
onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) },
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.more.NewUpdateScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateDownloadJob
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
|
||||
class NewUpdateScreen(
|
||||
|
@ -31,7 +31,7 @@ class NewUpdateScreen(
|
|||
onOpenInBrowser = { context.openInBrowser(releaseLink) },
|
||||
onRejectUpdate = navigator::pop,
|
||||
onAcceptUpdate = {
|
||||
AppUpdateService.start(
|
||||
AppUpdateDownloadJob.start(
|
||||
context = context,
|
||||
url = downloadLink,
|
||||
title = versionName,
|
||||
|
|
|
@ -41,6 +41,7 @@ import com.google.android.material.elevation.SurfaceColors
|
|||
import com.google.android.material.transition.platform.MaterialContainerTransform
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.presentation.reader.BrightnessOverlay
|
||||
import eu.kanade.presentation.reader.OrientationModeSelectDialog
|
||||
import eu.kanade.presentation.reader.PageIndicatorText
|
||||
import eu.kanade.presentation.reader.ReaderPageActionsDialog
|
||||
|
@ -304,12 +305,6 @@ class ReaderActivity : BaseActivity() {
|
|||
* Initializes the reader menu. It sets up click listeners and the initial visibility.
|
||||
*/
|
||||
private fun initializeMenu() {
|
||||
binding.dialogRoot.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
margin(vertical = true, horizontal = true)
|
||||
}
|
||||
}
|
||||
|
||||
binding.pageNumber.setComposeContent {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val showPageNumber by viewModel.readerPreferences.showPageNumber().collectAsState()
|
||||
|
@ -384,6 +379,10 @@ class ReaderActivity : BaseActivity() {
|
|||
onClickSettings = viewModel::openSettingsDialog,
|
||||
)
|
||||
|
||||
BrightnessOverlay(
|
||||
value = state.brightnessOverlayValue,
|
||||
)
|
||||
|
||||
val onDismissRequest = viewModel::closeDialog
|
||||
when (state.dialog) {
|
||||
is ReaderViewModel.Dialog.Loading -> {
|
||||
|
@ -918,17 +917,9 @@ class ReaderActivity : BaseActivity() {
|
|||
}
|
||||
else -> WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
}
|
||||
|
||||
window.attributes = window.attributes.apply { screenBrightness = readerBrightness }
|
||||
|
||||
// Set black overlay visibility.
|
||||
if (value < 0) {
|
||||
binding.brightnessOverlay.isVisible = true
|
||||
val alpha = (abs(value) * 2.56).toInt()
|
||||
binding.brightnessOverlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
|
||||
} else {
|
||||
binding.brightnessOverlay.isVisible = false
|
||||
}
|
||||
viewModel.setBrightnessOverlayValue(value)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -761,6 +761,10 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||
mutableState.update { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
fun setBrightnessOverlayValue(value: Int) {
|
||||
mutableState.update { it.copy(brightnessOverlayValue = value) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image of this the selected page on the pictures directory and notifies the UI of the result.
|
||||
* There's also a notification to allow sharing the image somewhere else or deleting it.
|
||||
|
@ -922,6 +926,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
|||
val viewer: Viewer? = null,
|
||||
val dialog: Dialog? = null,
|
||||
val menuVisible: Boolean = false,
|
||||
val brightnessOverlayValue: Int = 0,
|
||||
) {
|
||||
val currentChapter: ReaderChapter?
|
||||
get() = viewerChapters?.currChapter
|
||||
|
|
|
@ -13,7 +13,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
||||
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
||||
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
|
||||
|
@ -23,7 +23,7 @@ import eu.kanade.presentation.util.isTabletUi
|
|||
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||
|
||||
class SettingsScreen private constructor(
|
||||
val toBackup: Boolean,
|
||||
val toDataAndStorage: Boolean,
|
||||
val toAbout: Boolean,
|
||||
) : Screen() {
|
||||
|
||||
|
@ -32,8 +32,8 @@ class SettingsScreen private constructor(
|
|||
val parentNavigator = LocalNavigator.currentOrThrow
|
||||
if (!isTabletUi()) {
|
||||
Navigator(
|
||||
screen = if (toBackup) {
|
||||
SettingsBackupScreen
|
||||
screen = if (toDataAndStorage) {
|
||||
SettingsDataScreen
|
||||
} else if (toAbout) {
|
||||
AboutScreen
|
||||
} else {
|
||||
|
@ -54,8 +54,8 @@ class SettingsScreen private constructor(
|
|||
)
|
||||
} else {
|
||||
Navigator(
|
||||
screen = if (toBackup) {
|
||||
SettingsBackupScreen
|
||||
screen = if (toDataAndStorage) {
|
||||
SettingsDataScreen
|
||||
} else if (toAbout) {
|
||||
AboutScreen
|
||||
} else {
|
||||
|
@ -79,10 +79,10 @@ class SettingsScreen private constructor(
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun toMainScreen() = SettingsScreen(toBackup = false, toAbout = false)
|
||||
fun toMainScreen() = SettingsScreen(toDataAndStorage = false, toAbout = false)
|
||||
|
||||
fun toBackupScreen() = SettingsScreen(toBackup = true, toAbout = false)
|
||||
fun toDataAndStorageScreen() = SettingsScreen(toDataAndStorage = true, toAbout = false)
|
||||
|
||||
fun toAboutScreen() = SettingsScreen(toBackup = false, toAbout = true)
|
||||
fun toAboutScreen() = SettingsScreen(toDataAndStorage = false, toAbout = true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.util
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
|
@ -10,14 +12,24 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
|
|||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.lang.withUIContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class CrashLogUtil(private val context: Context) {
|
||||
class CrashLogUtil(
|
||||
private val context: Context,
|
||||
private val mangaExtensionManager: MangaExtensionManager = Injekt.get(),
|
||||
private val animeExtensionManager: AnimeExtensionManager = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend fun dumpLogs() = withNonCancellableContext {
|
||||
try {
|
||||
val file = context.createFileInCacheDir("aniyomi_crash_logs.txt")
|
||||
|
||||
file.appendText(getDebugInfo() + "\n\n")
|
||||
getMangaExtensionsInfo()?.let { file.appendText("$it\n\n") }
|
||||
getAnimeExtensionsInfo()?.let { file.appendText("$it\n\n") }
|
||||
|
||||
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
|
||||
file.appendText(getDebugInfo())
|
||||
|
||||
val uri = file.getUriCompat(context)
|
||||
context.startActivity(uri.toShareIntent(context, "text/plain"))
|
||||
|
@ -28,15 +40,65 @@ class CrashLogUtil(private val context: Context) {
|
|||
|
||||
fun getDebugInfo(): String {
|
||||
return """
|
||||
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}, ${BuildConfig.BUILD_TIME})
|
||||
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
|
||||
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}; build ${Build.DISPLAY})
|
||||
Android build ID: ${Build.DISPLAY}
|
||||
Device brand: ${Build.BRAND}
|
||||
Device manufacturer: ${Build.MANUFACTURER}
|
||||
Device name: ${Build.DEVICE}
|
||||
Device name: ${Build.DEVICE} (${Build.PRODUCT})
|
||||
Device model: ${Build.MODEL}
|
||||
Device product name: ${Build.PRODUCT}
|
||||
WebView: ${WebViewUtil.getVersion(context)}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
private fun getMangaExtensionsInfo(): String? {
|
||||
val availableExtensions = mangaExtensionManager.availableExtensionsFlow.value.associateBy { it.pkgName }
|
||||
|
||||
val extensionInfoList = mangaExtensionManager.installedExtensionsFlow.value
|
||||
.sortedBy { it.name }
|
||||
.mapNotNull {
|
||||
val availableExtension = availableExtensions[it.pkgName]
|
||||
val hasUpdate = (availableExtension?.versionCode ?: 0) > it.versionCode
|
||||
|
||||
if (!hasUpdate && !it.isObsolete && !it.isUnofficial) return@mapNotNull null
|
||||
|
||||
"""
|
||||
- ${it.name}
|
||||
Installed: ${it.versionName} / Available: ${availableExtension?.versionName ?: "?"}
|
||||
Obsolete: ${it.isObsolete} / Unofficial: ${it.isUnofficial}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
return if (extensionInfoList.isNotEmpty()) {
|
||||
(listOf("Problematic extensions:") + extensionInfoList)
|
||||
.joinToString("\n")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAnimeExtensionsInfo(): String? {
|
||||
val availableExtensions = animeExtensionManager.availableExtensionsFlow.value.associateBy { it.pkgName }
|
||||
|
||||
val extensionInfoList = animeExtensionManager.installedExtensionsFlow.value
|
||||
.sortedBy { it.name }
|
||||
.mapNotNull {
|
||||
val availableExtension = availableExtensions[it.pkgName]
|
||||
val hasUpdate = (availableExtension?.versionCode ?: 0) > it.versionCode
|
||||
|
||||
if (!hasUpdate && !it.isObsolete && !it.isUnofficial) return@mapNotNull null
|
||||
|
||||
"""
|
||||
- ${it.name}
|
||||
Installed: ${it.versionName} / Available: ${availableExtension?.versionName ?: "?"}
|
||||
Obsolete: ${it.isObsolete} / Unofficial: ${it.isUnofficial}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
return if (extensionInfoList.isNotEmpty()) {
|
||||
(listOf("Problematic extensions:") + extensionInfoList)
|
||||
.joinToString("\n")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,12 +36,6 @@
|
|||
android:focusable="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/brightness_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/dialog_root"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -19,7 +19,7 @@ class NetworkPreferences(
|
|||
fun defaultUserAgent(): Preference<String> {
|
||||
return preferenceStore.getString(
|
||||
"default_user_agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package tachiyomi.data.category
|
||||
|
||||
import tachiyomi.domain.category.model.Category
|
||||
|
||||
val categoryMapper: (Long, String, Long, Long, Long) -> Category =
|
||||
{ id, name, order, flags, hidden ->
|
||||
Category(
|
||||
id = id,
|
||||
name = name,
|
||||
order = order,
|
||||
flags = flags,
|
||||
hidden = hidden == 1L,
|
||||
)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package tachiyomi.data.category.anime
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.data.category.categoryMapper
|
||||
import tachiyomi.data.handlers.anime.AnimeDatabaseHandler
|
||||
import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository
|
||||
import tachiyomi.domain.category.model.Category
|
||||
|
@ -13,46 +12,46 @@ class AnimeCategoryRepositoryImpl(
|
|||
) : AnimeCategoryRepository {
|
||||
|
||||
override suspend fun getAnimeCategory(id: Long): Category? {
|
||||
return handler.awaitOneOrNull { categoriesQueries.getCategory(id, categoryMapper) }
|
||||
return handler.awaitOneOrNull { categoriesQueries.getCategory(id, ::mapCategory) }
|
||||
}
|
||||
|
||||
override suspend fun getAllAnimeCategories(): List<Category> {
|
||||
return handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
|
||||
return handler.awaitList { categoriesQueries.getCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override suspend fun getAllVisibleAnimeCategories(): List<Category> {
|
||||
return handler.awaitList { categoriesQueries.getVisibleCategories(categoryMapper) }
|
||||
return handler.awaitList { categoriesQueries.getVisibleCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override fun getAllAnimeCategoriesAsFlow(): Flow<List<Category>> {
|
||||
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
|
||||
return handler.subscribeToList { categoriesQueries.getCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override fun getAllVisibleAnimeCategoriesAsFlow(): Flow<List<Category>> {
|
||||
return handler.subscribeToList { categoriesQueries.getVisibleCategories(categoryMapper) }
|
||||
return handler.subscribeToList { categoriesQueries.getVisibleCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override suspend fun getCategoriesByAnimeId(animeId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getVisibleCategoriesByAnimeId(animeId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getVisibleCategoriesByAnimeId(animeId, categoryMapper)
|
||||
categoriesQueries.getVisibleCategoriesByAnimeId(animeId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>> {
|
||||
return handler.subscribeToList {
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, categoryMapper)
|
||||
categoriesQueries.getCategoriesByAnimeId(animeId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVisibleCategoriesByAnimeIdAsFlow(animeId: Long): Flow<List<Category>> {
|
||||
return handler.subscribeToList {
|
||||
categoriesQueries.getVisibleCategoriesByAnimeId(animeId, categoryMapper)
|
||||
categoriesQueries.getVisibleCategoriesByAnimeId(animeId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,4 +102,20 @@ class AnimeCategoryRepositoryImpl(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapCategory(
|
||||
id: Long,
|
||||
name: String,
|
||||
order: Long,
|
||||
flags: Long,
|
||||
hidden: Long,
|
||||
): Category {
|
||||
return Category(
|
||||
id = id,
|
||||
name = name,
|
||||
order = order,
|
||||
flags = flags,
|
||||
hidden = hidden == 1L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package tachiyomi.data.category.manga
|
|||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.data.Database
|
||||
import tachiyomi.data.category.categoryMapper
|
||||
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
||||
import tachiyomi.domain.category.manga.repository.MangaCategoryRepository
|
||||
import tachiyomi.domain.category.model.Category
|
||||
|
@ -13,46 +12,46 @@ class MangaCategoryRepositoryImpl(
|
|||
) : MangaCategoryRepository {
|
||||
|
||||
override suspend fun getMangaCategory(id: Long): Category? {
|
||||
return handler.awaitOneOrNull { categoriesQueries.getCategory(id, categoryMapper) }
|
||||
return handler.awaitOneOrNull { categoriesQueries.getCategory(id, ::mapCategory) }
|
||||
}
|
||||
|
||||
override suspend fun getAllMangaCategories(): List<Category> {
|
||||
return handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
|
||||
return handler.awaitList { categoriesQueries.getCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override suspend fun getAllVisibleMangaCategories(): List<Category> {
|
||||
return handler.awaitList { categoriesQueries.getVisibleCategories(categoryMapper) }
|
||||
return handler.awaitList { categoriesQueries.getVisibleCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override fun getAllMangaCategoriesAsFlow(): Flow<List<Category>> {
|
||||
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
|
||||
return handler.subscribeToList { categoriesQueries.getCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override fun getAllVisibleMangaCategoriesAsFlow(): Flow<List<Category>> {
|
||||
return handler.subscribeToList { categoriesQueries.getVisibleCategories(categoryMapper) }
|
||||
return handler.subscribeToList { categoriesQueries.getVisibleCategories(::mapCategory) }
|
||||
}
|
||||
|
||||
override suspend fun getCategoriesByMangaId(mangaId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getVisibleCategoriesByMangaId(mangaId: Long): List<Category> {
|
||||
return handler.awaitList {
|
||||
categoriesQueries.getVisibleCategoriesByMangaId(mangaId, categoryMapper)
|
||||
categoriesQueries.getVisibleCategoriesByMangaId(mangaId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>> {
|
||||
return handler.subscribeToList {
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
|
||||
categoriesQueries.getCategoriesByMangaId(mangaId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVisibleCategoriesByMangaIdAsFlow(mangaId: Long): Flow<List<Category>> {
|
||||
return handler.subscribeToList {
|
||||
categoriesQueries.getVisibleCategoriesByMangaId(mangaId, categoryMapper)
|
||||
categoriesQueries.getVisibleCategoriesByMangaId(mangaId, ::mapCategory)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,4 +102,20 @@ class MangaCategoryRepositoryImpl(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapCategory(
|
||||
id: Long,
|
||||
name: String,
|
||||
order: Long,
|
||||
flags: Long,
|
||||
hidden: Long,
|
||||
): Category {
|
||||
return Category(
|
||||
id = id,
|
||||
name = name,
|
||||
order = order,
|
||||
flags = flags,
|
||||
hidden = hidden == 1L,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,120 +4,116 @@ import eu.kanade.tachiyomi.model.UpdateStrategy
|
|||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.library.anime.LibraryAnime
|
||||
|
||||
val animeMapper: (
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
String?,
|
||||
String?,
|
||||
List<String>?,
|
||||
String,
|
||||
Long,
|
||||
String?,
|
||||
Boolean,
|
||||
Long?,
|
||||
Long?,
|
||||
Boolean,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
UpdateStrategy,
|
||||
Long,
|
||||
Long,
|
||||
Long?,
|
||||
) -> Anime =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, episodeFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt ->
|
||||
Anime(
|
||||
id = id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
nextUpdate = nextUpdate ?: 0,
|
||||
fetchInterval = calculateInterval.toInt(),
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
episodeFlags = episodeFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
updateStrategy = updateStrategy,
|
||||
initialized = initialized,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
favoriteModifiedAt = favoriteModifiedAt,
|
||||
)
|
||||
}
|
||||
object AnimeMapper {
|
||||
fun mapAnime(
|
||||
id: Long,
|
||||
source: Long,
|
||||
url: String,
|
||||
artist: String?,
|
||||
author: String?,
|
||||
description: String?,
|
||||
genre: List<String>?,
|
||||
title: String,
|
||||
status: Long,
|
||||
thumbnailUrl: String?,
|
||||
favorite: Boolean,
|
||||
lastUpdate: Long?,
|
||||
nextUpdate: Long?,
|
||||
initialized: Boolean,
|
||||
viewerFlags: Long,
|
||||
chapterFlags: Long,
|
||||
coverLastModified: Long,
|
||||
dateAdded: Long,
|
||||
updateStrategy: UpdateStrategy,
|
||||
calculateInterval: Long,
|
||||
lastModifiedAt: Long,
|
||||
favoriteModifiedAt: Long?,
|
||||
): Anime = Anime(
|
||||
id = id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
nextUpdate = nextUpdate ?: 0,
|
||||
fetchInterval = calculateInterval.toInt(),
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
episodeFlags = chapterFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
updateStrategy = updateStrategy,
|
||||
initialized = initialized,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
favoriteModifiedAt = favoriteModifiedAt,
|
||||
)
|
||||
|
||||
val libraryAnime: (
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
String?,
|
||||
String?,
|
||||
List<String>?,
|
||||
String,
|
||||
Long,
|
||||
String?,
|
||||
Boolean,
|
||||
Long?,
|
||||
Long?,
|
||||
Boolean,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
UpdateStrategy,
|
||||
Long,
|
||||
Long,
|
||||
Long?,
|
||||
Long,
|
||||
Double,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Double,
|
||||
Long,
|
||||
) -> LibraryAnime =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, episodeFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, seenCount, latestUpload, episodeFetchedAt, lastSeen, bookmarkCount, category ->
|
||||
LibraryAnime(
|
||||
anime = animeMapper(
|
||||
id,
|
||||
source,
|
||||
url,
|
||||
artist,
|
||||
author,
|
||||
description,
|
||||
genre,
|
||||
title,
|
||||
status,
|
||||
thumbnailUrl,
|
||||
favorite,
|
||||
lastUpdate,
|
||||
nextUpdate,
|
||||
initialized,
|
||||
viewerFlags,
|
||||
episodeFlags,
|
||||
coverLastModified,
|
||||
dateAdded,
|
||||
updateStrategy,
|
||||
calculateInterval,
|
||||
lastModifiedAt,
|
||||
favoriteModifiedAt,
|
||||
),
|
||||
category = category,
|
||||
totalEpisodes = totalCount,
|
||||
seenCount = seenCount.toLong(),
|
||||
bookmarkCount = bookmarkCount.toLong(),
|
||||
latestUpload = latestUpload,
|
||||
episodeFetchedAt = episodeFetchedAt,
|
||||
lastSeen = lastSeen,
|
||||
)
|
||||
}
|
||||
fun mapLibraryAnime(
|
||||
id: Long,
|
||||
source: Long,
|
||||
url: String,
|
||||
artist: String?,
|
||||
author: String?,
|
||||
description: String?,
|
||||
genre: List<String>?,
|
||||
title: String,
|
||||
status: Long,
|
||||
thumbnailUrl: String?,
|
||||
favorite: Boolean,
|
||||
lastUpdate: Long?,
|
||||
nextUpdate: Long?,
|
||||
initialized: Boolean,
|
||||
viewerFlags: Long,
|
||||
chapterFlags: Long,
|
||||
coverLastModified: Long,
|
||||
dateAdded: Long,
|
||||
updateStrategy: UpdateStrategy,
|
||||
calculateInterval: Long,
|
||||
lastModifiedAt: Long,
|
||||
favoriteModifiedAt: Long?,
|
||||
totalCount: Long,
|
||||
seenCount: Double,
|
||||
latestUpload: Long,
|
||||
episodeFetchedAt: Long,
|
||||
lastSeen: Long,
|
||||
bookmarkCount: Double,
|
||||
category: Long,
|
||||
): LibraryAnime = LibraryAnime(
|
||||
anime = mapAnime(
|
||||
id,
|
||||
source,
|
||||
url,
|
||||
artist,
|
||||
author,
|
||||
description,
|
||||
genre,
|
||||
title,
|
||||
status,
|
||||
thumbnailUrl,
|
||||
favorite,
|
||||
lastUpdate,
|
||||
nextUpdate,
|
||||
initialized,
|
||||
viewerFlags,
|
||||
chapterFlags,
|
||||
coverLastModified,
|
||||
dateAdded,
|
||||
updateStrategy,
|
||||
calculateInterval,
|
||||
lastModifiedAt,
|
||||
favoriteModifiedAt,
|
||||
),
|
||||
category = category,
|
||||
totalEpisodes = totalCount,
|
||||
seenCount = seenCount.toLong(),
|
||||
bookmarkCount = bookmarkCount.toLong(),
|
||||
latestUpload = latestUpload,
|
||||
episodeFetchedAt = episodeFetchedAt,
|
||||
lastSeen = lastSeen,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ class AnimeRepositoryImpl(
|
|||
) : AnimeRepository {
|
||||
|
||||
override suspend fun getAnimeById(id: Long): Anime {
|
||||
return handler.awaitOne { animesQueries.getAnimeById(id, animeMapper) }
|
||||
return handler.awaitOne { animesQueries.getAnimeById(id, AnimeMapper::mapAnime) }
|
||||
}
|
||||
|
||||
override suspend fun getAnimeByIdAsFlow(id: Long): Flow<Anime> {
|
||||
return handler.subscribeToOne { animesQueries.getAnimeById(id, animeMapper) }
|
||||
return handler.subscribeToOne { animesQueries.getAnimeById(id, AnimeMapper::mapAnime) }
|
||||
}
|
||||
|
||||
override suspend fun getAnimeByUrlAndSourceId(url: String, sourceId: Long): Anime? {
|
||||
|
@ -29,7 +29,7 @@ class AnimeRepositoryImpl(
|
|||
animesQueries.getAnimeByUrlAndSource(
|
||||
url,
|
||||
sourceId,
|
||||
animeMapper,
|
||||
AnimeMapper::mapAnime,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -39,30 +39,30 @@ class AnimeRepositoryImpl(
|
|||
animesQueries.getAnimeByUrlAndSource(
|
||||
url,
|
||||
sourceId,
|
||||
animeMapper,
|
||||
AnimeMapper::mapAnime,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAnimeFavorites(): List<Anime> {
|
||||
return handler.awaitList { animesQueries.getFavorites(animeMapper) }
|
||||
return handler.awaitList { animesQueries.getFavorites(AnimeMapper::mapAnime) }
|
||||
}
|
||||
|
||||
override suspend fun getLibraryAnime(): List<LibraryAnime> {
|
||||
return handler.awaitList { animelibViewQueries.animelib(libraryAnime) }
|
||||
return handler.awaitList { animelibViewQueries.animelib(AnimeMapper::mapLibraryAnime) }
|
||||
}
|
||||
|
||||
override fun getLibraryAnimeAsFlow(): Flow<List<LibraryAnime>> {
|
||||
return handler.subscribeToList { animelibViewQueries.animelib(libraryAnime) }
|
||||
return handler.subscribeToList { animelibViewQueries.animelib(AnimeMapper::mapLibraryAnime) }
|
||||
}
|
||||
|
||||
override fun getAnimeFavoritesBySourceId(sourceId: Long): Flow<List<Anime>> {
|
||||
return handler.subscribeToList { animesQueries.getFavoriteBySourceId(sourceId, animeMapper) }
|
||||
return handler.subscribeToList { animesQueries.getFavoriteBySourceId(sourceId, AnimeMapper::mapAnime) }
|
||||
}
|
||||
|
||||
override suspend fun getDuplicateLibraryAnime(id: Long, title: String): List<Anime> {
|
||||
return handler.awaitList {
|
||||
animesQueries.getDuplicateLibraryAnime(title, id, animeMapper)
|
||||
animesQueries.getDuplicateLibraryAnime(title, id, AnimeMapper::mapAnime)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,120 +4,116 @@ import eu.kanade.tachiyomi.model.UpdateStrategy
|
|||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.library.manga.LibraryManga
|
||||
|
||||
val mangaMapper: (
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
String?,
|
||||
String?,
|
||||
List<String>?,
|
||||
String,
|
||||
Long,
|
||||
String?,
|
||||
Boolean,
|
||||
Long?,
|
||||
Long?,
|
||||
Boolean,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
UpdateStrategy,
|
||||
Long,
|
||||
Long,
|
||||
Long?,
|
||||
) -> Manga =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt ->
|
||||
Manga(
|
||||
id = id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
nextUpdate = nextUpdate ?: 0,
|
||||
fetchInterval = calculateInterval.toInt(),
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
chapterFlags = chapterFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
updateStrategy = updateStrategy,
|
||||
initialized = initialized,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
favoriteModifiedAt = favoriteModifiedAt,
|
||||
)
|
||||
}
|
||||
object MangaMapper {
|
||||
fun mapManga(
|
||||
id: Long,
|
||||
source: Long,
|
||||
url: String,
|
||||
artist: String?,
|
||||
author: String?,
|
||||
description: String?,
|
||||
genre: List<String>?,
|
||||
title: String,
|
||||
status: Long,
|
||||
thumbnailUrl: String?,
|
||||
favorite: Boolean,
|
||||
lastUpdate: Long?,
|
||||
nextUpdate: Long?,
|
||||
initialized: Boolean,
|
||||
viewerFlags: Long,
|
||||
chapterFlags: Long,
|
||||
coverLastModified: Long,
|
||||
dateAdded: Long,
|
||||
updateStrategy: UpdateStrategy,
|
||||
calculateInterval: Long,
|
||||
lastModifiedAt: Long,
|
||||
favoriteModifiedAt: Long?,
|
||||
): Manga = Manga(
|
||||
id = id,
|
||||
source = source,
|
||||
favorite = favorite,
|
||||
lastUpdate = lastUpdate ?: 0,
|
||||
nextUpdate = nextUpdate ?: 0,
|
||||
fetchInterval = calculateInterval.toInt(),
|
||||
dateAdded = dateAdded,
|
||||
viewerFlags = viewerFlags,
|
||||
chapterFlags = chapterFlags,
|
||||
coverLastModified = coverLastModified,
|
||||
url = url,
|
||||
title = title,
|
||||
artist = artist,
|
||||
author = author,
|
||||
description = description,
|
||||
genre = genre,
|
||||
status = status,
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
updateStrategy = updateStrategy,
|
||||
initialized = initialized,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
favoriteModifiedAt = favoriteModifiedAt,
|
||||
)
|
||||
|
||||
val libraryManga: (
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
String?,
|
||||
String?,
|
||||
List<String>?,
|
||||
String,
|
||||
Long,
|
||||
String?,
|
||||
Boolean,
|
||||
Long?,
|
||||
Long?,
|
||||
Boolean,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
UpdateStrategy,
|
||||
Long,
|
||||
Long,
|
||||
Long?,
|
||||
Long,
|
||||
Double,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Double,
|
||||
Long,
|
||||
) -> LibraryManga =
|
||||
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, calculateInterval, lastModifiedAt, favoriteModifiedAt, totalCount, readCount, latestUpload, chapterFetchedAt, lastRead, bookmarkCount, category ->
|
||||
LibraryManga(
|
||||
manga = mangaMapper(
|
||||
id,
|
||||
source,
|
||||
url,
|
||||
artist,
|
||||
author,
|
||||
description,
|
||||
genre,
|
||||
title,
|
||||
status,
|
||||
thumbnailUrl,
|
||||
favorite,
|
||||
lastUpdate,
|
||||
nextUpdate,
|
||||
initialized,
|
||||
viewerFlags,
|
||||
chapterFlags,
|
||||
coverLastModified,
|
||||
dateAdded,
|
||||
updateStrategy,
|
||||
calculateInterval,
|
||||
lastModifiedAt,
|
||||
favoriteModifiedAt,
|
||||
),
|
||||
category = category,
|
||||
totalChapters = totalCount,
|
||||
readCount = readCount.toLong(),
|
||||
bookmarkCount = bookmarkCount.toLong(),
|
||||
latestUpload = latestUpload,
|
||||
chapterFetchedAt = chapterFetchedAt,
|
||||
lastRead = lastRead,
|
||||
)
|
||||
}
|
||||
fun mapLibraryManga(
|
||||
id: Long,
|
||||
source: Long,
|
||||
url: String,
|
||||
artist: String?,
|
||||
author: String?,
|
||||
description: String?,
|
||||
genre: List<String>?,
|
||||
title: String,
|
||||
status: Long,
|
||||
thumbnailUrl: String?,
|
||||
favorite: Boolean,
|
||||
lastUpdate: Long?,
|
||||
nextUpdate: Long?,
|
||||
initialized: Boolean,
|
||||
viewerFlags: Long,
|
||||
chapterFlags: Long,
|
||||
coverLastModified: Long,
|
||||
dateAdded: Long,
|
||||
updateStrategy: UpdateStrategy,
|
||||
calculateInterval: Long,
|
||||
lastModifiedAt: Long,
|
||||
favoriteModifiedAt: Long?,
|
||||
totalCount: Long,
|
||||
readCount: Double,
|
||||
latestUpload: Long,
|
||||
chapterFetchedAt: Long,
|
||||
lastRead: Long,
|
||||
bookmarkCount: Double,
|
||||
category: Long,
|
||||
): LibraryManga = LibraryManga(
|
||||
manga = mapManga(
|
||||
id,
|
||||
source,
|
||||
url,
|
||||
artist,
|
||||
author,
|
||||
description,
|
||||
genre,
|
||||
title,
|
||||
status,
|
||||
thumbnailUrl,
|
||||
favorite,
|
||||
lastUpdate,
|
||||
nextUpdate,
|
||||
initialized,
|
||||
viewerFlags,
|
||||
chapterFlags,
|
||||
coverLastModified,
|
||||
dateAdded,
|
||||
updateStrategy,
|
||||
calculateInterval,
|
||||
lastModifiedAt,
|
||||
favoriteModifiedAt,
|
||||
),
|
||||
category = category,
|
||||
totalChapters = totalCount,
|
||||
readCount = readCount.toLong(),
|
||||
bookmarkCount = bookmarkCount.toLong(),
|
||||
latestUpload = latestUpload,
|
||||
chapterFetchedAt = chapterFetchedAt,
|
||||
lastRead = lastRead,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ class MangaRepositoryImpl(
|
|||
) : MangaRepository {
|
||||
|
||||
override suspend fun getMangaById(id: Long): Manga {
|
||||
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) }
|
||||
return handler.awaitOne { mangasQueries.getMangaById(id, MangaMapper::mapManga) }
|
||||
}
|
||||
|
||||
override suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> {
|
||||
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
|
||||
return handler.subscribeToOne { mangasQueries.getMangaById(id, MangaMapper::mapManga) }
|
||||
}
|
||||
|
||||
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
|
||||
|
@ -29,7 +29,7 @@ class MangaRepositoryImpl(
|
|||
mangasQueries.getMangaByUrlAndSource(
|
||||
url,
|
||||
sourceId,
|
||||
mangaMapper,
|
||||
MangaMapper::mapManga,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -39,30 +39,30 @@ class MangaRepositoryImpl(
|
|||
mangasQueries.getMangaByUrlAndSource(
|
||||
url,
|
||||
sourceId,
|
||||
mangaMapper,
|
||||
MangaMapper::mapManga,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMangaFavorites(): List<Manga> {
|
||||
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) }
|
||||
return handler.awaitList { mangasQueries.getFavorites(MangaMapper::mapManga) }
|
||||
}
|
||||
|
||||
override suspend fun getLibraryManga(): List<LibraryManga> {
|
||||
return handler.awaitList { libraryViewQueries.library(libraryManga) }
|
||||
return handler.awaitList { libraryViewQueries.library(MangaMapper::mapLibraryManga) }
|
||||
}
|
||||
|
||||
override fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>> {
|
||||
return handler.subscribeToList { libraryViewQueries.library(libraryManga) }
|
||||
return handler.subscribeToList { libraryViewQueries.library(MangaMapper::mapLibraryManga) }
|
||||
}
|
||||
|
||||
override fun getMangaFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> {
|
||||
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) }
|
||||
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, MangaMapper::mapManga) }
|
||||
}
|
||||
|
||||
override suspend fun getDuplicateLibraryManga(id: Long, title: String): List<Manga> {
|
||||
return handler.awaitList {
|
||||
mangasQueries.getDuplicateLibraryManga(title, id, mangaMapper)
|
||||
mangasQueries.getDuplicateLibraryManga(title, id, MangaMapper::mapManga)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,28 +5,29 @@ import tachiyomi.domain.history.anime.model.AnimeHistory
|
|||
import tachiyomi.domain.history.anime.model.AnimeHistoryWithRelations
|
||||
import java.util.Date
|
||||
|
||||
val animeHistoryMapper: (Long, Long, Date?) -> AnimeHistory = { id, episodeId, seenAt ->
|
||||
AnimeHistory(
|
||||
object AnimeHistoryMapper {
|
||||
fun mapAnimeHistory(
|
||||
id: Long,
|
||||
episodeId: Long,
|
||||
seenAt: Date?,
|
||||
): AnimeHistory = AnimeHistory(
|
||||
id = id,
|
||||
episodeId = episodeId,
|
||||
seenAt = seenAt,
|
||||
)
|
||||
}
|
||||
|
||||
val animeHistoryWithRelationsMapper: (
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
Long,
|
||||
Boolean,
|
||||
Long,
|
||||
Double,
|
||||
Date?,
|
||||
) -> AnimeHistoryWithRelations = {
|
||||
historyId, animeId, episodeId, title, thumbnailUrl, sourceId, isFavorite, coverLastModified, episodeNumber, seenAt ->
|
||||
AnimeHistoryWithRelations(
|
||||
fun mapAnimeHistoryWithRelations(
|
||||
historyId: Long,
|
||||
animeId: Long,
|
||||
episodeId: Long,
|
||||
title: String,
|
||||
thumbnailUrl: String?,
|
||||
sourceId: Long,
|
||||
isFavorite: Boolean,
|
||||
coverLastModified: Long,
|
||||
episodeNumber: Double,
|
||||
seenAt: Date?,
|
||||
): AnimeHistoryWithRelations = AnimeHistoryWithRelations(
|
||||
id = historyId,
|
||||
episodeId = episodeId,
|
||||
animeId = animeId,
|
||||
|
|
|
@ -15,13 +15,13 @@ class AnimeHistoryRepositoryImpl(
|
|||
|
||||
override fun getAnimeHistory(query: String): Flow<List<AnimeHistoryWithRelations>> {
|
||||
return handler.subscribeToList {
|
||||
animehistoryViewQueries.animehistory(query, animeHistoryWithRelationsMapper)
|
||||
animehistoryViewQueries.animehistory(query, AnimeHistoryMapper::mapAnimeHistoryWithRelations)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getLastAnimeHistory(): AnimeHistoryWithRelations? {
|
||||
return handler.awaitOneOrNull {
|
||||
animehistoryViewQueries.getLatestAnimeHistory(animeHistoryWithRelationsMapper)
|
||||
animehistoryViewQueries.getLatestAnimeHistory(AnimeHistoryMapper::mapAnimeHistoryWithRelations)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ class AnimeHistoryRepositoryImpl(
|
|||
return handler.awaitList {
|
||||
animehistoryQueries.getHistoryByAnimeId(
|
||||
animeId,
|
||||
animeHistoryMapper,
|
||||
AnimeHistoryMapper::mapAnimeHistory,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,30 +5,32 @@ import tachiyomi.domain.history.manga.model.MangaHistory
|
|||
import tachiyomi.domain.history.manga.model.MangaHistoryWithRelations
|
||||
import java.util.Date
|
||||
|
||||
val mangaHistoryMapper: (Long, Long, Date?, Long) -> MangaHistory = { id, chapterId, readAt, readDuration ->
|
||||
MangaHistory(
|
||||
object MangaHistoryMapper {
|
||||
fun mapMangaHistory(
|
||||
id: Long,
|
||||
chapterId: Long,
|
||||
readAt: Date?,
|
||||
readDuration: Long,
|
||||
): MangaHistory = MangaHistory(
|
||||
id = id,
|
||||
chapterId = chapterId,
|
||||
readAt = readAt,
|
||||
readDuration = readDuration,
|
||||
)
|
||||
}
|
||||
|
||||
val mangaHistoryWithRelationsMapper: (
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
Long,
|
||||
Boolean,
|
||||
Long,
|
||||
Double,
|
||||
Date?,
|
||||
Long,
|
||||
) -> MangaHistoryWithRelations = {
|
||||
historyId, mangaId, chapterId, title, thumbnailUrl, sourceId, isFavorite, coverLastModified, chapterNumber, readAt, readDuration ->
|
||||
MangaHistoryWithRelations(
|
||||
fun mapMangaHistoryWithRelations(
|
||||
historyId: Long,
|
||||
mangaId: Long,
|
||||
chapterId: Long,
|
||||
title: String,
|
||||
thumbnailUrl: String?,
|
||||
sourceId: Long,
|
||||
isFavorite: Boolean,
|
||||
coverLastModified: Long,
|
||||
chapterNumber: Double,
|
||||
readAt: Date?,
|
||||
readDuration: Long,
|
||||
): MangaHistoryWithRelations = MangaHistoryWithRelations(
|
||||
id = historyId,
|
||||
chapterId = chapterId,
|
||||
mangaId = mangaId,
|
||||
|
|
|
@ -15,13 +15,13 @@ class MangaHistoryRepositoryImpl(
|
|||
|
||||
override fun getMangaHistory(query: String): Flow<List<MangaHistoryWithRelations>> {
|
||||
return handler.subscribeToList {
|
||||
historyViewQueries.history(query, mangaHistoryWithRelationsMapper)
|
||||
historyViewQueries.history(query, MangaHistoryMapper::mapMangaHistoryWithRelations)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getLastMangaHistory(): MangaHistoryWithRelations? {
|
||||
return handler.awaitOneOrNull {
|
||||
historyViewQueries.getLatestHistory(mangaHistoryWithRelationsMapper)
|
||||
historyViewQueries.getLatestHistory(MangaHistoryMapper::mapMangaHistoryWithRelations)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class MangaHistoryRepositoryImpl(
|
|||
}
|
||||
|
||||
override suspend fun getHistoryByMangaId(mangaId: Long): List<MangaHistory> {
|
||||
return handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, mangaHistoryMapper) }
|
||||
return handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, MangaHistoryMapper::mapMangaHistory) }
|
||||
}
|
||||
|
||||
override suspend fun resetMangaHistory(historyId: Long) {
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package tachiyomi.data.items.chapter
|
||||
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
|
||||
val chapterMapper: (
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String,
|
||||
String?,
|
||||
Boolean,
|
||||
Boolean,
|
||||
Long,
|
||||
Double,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
) -> Chapter =
|
||||
{ id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload, lastModifiedAt ->
|
||||
Chapter(
|
||||
id = id,
|
||||
mangaId = mangaId,
|
||||
read = read,
|
||||
bookmark = bookmark,
|
||||
lastPageRead = lastPageRead,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = url,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
chapterNumber = chapterNumber,
|
||||
scanlator = scanlator,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
)
|
||||
}
|
|
@ -77,27 +77,27 @@ class ChapterRepositoryImpl(
|
|||
}
|
||||
|
||||
override suspend fun getChapterByMangaId(mangaId: Long): List<Chapter> {
|
||||
return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
|
||||
return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, ::mapChapter) }
|
||||
}
|
||||
|
||||
override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List<Chapter> {
|
||||
return handler.awaitList {
|
||||
chaptersQueries.getBookmarkedChaptersByMangaId(
|
||||
mangaId,
|
||||
chapterMapper,
|
||||
::mapChapter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getChapterById(id: Long): Chapter? {
|
||||
return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, chapterMapper) }
|
||||
return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, ::mapChapter) }
|
||||
}
|
||||
|
||||
override suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> {
|
||||
return handler.subscribeToList {
|
||||
chaptersQueries.getChaptersByMangaId(
|
||||
mangaId,
|
||||
chapterMapper,
|
||||
::mapChapter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -107,8 +107,38 @@ class ChapterRepositoryImpl(
|
|||
chaptersQueries.getChapterByUrlAndMangaId(
|
||||
url,
|
||||
mangaId,
|
||||
chapterMapper,
|
||||
::mapChapter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapChapter(
|
||||
id: Long,
|
||||
mangaId: Long,
|
||||
url: String,
|
||||
name: String,
|
||||
scanlator: String?,
|
||||
read: Boolean,
|
||||
bookmark: Boolean,
|
||||
lastPageRead: Long,
|
||||
chapterNumber: Double,
|
||||
sourceOrder: Long,
|
||||
dateFetch: Long,
|
||||
dateUpload: Long,
|
||||
lastModifiedAt: Long,
|
||||
): Chapter = Chapter(
|
||||
id = id,
|
||||
mangaId = mangaId,
|
||||
read = read,
|
||||
bookmark = bookmark,
|
||||
lastPageRead = lastPageRead,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = url,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
chapterNumber = chapterNumber,
|
||||
scanlator = scanlator,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package tachiyomi.data.items.episode
|
||||
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
|
||||
val episodeMapper: (
|
||||
Long,
|
||||
Long,
|
||||
String,
|
||||
String,
|
||||
String?,
|
||||
Boolean,
|
||||
Boolean,
|
||||
Long,
|
||||
Long,
|
||||
Double,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
) -> Episode =
|
||||
{ id, animeId, url, name, scanlator, seen, bookmark, lastSecondSeen, totalSeconds, episodeNumber, sourceOrder, dateFetch, dateUpload, lastModifiedAt ->
|
||||
Episode(
|
||||
id = id,
|
||||
animeId = animeId,
|
||||
seen = seen,
|
||||
bookmark = bookmark,
|
||||
lastSecondSeen = lastSecondSeen,
|
||||
totalSeconds = totalSeconds,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = url,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
episodeNumber = episodeNumber,
|
||||
scanlator = scanlator,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
)
|
||||
}
|
|
@ -79,27 +79,27 @@ class EpisodeRepositoryImpl(
|
|||
}
|
||||
|
||||
override suspend fun getEpisodeByAnimeId(animeId: Long): List<Episode> {
|
||||
return handler.awaitList { episodesQueries.getEpisodesByAnimeId(animeId, episodeMapper) }
|
||||
return handler.awaitList { episodesQueries.getEpisodesByAnimeId(animeId, ::mapEpisode) }
|
||||
}
|
||||
|
||||
override suspend fun getBookmarkedEpisodesByAnimeId(animeId: Long): List<Episode> {
|
||||
return handler.awaitList {
|
||||
episodesQueries.getBookmarkedEpisodesByAnimeId(
|
||||
animeId,
|
||||
episodeMapper,
|
||||
::mapEpisode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getEpisodeById(id: Long): Episode? {
|
||||
return handler.awaitOneOrNull { episodesQueries.getEpisodeById(id, episodeMapper) }
|
||||
return handler.awaitOneOrNull { episodesQueries.getEpisodeById(id, ::mapEpisode) }
|
||||
}
|
||||
|
||||
override suspend fun getEpisodeByAnimeIdAsFlow(animeId: Long): Flow<List<Episode>> {
|
||||
return handler.subscribeToList {
|
||||
episodesQueries.getEpisodesByAnimeId(
|
||||
animeId,
|
||||
episodeMapper,
|
||||
::mapEpisode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -109,8 +109,40 @@ class EpisodeRepositoryImpl(
|
|||
episodesQueries.getEpisodeByUrlAndAnimeId(
|
||||
url,
|
||||
animeId,
|
||||
episodeMapper,
|
||||
::mapEpisode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapEpisode(
|
||||
id: Long,
|
||||
animeId: Long,
|
||||
url: String,
|
||||
name: String,
|
||||
scanlator: String?,
|
||||
seen: Boolean,
|
||||
bookmark: Boolean,
|
||||
lastSecondSeen: Long,
|
||||
totalSeconds: Long,
|
||||
episodeNumber: Double,
|
||||
sourceOrder: Long,
|
||||
dateFetch: Long,
|
||||
dateUpload: Long,
|
||||
lastModifiedAt: Long,
|
||||
): Episode = Episode(
|
||||
id = id,
|
||||
animeId = animeId ,
|
||||
seen = seen ,
|
||||
bookmark = bookmark,
|
||||
lastSecondSeen = lastSecondSeen ,
|
||||
totalSeconds = totalSeconds,
|
||||
dateFetch = dateFetch,
|
||||
sourceOrder = sourceOrder,
|
||||
url = url,
|
||||
name = name,
|
||||
dateUpload = dateUpload,
|
||||
episodeNumber = episodeNumber,
|
||||
scanlator = scanlator,
|
||||
lastModifiedAt = lastModifiedAt,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package tachiyomi.data.source.anime
|
||||
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||
|
||||
val animeSourceMapper: (eu.kanade.tachiyomi.animesource.AnimeSource) -> AnimeSource = { source ->
|
||||
AnimeSource(
|
||||
id = source.id,
|
||||
lang = source.lang,
|
||||
name = source.name,
|
||||
supportsLatest = false,
|
||||
isStub = false,
|
||||
)
|
||||
}
|
||||
|
||||
val animeSourceDataMapper: (Long, String, String) -> StubAnimeSource = { id, lang, name ->
|
||||
StubAnimeSource(id, lang, name)
|
||||
}
|
|
@ -1,48 +1,50 @@
|
|||
package tachiyomi.data.source.anime
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.handlers.anime.AnimeDatabaseHandler
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.domain.source.anime.model.AnimeSourceWithCount
|
||||
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||
import tachiyomi.domain.source.anime.repository.AnimeSourcePagingSourceType
|
||||
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource as DomainSource
|
||||
|
||||
class AnimeSourceRepositoryImpl(
|
||||
private val sourceManager: AnimeSourceManager,
|
||||
private val handler: AnimeDatabaseHandler,
|
||||
) : AnimeSourceRepository {
|
||||
|
||||
override fun getAnimeSources(): Flow<List<AnimeSource>> {
|
||||
override fun getAnimeSources(): Flow<List<DomainSource>> {
|
||||
return sourceManager.catalogueSources.map { sources ->
|
||||
sources.map {
|
||||
animeSourceMapper(it).copy(
|
||||
mapSourceToDomainSource(it).copy(
|
||||
supportsLatest = it.supportsLatest,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnlineAnimeSources(): Flow<List<AnimeSource>> {
|
||||
override fun getOnlineAnimeSources(): Flow<List<DomainSource>> {
|
||||
return sourceManager.catalogueSources.map { sources ->
|
||||
sources
|
||||
.filterIsInstance<AnimeHttpSource>()
|
||||
.map(animeSourceMapper)
|
||||
.map(::mapSourceToDomainSource)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAnimeSourcesWithFavoriteCount(): Flow<List<Pair<AnimeSource, Long>>> {
|
||||
val sourceIdWithFavoriteCount = handler.subscribeToList { animesQueries.getAnimeSourceIdWithFavoriteCount() }
|
||||
override fun getAnimeSourcesWithFavoriteCount(): Flow<List<Pair<DomainSource, Long>>> {
|
||||
val sourceIdWithFavoriteCount =
|
||||
handler.subscribeToList { animesQueries.getAnimeSourceIdWithFavoriteCount() }
|
||||
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
||||
sourceIdsWithCount
|
||||
.map { (sourceId, count) ->
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
val domainSource = animeSourceMapper(source).copy(
|
||||
val domainSource = mapSourceToDomainSource(source).copy(
|
||||
isStub = source is StubAnimeSource,
|
||||
)
|
||||
domainSource to count
|
||||
|
@ -51,11 +53,12 @@ class AnimeSourceRepositoryImpl(
|
|||
}
|
||||
|
||||
override fun getSourcesWithNonLibraryAnime(): Flow<List<AnimeSourceWithCount>> {
|
||||
val sourceIdWithNonLibraryAnime = handler.subscribeToList { animesQueries.getSourceIdsWithNonLibraryAnime() }
|
||||
val sourceIdWithNonLibraryAnime =
|
||||
handler.subscribeToList { animesQueries.getSourceIdsWithNonLibraryAnime() }
|
||||
return sourceIdWithNonLibraryAnime.map { sourceId ->
|
||||
sourceId.map { (sourceId, count) ->
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
val domainSource = animeSourceMapper(source).copy(
|
||||
val domainSource = mapSourceToDomainSource(source).copy(
|
||||
isStub = source is StubAnimeSource,
|
||||
)
|
||||
AnimeSourceWithCount(domainSource, count)
|
||||
|
@ -81,4 +84,12 @@ class AnimeSourceRepositoryImpl(
|
|||
val source = sourceManager.get(sourceId) as AnimeCatalogueSource
|
||||
return AnimeSourceLatestPagingSource(source)
|
||||
}
|
||||
|
||||
private fun mapSourceToDomainSource(source: AnimeSource): DomainSource = DomainSource(
|
||||
id = source.id,
|
||||
lang = source.lang,
|
||||
name = source.name,
|
||||
supportsLatest = false,
|
||||
isStub = false,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ class AnimeStubSourceRepositoryImpl(
|
|||
) : AnimeStubSourceRepository {
|
||||
|
||||
override fun subscribeAllAnime(): Flow<List<StubAnimeSource>> {
|
||||
return handler.subscribeToList { animesourcesQueries.findAll(animeSourceDataMapper) }
|
||||
return handler.subscribeToList { animesourcesQueries.findAll(::mapStubSource) }
|
||||
}
|
||||
|
||||
override suspend fun getStubAnimeSource(id: Long): StubAnimeSource? {
|
||||
return handler.awaitOneOrNull {
|
||||
animesourcesQueries.findOne(
|
||||
id,
|
||||
animeSourceDataMapper,
|
||||
::mapStubSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,4 +25,10 @@ class AnimeStubSourceRepositoryImpl(
|
|||
override suspend fun upsertStubAnimeSource(id: Long, lang: String, name: String) {
|
||||
handler.await { animesourcesQueries.upsert(id, lang, name) }
|
||||
}
|
||||
|
||||
private fun mapStubSource(
|
||||
id: Long,
|
||||
lang: String,
|
||||
name: String,
|
||||
): StubAnimeSource = StubAnimeSource(id = id, lang = lang, name = name)
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package tachiyomi.data.source.manga
|
||||
|
||||
import tachiyomi.domain.source.manga.model.Source
|
||||
import tachiyomi.domain.source.manga.model.StubMangaSource
|
||||
|
||||
val mangaSourceMapper: (eu.kanade.tachiyomi.source.MangaSource) -> Source = { source ->
|
||||
Source(
|
||||
id = source.id,
|
||||
lang = source.lang,
|
||||
name = source.name,
|
||||
supportsLatest = false,
|
||||
isStub = false,
|
||||
)
|
||||
}
|
||||
|
||||
val mangaSourceDataMapper: (Long, String, String) -> StubMangaSource = { id, lang, name ->
|
||||
StubMangaSource(id, lang, name)
|
||||
}
|
|
@ -1,48 +1,50 @@
|
|||
package tachiyomi.data.source.manga
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
||||
import tachiyomi.domain.source.manga.model.MangaSourceWithCount
|
||||
import tachiyomi.domain.source.manga.model.Source
|
||||
import tachiyomi.domain.source.manga.model.StubMangaSource
|
||||
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
||||
import tachiyomi.domain.source.manga.repository.SourcePagingSourceType
|
||||
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||
import tachiyomi.domain.source.manga.model.Source as DomainSource
|
||||
|
||||
class MangaSourceRepositoryImpl(
|
||||
private val sourceManager: MangaSourceManager,
|
||||
private val handler: MangaDatabaseHandler,
|
||||
) : MangaSourceRepository {
|
||||
|
||||
override fun getMangaSources(): Flow<List<Source>> {
|
||||
override fun getMangaSources(): Flow<List<DomainSource>> {
|
||||
return sourceManager.catalogueSources.map { sources ->
|
||||
sources.map {
|
||||
mangaSourceMapper(it).copy(
|
||||
mapSourceToDomainSource(it).copy(
|
||||
supportsLatest = it.supportsLatest,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnlineMangaSources(): Flow<List<Source>> {
|
||||
override fun getOnlineMangaSources(): Flow<List<DomainSource>> {
|
||||
return sourceManager.catalogueSources.map { sources ->
|
||||
sources
|
||||
.filterIsInstance<HttpSource>()
|
||||
.map(mangaSourceMapper)
|
||||
.map(::mapSourceToDomainSource)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMangaSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>> {
|
||||
val sourceIdWithFavoriteCount = handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
||||
override fun getMangaSourcesWithFavoriteCount(): Flow<List<Pair<DomainSource, Long>>> {
|
||||
val sourceIdWithFavoriteCount =
|
||||
handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
|
||||
return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
|
||||
sourceIdsWithCount
|
||||
.map { (sourceId, count) ->
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
val domainSource = mangaSourceMapper(source).copy(
|
||||
val domainSource = mapSourceToDomainSource(source).copy(
|
||||
isStub = source is StubMangaSource,
|
||||
)
|
||||
domainSource to count
|
||||
|
@ -51,11 +53,12 @@ class MangaSourceRepositoryImpl(
|
|||
}
|
||||
|
||||
override fun getMangaSourcesWithNonLibraryManga(): Flow<List<MangaSourceWithCount>> {
|
||||
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
||||
val sourceIdWithNonLibraryManga =
|
||||
handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
||||
return sourceIdWithNonLibraryManga.map { sourceId ->
|
||||
sourceId.map { (sourceId, count) ->
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
val domainSource = mangaSourceMapper(source).copy(
|
||||
val domainSource = mapSourceToDomainSource(source).copy(
|
||||
isStub = source is StubMangaSource,
|
||||
)
|
||||
MangaSourceWithCount(domainSource, count)
|
||||
|
@ -81,4 +84,12 @@ class MangaSourceRepositoryImpl(
|
|||
val source = sourceManager.get(sourceId) as CatalogueSource
|
||||
return SourceLatestPagingSource(source)
|
||||
}
|
||||
|
||||
private fun mapSourceToDomainSource(source: MangaSource): DomainSource = DomainSource(
|
||||
id = source.id,
|
||||
lang = source.lang,
|
||||
name = source.name,
|
||||
supportsLatest = false,
|
||||
isStub = false,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ class MangaStubSourceRepositoryImpl(
|
|||
) : MangaStubSourceRepository {
|
||||
|
||||
override fun subscribeAllManga(): Flow<List<StubMangaSource>> {
|
||||
return handler.subscribeToList { sourcesQueries.findAll(mangaSourceDataMapper) }
|
||||
return handler.subscribeToList { sourcesQueries.findAll(::mapStubSource) }
|
||||
}
|
||||
|
||||
override suspend fun getStubMangaSource(id: Long): StubMangaSource? {
|
||||
return handler.awaitOneOrNull {
|
||||
sourcesQueries.findOne(
|
||||
id,
|
||||
mangaSourceDataMapper,
|
||||
::mapStubSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,4 +25,10 @@ class MangaStubSourceRepositoryImpl(
|
|||
override suspend fun upsertStubMangaSource(id: Long, lang: String, name: String) {
|
||||
handler.await { sourcesQueries.upsert(id, lang, name) }
|
||||
}
|
||||
|
||||
private fun mapStubSource(
|
||||
id: Long,
|
||||
lang: String,
|
||||
name: String,
|
||||
): StubMangaSource = StubMangaSource(id = id, lang = lang, name = name)
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package tachiyomi.data.track.anime
|
||||
|
||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||
|
||||
val animeTrackMapper: (
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long?,
|
||||
String,
|
||||
Double,
|
||||
Long,
|
||||
Long,
|
||||
Double,
|
||||
String,
|
||||
Long,
|
||||
Long,
|
||||
) -> AnimeTrack =
|
||||
{ id, animeId, syncId, remoteId, libraryId, title, lastEpisodeSeen, totalEpisodes, status, score, remoteUrl, startDate, finishDate ->
|
||||
AnimeTrack(
|
||||
id = id,
|
||||
animeId = animeId,
|
||||
syncId = syncId,
|
||||
remoteId = remoteId,
|
||||
libraryId = libraryId,
|
||||
title = title,
|
||||
lastEpisodeSeen = lastEpisodeSeen,
|
||||
totalEpisodes = totalEpisodes,
|
||||
status = status,
|
||||
score = score,
|
||||
remoteUrl = remoteUrl,
|
||||
startDate = startDate,
|
||||
finishDate = finishDate,
|
||||
)
|
||||
}
|
|
@ -10,24 +10,24 @@ class AnimeTrackRepositoryImpl(
|
|||
) : AnimeTrackRepository {
|
||||
|
||||
override suspend fun getTrackByAnimeId(id: Long): AnimeTrack? {
|
||||
return handler.awaitOneOrNull { anime_syncQueries.getTrackByAnimeId(id, animeTrackMapper) }
|
||||
return handler.awaitOneOrNull { anime_syncQueries.getTrackByAnimeId(id, ::mapTrack) }
|
||||
}
|
||||
|
||||
override suspend fun getTracksByAnimeId(animeId: Long): List<AnimeTrack> {
|
||||
return handler.awaitList {
|
||||
anime_syncQueries.getTracksByAnimeId(animeId, animeTrackMapper)
|
||||
anime_syncQueries.getTracksByAnimeId(animeId, ::mapTrack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAnimeTracksAsFlow(): Flow<List<AnimeTrack>> {
|
||||
return handler.subscribeToList {
|
||||
anime_syncQueries.getAnimeTracks(animeTrackMapper)
|
||||
anime_syncQueries.getAnimeTracks(::mapTrack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTracksByAnimeIdAsFlow(animeId: Long): Flow<List<AnimeTrack>> {
|
||||
return handler.subscribeToList {
|
||||
anime_syncQueries.getTracksByAnimeId(animeId, animeTrackMapper)
|
||||
anime_syncQueries.getTracksByAnimeId(animeId, ::mapTrack)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,4 +68,34 @@ class AnimeTrackRepositoryImpl(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapTrack(
|
||||
id: Long,
|
||||
animeId: Long,
|
||||
syncId: Long,
|
||||
remoteId: Long,
|
||||
libraryId: Long?,
|
||||
title: String,
|
||||
lastEpisodeSeen: Double,
|
||||
totalEpisodes: Long,
|
||||
status: Long,
|
||||
score: Double,
|
||||
remoteUrl: String,
|
||||
startDate: Long,
|
||||
finishDate: Long,
|
||||
): AnimeTrack = AnimeTrack(
|
||||
id = id,
|
||||
animeId = animeId,
|
||||
syncId = syncId,
|
||||
remoteId = remoteId,
|
||||
libraryId = libraryId,
|
||||
title = title,
|
||||
lastEpisodeSeen = lastEpisodeSeen,
|
||||
totalEpisodes = totalEpisodes,
|
||||
status = status,
|
||||
score = score,
|
||||
remoteUrl = remoteUrl,
|
||||
startDate = startDate,
|
||||
finishDate = finishDate,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package tachiyomi.data.track.manga
|
||||
|
||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
||||
|
||||
val mangaTrackMapper: (
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Long?,
|
||||
String,
|
||||
Double,
|
||||
Long,
|
||||
Long,
|
||||
Double,
|
||||
String,
|
||||
Long,
|
||||
Long,
|
||||
) -> MangaTrack =
|
||||
{ id, mangaId, syncId, remoteId, libraryId, title, lastChapterRead, totalChapters, status, score, remoteUrl, startDate, finishDate ->
|
||||
MangaTrack(
|
||||
id = id,
|
||||
mangaId = mangaId,
|
||||
syncId = syncId,
|
||||
remoteId = remoteId,
|
||||
libraryId = libraryId,
|
||||
title = title,
|
||||
lastChapterRead = lastChapterRead,
|
||||
totalChapters = totalChapters,
|
||||
status = status,
|
||||
score = score,
|
||||
remoteUrl = remoteUrl,
|
||||
startDate = startDate,
|
||||
finishDate = finishDate,
|
||||
)
|
||||
}
|
|
@ -10,24 +10,24 @@ class MangaTrackRepositoryImpl(
|
|||
) : MangaTrackRepository {
|
||||
|
||||
override suspend fun getTrackByMangaId(id: Long): MangaTrack? {
|
||||
return handler.awaitOneOrNull { manga_syncQueries.getTrackById(id, mangaTrackMapper) }
|
||||
return handler.awaitOneOrNull { manga_syncQueries.getTrackById(id, ::mapTrack) }
|
||||
}
|
||||
|
||||
override suspend fun getTracksByMangaId(mangaId: Long): List<MangaTrack> {
|
||||
return handler.awaitList {
|
||||
manga_syncQueries.getTracksByMangaId(mangaId, mangaTrackMapper)
|
||||
manga_syncQueries.getTracksByMangaId(mangaId, ::mapTrack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMangaTracksAsFlow(): Flow<List<MangaTrack>> {
|
||||
return handler.subscribeToList {
|
||||
manga_syncQueries.getTracks(mangaTrackMapper)
|
||||
manga_syncQueries.getTracks(::mapTrack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow<List<MangaTrack>> {
|
||||
return handler.subscribeToList {
|
||||
manga_syncQueries.getTracksByMangaId(mangaId, mangaTrackMapper)
|
||||
manga_syncQueries.getTracksByMangaId(mangaId, ::mapTrack)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,4 +68,34 @@ class MangaTrackRepositoryImpl(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapTrack(
|
||||
id: Long,
|
||||
mangaId: Long,
|
||||
syncId: Long,
|
||||
remoteId: Long,
|
||||
libraryId: Long?,
|
||||
title: String,
|
||||
lastChapterRead: Double,
|
||||
totalChapters: Long,
|
||||
status: Long,
|
||||
score: Double,
|
||||
remoteUrl: String,
|
||||
startDate: Long,
|
||||
finishDate: Long,
|
||||
): MangaTrack = MangaTrack(
|
||||
id = id,
|
||||
mangaId = mangaId,
|
||||
syncId = syncId,
|
||||
remoteId = remoteId,
|
||||
libraryId = libraryId,
|
||||
title = title,
|
||||
lastChapterRead = lastChapterRead,
|
||||
totalChapters = totalChapters,
|
||||
status = status,
|
||||
score = score,
|
||||
remoteUrl = remoteUrl,
|
||||
startDate = startDate,
|
||||
finishDate = finishDate,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package tachiyomi.data.updates.anime
|
||||
|
||||
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||
import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations
|
||||
|
||||
val animeUpdateWithRelationMapper: (
|
||||
Long,
|
||||
String,
|
||||
Long,
|
||||
String,
|
||||
String?,
|
||||
Boolean,
|
||||
Boolean,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
Boolean,
|
||||
String?,
|
||||
Long,
|
||||
Long,
|
||||
Long,
|
||||
) -> AnimeUpdatesWithRelations = {
|
||||
animeId, animeTitle, episodeId, episodeName, scanlator, seen, bookmark, lastSecondSeen, totalSeconds, sourceId, favorite, thumbnailUrl, coverLastModified, _, dateFetch ->
|
||||
AnimeUpdatesWithRelations(
|
||||
animeId = animeId,
|
||||
animeTitle = animeTitle,
|
||||
episodeId = episodeId,
|
||||
episodeName = episodeName,
|
||||
scanlator = scanlator,
|
||||
seen = seen,
|
||||
bookmark = bookmark,
|
||||
lastSecondSeen = lastSecondSeen,
|
||||
totalSeconds = totalSeconds,
|
||||
sourceId = sourceId,
|
||||
dateFetch = dateFetch,
|
||||
coverData = AnimeCover(
|
||||
animeId = animeId,
|
||||
sourceId = sourceId,
|
||||
isAnimeFavorite = favorite,
|
||||
url = thumbnailUrl,
|
||||
lastModified = coverLastModified,
|
||||
),
|
||||
)
|
||||
}
|
|
@ -2,6 +2,7 @@ package tachiyomi.data.updates.anime
|
|||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.data.handlers.anime.AnimeDatabaseHandler
|
||||
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||
import tachiyomi.domain.updates.anime.model.AnimeUpdatesWithRelations
|
||||
import tachiyomi.domain.updates.anime.repository.AnimeUpdatesRepository
|
||||
|
||||
|
@ -15,7 +16,7 @@ class AnimeUpdatesRepositoryImpl(
|
|||
seen = seen,
|
||||
after = after,
|
||||
limit = limit,
|
||||
mapper = animeUpdateWithRelationMapper,
|
||||
mapper = ::mapUpdatesWithRelations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +26,7 @@ class AnimeUpdatesRepositoryImpl(
|
|||
animeupdatesViewQueries.getRecentAnimeUpdates(
|
||||
after,
|
||||
limit,
|
||||
animeUpdateWithRelationMapper,
|
||||
::mapUpdatesWithRelations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +37,45 @@ class AnimeUpdatesRepositoryImpl(
|
|||
seen = seen,
|
||||
after = after,
|
||||
limit = limit,
|
||||
mapper = animeUpdateWithRelationMapper,
|
||||
mapper = ::mapUpdatesWithRelations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapUpdatesWithRelations(
|
||||
animeId: Long,
|
||||
animeTitle: String,
|
||||
episodeId: Long,
|
||||
episodeName: String,
|
||||
scanlator: String?,
|
||||
seen: Boolean,
|
||||
bookmark: Boolean,
|
||||
lastSecondSeen: Long,
|
||||
totalSeconds: Long,
|
||||
sourceId: Long,
|
||||
favorite: Boolean,
|
||||
thumbnailUrl: String?,
|
||||
coverLastModified: Long,
|
||||
dateUpload: Long,
|
||||
dateFetch: Long,
|
||||
): AnimeUpdatesWithRelations = AnimeUpdatesWithRelations(
|
||||
animeId = animeId,
|
||||
animeTitle = animeTitle,
|
||||
episodeId = episodeId,
|
||||
episodeName = episodeName,
|
||||
scanlator = scanlator,
|
||||
seen = seen,
|
||||
bookmark = bookmark,
|
||||
lastSecondSeen = lastSecondSeen,
|
||||
totalSeconds = totalSeconds,
|
||||
sourceId = sourceId,
|
||||
dateFetch = dateFetch,
|
||||
coverData = AnimeCover(
|
||||
animeId = animeId,
|
||||
sourceId = sourceId,
|
||||
isAnimeFavorite = favorite,
|
||||
url = thumbnailUrl,
|
||||
lastModified = coverLastModified,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue