mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 20:27:06 +03:00
parent
6dd16ca07d
commit
03ee4bc5f4
36 changed files with 583 additions and 569 deletions
10
.github/renovate.json
vendored
10
.github/renovate.json
vendored
|
@ -3,9 +3,11 @@
|
|||
"config:base"
|
||||
],
|
||||
"schedule": ["every sunday"],
|
||||
"ignoreDeps": [
|
||||
"androidx.core:core-splashscreen",
|
||||
"com.android.tools:r8",
|
||||
"com.google.guava:guava"
|
||||
"packageRules": [
|
||||
{
|
||||
"managers": ["maven"],
|
||||
"packageNames": ["com.google.guava:guava"],
|
||||
"versionScheme": "docker"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import eu.kanade.presentation.browse.InLibraryBadge
|
||||
import eu.kanade.presentation.browse.manga.components.BrowseSourceLoadingItem
|
||||
import eu.kanade.presentation.library.CommonEntryItemDefaults
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import eu.kanade.presentation.browse.InLibraryBadge
|
||||
import eu.kanade.presentation.library.CommonEntryItemDefaults
|
||||
import eu.kanade.presentation.library.EntryListItem
|
||||
|
|
|
@ -33,6 +33,7 @@ import androidx.compose.material3.ProvideTextStyle
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
|
@ -44,6 +45,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.ViewConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -76,8 +79,15 @@ fun AnimeEpisodeListItem(
|
|||
onDownloadClick: ((EpisodeDownloadAction) -> Unit)?,
|
||||
onEpisodeSwipe: (LibraryPreferences.EpisodeSwipeAction) -> Unit,
|
||||
) {
|
||||
val textAlpha = remember(seen) { if (seen) ReadItemAlpha else 1f }
|
||||
val textSubtitleAlpha = remember(seen) { if (seen) ReadItemAlpha else SecondaryItemAlpha }
|
||||
// Increase touch slop of swipe action to reduce accidental trigger
|
||||
val configuration = LocalViewConfiguration.current
|
||||
CompositionLocalProvider(
|
||||
LocalViewConfiguration provides object : ViewConfiguration by configuration {
|
||||
override val touchSlop: Float = configuration.touchSlop * 3f
|
||||
},
|
||||
) {
|
||||
val textAlpha = if (seen) ReadItemAlpha else 1f
|
||||
val textSubtitleAlpha = if (seen) ReadItemAlpha else SecondaryItemAlpha
|
||||
|
||||
val episodeSwipeStartEnabled = episodeSwipeStartAction != LibraryPreferences.EpisodeSwipeAction.Disabled
|
||||
val episodeSwipeEndEnabled = episodeSwipeEndAction != LibraryPreferences.EpisodeSwipeAction.Disabled
|
||||
|
@ -120,7 +130,6 @@ fun AnimeEpisodeListItem(
|
|||
dismissState.snapTo(DismissValue.Default)
|
||||
dismissDirections.addAll(dismissDirectionsCopy)
|
||||
}
|
||||
|
||||
DismissValue.DismissedToStart -> {
|
||||
lastDismissDirection = DismissDirection.EndToStart
|
||||
val dismissDirectionsCopy = dismissDirections.toSet()
|
||||
|
@ -129,7 +138,6 @@ fun AnimeEpisodeListItem(
|
|||
dismissState.snapTo(DismissValue.Default)
|
||||
dismissDirections.addAll(dismissDirectionsCopy)
|
||||
}
|
||||
|
||||
DismissValue.Default -> { }
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +208,7 @@ fun AnimeEpisodeListItem(
|
|||
if (!seen) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Circle,
|
||||
contentDescription = stringResource(R.string.unread),
|
||||
contentDescription = stringResource(R.string.unseen),
|
||||
modifier = Modifier
|
||||
.height(8.dp)
|
||||
.padding(end = 4.dp),
|
||||
|
@ -274,6 +282,7 @@ fun AnimeEpisodeListItem(
|
|||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwipeBackgroundIcon(
|
||||
|
|
|
@ -56,6 +56,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
|
@ -122,6 +123,7 @@ fun AnimeInfoBox(
|
|||
brush = Brush.verticalGradient(colors = backdropGradientColors),
|
||||
)
|
||||
}
|
||||
.blur(4.dp)
|
||||
.alpha(.2f),
|
||||
)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import androidx.compose.material3.ProvideTextStyle
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
|
@ -43,6 +44,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.ViewConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -74,12 +77,21 @@ fun MangaChapterListItem(
|
|||
onClick: () -> Unit,
|
||||
onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
|
||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
) {
|
||||
// Increase touch slop of swipe action to reduce accidental trigger
|
||||
val configuration = LocalViewConfiguration.current
|
||||
CompositionLocalProvider(
|
||||
LocalViewConfiguration provides object : ViewConfiguration by configuration {
|
||||
override val touchSlop: Float = configuration.touchSlop * 3f
|
||||
},
|
||||
) {
|
||||
val textAlpha = if (read) ReadItemAlpha else 1f
|
||||
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
|
||||
|
||||
val chapterSwipeStartEnabled = chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled
|
||||
val chapterSwipeEndEnabled = chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled
|
||||
val chapterSwipeStartEnabled =
|
||||
chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled
|
||||
val chapterSwipeEndEnabled =
|
||||
chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled
|
||||
|
||||
val dismissState = rememberDismissState()
|
||||
val dismissDirections = remember { mutableSetOf<DismissDirection>() }
|
||||
|
@ -88,6 +100,7 @@ fun MangaChapterListItem(
|
|||
if (chapterSwipeStartEnabled) {
|
||||
dismissDirections.add(DismissDirection.EndToStart)
|
||||
}
|
||||
|
||||
if (chapterSwipeEndEnabled) {
|
||||
dismissDirections.add(DismissDirection.StartToEnd)
|
||||
}
|
||||
|
@ -100,8 +113,10 @@ fun MangaChapterListItem(
|
|||
lastDismissDirection = null
|
||||
},
|
||||
)
|
||||
val dismissContentAlpha = if (lastDismissDirection != null) animateDismissContentAlpha else 1f
|
||||
val backgroundColor = if (chapterSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) {
|
||||
val dismissContentAlpha =
|
||||
if (lastDismissDirection != null) animateDismissContentAlpha else 1f
|
||||
val backgroundColor =
|
||||
if (chapterSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else if (chapterSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
|
@ -273,6 +288,7 @@ fun MangaChapterListItem(
|
|||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwipeBackgroundIcon(
|
||||
|
|
|
@ -56,6 +56,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
|
@ -122,6 +123,7 @@ fun MangaInfoBox(
|
|||
brush = Brush.verticalGradient(colors = backdropGradientColors),
|
||||
)
|
||||
}
|
||||
.blur(4.dp)
|
||||
.alpha(.2f),
|
||||
)
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class GlobalExceptionHandler private constructor(
|
|||
return try {
|
||||
Json.decodeFromString(ThrowableSerializer, intent.getStringExtra(INTENT_EXTRA)!!)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) { "Wasn't able to retrive throwable from intent" }
|
||||
logcat(LogPriority.ERROR, e) { "Wasn't able to retrieve throwable from intent" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ class ImageSaver(
|
|||
|
||||
try {
|
||||
data().use { input ->
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
context.contentResolver.openOutputStream(picture, "w").use { output ->
|
||||
input.copyTo(output!!)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,6 @@ class ShizukuInstallerAnime(private val service: Service) : InstallerAnime(servi
|
|||
|
||||
override var ready = false
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
override fun processEntry(entry: Entry) {
|
||||
super.processEntry(entry)
|
||||
scope.launch {
|
||||
|
|
|
@ -43,7 +43,6 @@ class ShizukuInstallerManga(private val service: Service) : InstallerManga(servi
|
|||
|
||||
override var ready = false
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
override fun processEntry(entry: Entry) {
|
||||
super.processEntry(entry)
|
||||
scope.launch {
|
||||
|
|
|
@ -290,7 +290,6 @@ internal class MigrateAnimeDialogScreenModel(
|
|||
|
||||
// Update custom cover (recheck if custom cover exists)
|
||||
if (migrateCustomCover && oldAnime.hasCustomCover()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
coverCache.setCustomCoverToCache(newAnime, coverCache.getCustomCoverFile(oldAnime.id).inputStream())
|
||||
}
|
||||
|
||||
|
|
|
@ -289,7 +289,6 @@ internal class MigrateMangaDialogScreenModel(
|
|||
|
||||
// Update custom cover (recheck if custom cover exists)
|
||||
if (migrateCustomCover && oldManga.hasCustomCover()) {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
coverCache.setCustomCoverToCache(newManga, coverCache.getCustomCoverFile(oldManga.id).inputStream())
|
||||
}
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ class AnimeScreen(
|
|||
screenModel.showTrackDialog()
|
||||
}
|
||||
}
|
||||
when (val dialog = (state as? AnimeScreenState.Success)?.dialog) {
|
||||
when (val dialog = successState.dialog) {
|
||||
null -> {}
|
||||
is AnimeInfoScreenModel.Dialog.ChangeCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
|
|
|
@ -147,8 +147,13 @@ class AnimeInfoScreenModel(
|
|||
/**
|
||||
* Helper function to update the UI state only if it's currently in success state
|
||||
*/
|
||||
private fun updateSuccessState(func: (AnimeScreenState.Success) -> AnimeScreenState.Success) {
|
||||
mutableState.update { if (it is AnimeScreenState.Success) func(it) else it }
|
||||
private inline fun updateSuccessState(func: (AnimeScreenState.Success) -> AnimeScreenState.Success) {
|
||||
mutableState.update {
|
||||
when (it) {
|
||||
AnimeScreenState.Loading -> it
|
||||
is AnimeScreenState.Success -> func(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -289,17 +294,7 @@ class AnimeInfoScreenModel(
|
|||
if (checkDuplicate) {
|
||||
val duplicate = getDuplicateLibraryAnime.await(anime.title)
|
||||
if (duplicate != null) {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(
|
||||
dialog = Dialog.DuplicateAnime(
|
||||
anime,
|
||||
duplicate,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.DuplicateAnime(anime, duplicate)) }
|
||||
return@launchIO
|
||||
}
|
||||
}
|
||||
|
@ -362,10 +357,8 @@ class AnimeInfoScreenModel(
|
|||
coroutineScope.launch {
|
||||
val categories = getCategories()
|
||||
val selection = getAnimeCategoryIds(anime)
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(
|
||||
updateSuccessState { successState ->
|
||||
successState.copy(
|
||||
dialog = Dialog.ChangeCategory(
|
||||
anime = anime,
|
||||
initialSelection = categories.mapAsCheckboxState { it.id in selection },
|
||||
|
@ -374,7 +367,6 @@ class AnimeInfoScreenModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the anime has any downloads.
|
||||
|
@ -990,66 +982,31 @@ class AnimeInfoScreenModel(
|
|||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(dialog = null)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
fun showDeleteEpisodeDialog(episodes: List<Episode>) {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(dialog = Dialog.DeleteEpisodes(episodes))
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.DeleteEpisodes(episodes)) }
|
||||
}
|
||||
|
||||
fun showSettingsDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(dialog = Dialog.SettingsSheet)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.SettingsSheet) }
|
||||
}
|
||||
|
||||
fun showTrackDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(dialog = Dialog.TrackSheet)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.TrackSheet) }
|
||||
}
|
||||
|
||||
fun showCoverDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(dialog = Dialog.FullCover)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.FullCover) }
|
||||
}
|
||||
|
||||
fun showAnimeSkipIntroDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> state.copy(dialog = Dialog.ChangeAnimeSkipIntro)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.ChangeAnimeSkipIntro) }
|
||||
}
|
||||
|
||||
private fun showQualitiesDialog(episode: Episode) {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
AnimeScreenState.Loading -> state
|
||||
is AnimeScreenState.Success -> { state.copy(dialog = Dialog.ShowQualities(episode, state.anime, state.source)) }
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.ShowQualities(episode, it.anime, it.source)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ class MangaScreen(
|
|||
screenModel.showTrackDialog()
|
||||
}
|
||||
}
|
||||
when (val dialog = (state as? MangaScreenState.Success)?.dialog) {
|
||||
when (val dialog = successState.dialog) {
|
||||
null -> {}
|
||||
is MangaInfoScreenModel.Dialog.ChangeCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
|
|
|
@ -142,8 +142,13 @@ class MangaInfoScreenModel(
|
|||
/**
|
||||
* Helper function to update the UI state only if it's currently in success state
|
||||
*/
|
||||
private fun updateSuccessState(func: (MangaScreenState.Success) -> MangaScreenState.Success) {
|
||||
mutableState.update { if (it is MangaScreenState.Success) func(it) else it }
|
||||
private inline fun updateSuccessState(func: (MangaScreenState.Success) -> MangaScreenState.Success) {
|
||||
mutableState.update {
|
||||
when (it) {
|
||||
MangaScreenState.Loading -> it
|
||||
is MangaScreenState.Success -> func(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -285,17 +290,7 @@ class MangaInfoScreenModel(
|
|||
val duplicate = getDuplicateLibraryManga.await(manga.title)
|
||||
|
||||
if (duplicate != null) {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> state.copy(
|
||||
dialog = Dialog.DuplicateManga(
|
||||
manga,
|
||||
duplicate,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.DuplicateManga(manga, duplicate)) }
|
||||
return@launchIO
|
||||
}
|
||||
}
|
||||
|
@ -358,10 +353,8 @@ class MangaInfoScreenModel(
|
|||
coroutineScope.launch {
|
||||
val categories = getCategories()
|
||||
val selection = getMangaCategoryIds(manga)
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> state.copy(
|
||||
updateSuccessState { successState ->
|
||||
successState.copy(
|
||||
dialog = Dialog.ChangeCategory(
|
||||
manga = manga,
|
||||
initialSelection = categories.mapAsCheckboxState { it.id in selection },
|
||||
|
@ -370,7 +363,6 @@ class MangaInfoScreenModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the manga has any downloads.
|
||||
|
@ -972,52 +964,23 @@ class MangaInfoScreenModel(
|
|||
}
|
||||
|
||||
fun dismissDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> state.copy(dialog = null)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
fun showDeleteChapterDialog(chapters: List<Chapter>) {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> state.copy(dialog = Dialog.DeleteChapters(chapters))
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.DeleteChapters(chapters)) }
|
||||
}
|
||||
|
||||
fun showSettingsDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> state.copy(dialog = Dialog.SettingsSheet)
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.SettingsSheet) }
|
||||
}
|
||||
|
||||
fun showTrackDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> {
|
||||
state.copy(dialog = Dialog.TrackSheet)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.TrackSheet) }
|
||||
}
|
||||
|
||||
fun showCoverDialog() {
|
||||
mutableState.update { state ->
|
||||
when (state) {
|
||||
MangaScreenState.Loading -> state
|
||||
is MangaScreenState.Success -> {
|
||||
state.copy(dialog = Dialog.FullCover)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSuccessState { it.copy(dialog = Dialog.FullCover) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -534,6 +534,8 @@ class AnimeLibraryScreenModel(
|
|||
}
|
||||
|
||||
suspend fun getRandomAnimelibItemForCurrentCategory(): AnimeLibraryItem? {
|
||||
if (state.value.categories.isEmpty()) return null
|
||||
|
||||
return withIOContext {
|
||||
state.value
|
||||
.getAnimelibItemsByCategoryId(state.value.categories[activeCategoryIndex].id)
|
||||
|
|
|
@ -528,6 +528,8 @@ class MangaLibraryScreenModel(
|
|||
}
|
||||
|
||||
suspend fun getRandomLibraryItemForCurrentCategory(): MangaLibraryItem? {
|
||||
if (state.value.categories.isEmpty()) return null
|
||||
|
||||
return withIOContext {
|
||||
state.value
|
||||
.getLibraryItemsByCategoryId(state.value.categories[activeCategoryIndex].id)
|
||||
|
|
|
@ -281,7 +281,7 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
splashScreen?.setKeepVisibleCondition {
|
||||
splashScreen?.setKeepOnScreenCondition {
|
||||
val elapsed = System.currentTimeMillis() - startTime
|
||||
elapsed <= SPLASH_MIN_DURATION || !ready && elapsed <= SPLASH_MAX_DURATION
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.ui.reader.SaveImageNotifier
|
|||
import eu.kanade.tachiyomi.util.AniSkipApi
|
||||
import eu.kanade.tachiyomi.util.Stamp
|
||||
import eu.kanade.tachiyomi.util.editCover
|
||||
import eu.kanade.tachiyomi.util.episode.filterDownloadedEpisodes
|
||||
import eu.kanade.tachiyomi.util.lang.byteSize
|
||||
import eu.kanade.tachiyomi.util.lang.takeBytes
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
|
@ -97,7 +98,7 @@ class PlayerViewModel(
|
|||
private val setAnimeViewerFlags: SetAnimeViewerFlags = Injekt.get(),
|
||||
internal val networkPreferences: NetworkPreferences = Injekt.get(),
|
||||
internal val playerPreferences: PlayerPreferences = Injekt.get(),
|
||||
basePreferences: BasePreferences = Injekt.get(),
|
||||
private val basePreferences: BasePreferences = Injekt.get(),
|
||||
uiPreferences: UiPreferences = Injekt.get(),
|
||||
) : ViewModel() {
|
||||
|
||||
|
@ -272,6 +273,13 @@ class PlayerViewModel(
|
|||
|
||||
return episodes
|
||||
.sortedWith(getEpisodeSort(anime, sortDescending = false))
|
||||
.run {
|
||||
if (basePreferences.downloadedOnly().get()) {
|
||||
filterDownloadedEpisodes(anime)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
.map { it.toDbEpisode() }
|
||||
}
|
||||
|
||||
|
@ -540,9 +548,10 @@ class PlayerViewModel(
|
|||
}
|
||||
|
||||
private fun updateTrackEpisodeSeen(episode: Episode) {
|
||||
if (basePreferences.incognitoMode().get()) return
|
||||
if (!trackPreferences.autoUpdateTrack().get()) return
|
||||
val anime = currentAnime ?: return
|
||||
|
||||
val anime = currentAnime ?: return
|
||||
val episodeSeen = episode.episode_number.toDouble()
|
||||
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
|
@ -640,7 +649,7 @@ class PlayerViewModel(
|
|||
val episode = currentEpisode ?: return null
|
||||
val filenameSuffix = " - $timePos"
|
||||
return DiskUtil.buildValidFilename(
|
||||
"${anime.title} - ${episode.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
|
||||
"${anime.title} - ${episode.name}".takeBytes(DiskUtil.MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
|
||||
) + filenameSuffix
|
||||
}
|
||||
|
||||
|
@ -741,5 +750,3 @@ class PlayerViewModel(
|
|||
data class ShareImage(val uri: Uri, val seconds: String) : Event()
|
||||
}
|
||||
}
|
||||
|
||||
private const val MAX_FILE_NAME_BYTES = 250
|
||||
|
|
|
@ -57,12 +57,15 @@ class GestureHandler(
|
|||
private var scrollDiff: Float? = null
|
||||
|
||||
override fun onScroll(
|
||||
e1: MotionEvent,
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
distanceX: Float,
|
||||
distanceY: Float,
|
||||
): Boolean {
|
||||
if (SeekState.mode == SeekState.LOCKED) { playerControls.toggleControls(); return false }
|
||||
if (e1 != null) {
|
||||
if (SeekState.mode == SeekState.LOCKED) {
|
||||
playerControls.toggleControls(); return false
|
||||
}
|
||||
if (e1.y < height * 0.05F || e1.y > height * 0.95F) return false
|
||||
val dx = e1.x - e2.x
|
||||
val dy = e1.y - e2.y
|
||||
|
@ -80,20 +83,32 @@ class GestureHandler(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
STATE_VERTICAL_LEFT -> {
|
||||
val diff = 1.5F * distanceY / height
|
||||
if (preferences.gestureVolumeBrightness().get()) activity.verticalScrollLeft(diff)
|
||||
if (preferences.gestureVolumeBrightness().get()) {
|
||||
activity.verticalScrollLeft(
|
||||
diff,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
STATE_VERTICAL_RIGHT -> {
|
||||
val diff = 1.5F * distanceY / height
|
||||
if (preferences.gestureVolumeBrightness().get()) activity.verticalScrollRight(diff)
|
||||
if (preferences.gestureVolumeBrightness().get()) {
|
||||
activity.verticalScrollRight(
|
||||
diff,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
STATE_HORIZONTAL -> {
|
||||
val diff = 150F * -dx / width
|
||||
scrollDiff = diff
|
||||
if (preferences.gestureHorizontalSeek().get()) activity.horizontalScroll(diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -58,21 +58,21 @@ class ReaderNavigationOverlayView(context: Context, attributeSet: AttributeSet)
|
|||
strokeWidth = 8f
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (navigation == null) return
|
||||
|
||||
navigation?.regions?.forEach { region ->
|
||||
val rect = region.rectF
|
||||
|
||||
// Scale rect from 1f,1f to screen width and height
|
||||
canvas?.withScale(width.toFloat(), height.toFloat()) {
|
||||
canvas.withScale(width.toFloat(), height.toFloat()) {
|
||||
regionPaint.color = context.getColor(region.type.colorRes)
|
||||
drawRect(rect, regionPaint)
|
||||
}
|
||||
|
||||
// Don't want scale anymore because it messes with drawText
|
||||
// Translate origin to rect start (left, top)
|
||||
canvas?.withTranslation(x = (width * rect.left), y = (height * rect.top)) {
|
||||
canvas.withTranslation(x = (width * rect.left), y = (height * rect.top)) {
|
||||
// Calculate center of rect width on screen
|
||||
val x = width * (abs(rect.left - rect.right) / 2)
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.data.saver.Location
|
|||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.player.viewer.AspectState.Companion.get
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
|
||||
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
|
@ -35,6 +34,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
||||
import eu.kanade.tachiyomi.util.chapter.filterDownloadedChapters
|
||||
import eu.kanade.tachiyomi.util.chapter.removeDuplicates
|
||||
import eu.kanade.tachiyomi.util.editCover
|
||||
import eu.kanade.tachiyomi.util.lang.byteSize
|
||||
|
@ -92,6 +92,7 @@ class ReaderViewModel(
|
|||
private val downloadProvider: MangaDownloadProvider = Injekt.get(),
|
||||
private val imageSaver: ImageSaver = Injekt.get(),
|
||||
preferences: BasePreferences = Injekt.get(),
|
||||
private val basePreferences: BasePreferences = Injekt.get(),
|
||||
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
||||
private val readerPreferences: ReaderPreferences = Injekt.get(),
|
||||
private val trackPreferences: TrackPreferences = Injekt.get(),
|
||||
|
@ -185,6 +186,13 @@ class ReaderViewModel(
|
|||
this
|
||||
}
|
||||
}
|
||||
.run {
|
||||
if (basePreferences.downloadedOnly().get()) {
|
||||
filterDownloadedChapters(manga)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
.map { it.toDbChapter() }
|
||||
.map(::ReaderChapter)
|
||||
}
|
||||
|
@ -427,13 +435,14 @@ class ReaderViewModel(
|
|||
currentPage = page.index + 1,
|
||||
)
|
||||
}
|
||||
if (!incognitoMode) {
|
||||
selectedChapter.chapter.last_page_read = page.index
|
||||
val shouldTrack = !incognitoMode || hasTrackers
|
||||
if (selectedChapter.pages?.lastIndex == page.index && shouldTrack) {
|
||||
if (selectedChapter.pages?.lastIndex == page.index) {
|
||||
selectedChapter.chapter.read = true
|
||||
updateTrackChapterRead(selectedChapter)
|
||||
deleteChapterIfNeeded(selectedChapter)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedChapter != currentChapters.currChapter) {
|
||||
logcat { "Setting ${selectedChapter.chapter.url} as active" }
|
||||
|
@ -496,16 +505,17 @@ class ReaderViewModel(
|
|||
* @param currentChapter current chapter, which is going to be marked as read.
|
||||
*/
|
||||
private fun deleteChapterIfNeeded(currentChapter: ReaderChapter) {
|
||||
val removeAfterReadSlots = downloadPreferences.removeAfterReadSlots().get()
|
||||
if (removeAfterReadSlots == -1) return
|
||||
|
||||
// Determine which chapter should be deleted and enqueue
|
||||
val currentChapterPosition = chapterList.indexOf(currentChapter)
|
||||
val removeAfterReadSlots = downloadPreferences.removeAfterReadSlots().get()
|
||||
val chapterToDelete = chapterList.getOrNull(currentChapterPosition - removeAfterReadSlots)
|
||||
|
||||
// If chapter is completely read no need to download it
|
||||
// If chapter is completely read, no need to download it
|
||||
chapterToDownload = null
|
||||
|
||||
// Check if deleting option is enabled and chapter exists
|
||||
if (removeAfterReadSlots != -1 && chapterToDelete != null) {
|
||||
if (chapterToDelete != null) {
|
||||
enqueueDeleteReadChapters(chapterToDelete)
|
||||
}
|
||||
}
|
||||
|
@ -526,10 +536,11 @@ class ReaderViewModel(
|
|||
|
||||
/**
|
||||
* Saves this [readerChapter] progress (last read page and whether it's read).
|
||||
* If incognito mode isn't on or has at least 1 tracker
|
||||
* if incognito mode isn't on.
|
||||
*/
|
||||
private suspend fun saveChapterProgress(readerChapter: ReaderChapter) {
|
||||
if (!incognitoMode || hasTrackers) {
|
||||
if (!incognitoMode) return
|
||||
|
||||
val chapter = readerChapter.chapter
|
||||
getCurrentChapter()?.requestedPage = chapter.last_page_read
|
||||
updateChapter.await(
|
||||
|
@ -541,7 +552,6 @@ class ReaderViewModel(
|
|||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this [readerChapter] last read history if incognito mode isn't on.
|
||||
|
@ -704,7 +714,7 @@ class ReaderViewModel(
|
|||
val chapter = page.chapter.chapter
|
||||
val filenameSuffix = " - ${page.number}"
|
||||
return DiskUtil.buildValidFilename(
|
||||
"${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
|
||||
"${manga.title} - ${chapter.name}".takeBytes(DiskUtil.MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
|
||||
) + filenameSuffix
|
||||
}
|
||||
|
||||
|
@ -822,9 +832,10 @@ class ReaderViewModel(
|
|||
* will run in a background thread and errors are ignored.
|
||||
*/
|
||||
private fun updateTrackChapterRead(readerChapter: ReaderChapter) {
|
||||
if (incognitoMode || !hasTrackers) return
|
||||
if (!trackPreferences.autoUpdateTrack().get()) return
|
||||
val manga = manga ?: return
|
||||
|
||||
val manga = manga ?: return
|
||||
val chapterRead = readerChapter.chapter.chapter_number.toDouble()
|
||||
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
|
@ -908,9 +919,4 @@ class ReaderViewModel(
|
|||
data class SavedImage(val result: SaveImageResult) : Event()
|
||||
data class ShareImage(val uri: Uri, val page: ReaderPage) : Event()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Safe theoretical max filename size is 255 bytes and 1 char = 2-4 bytes (UTF-8)
|
||||
private const val MAX_FILE_NAME_BYTES = 250
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
|
|||
}
|
||||
|
||||
override fun onFling(
|
||||
e1: MotionEvent,
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package eu.kanade.tachiyomi.util.chapter
|
||||
|
||||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadCache
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Returns a copy of the list with not downloaded chapters removed
|
||||
*/
|
||||
fun List<Chapter>.filterDownloadedChapters(manga: Manga): List<Chapter> {
|
||||
val downloadCache: MangaDownloadCache = Injekt.get()
|
||||
|
||||
return filter { downloadCache.isChapterDownloaded(it.name, it.scanlator, manga.title, manga.source, false) }
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package eu.kanade.tachiyomi.util.episode
|
||||
|
||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Returns a copy of the list with not downloaded chapters removed
|
||||
*/
|
||||
fun List<Episode>.filterDownloadedEpisodes(anime: Anime): List<Episode> {
|
||||
val downloadCache: AnimeDownloadCache = Injekt.get()
|
||||
|
||||
return filter { downloadCache.isEpisodeDownloaded(it.name, it.scanlator, anime.title, anime.source, false) }
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
object AndroidConfig {
|
||||
const val compileSdk = 33
|
||||
const val compileSdk = 34
|
||||
const val minSdk = 23
|
||||
const val targetSdk = 29
|
||||
const val ndk = "22.1.7171670"
|
||||
|
|
|
@ -114,4 +114,7 @@ object DiskUtil {
|
|||
}
|
||||
|
||||
const val NOMEDIA_FILE = ".nomedia"
|
||||
|
||||
// Safe theoretical max filename size is 255 bytes and 1 char = 2-4 bytes (UTF-8)
|
||||
const val MAX_FILE_NAME_BYTES = 250
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ class GetAnimeUpdates(
|
|||
}
|
||||
|
||||
fun subscribe(calendar: Calendar): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
return repository.subscribeAllAnimeUpdates(calendar.time.time, limit = 250)
|
||||
return repository.subscribeAllAnimeUpdates(calendar.time.time, limit = 500)
|
||||
}
|
||||
|
||||
fun subscribe(seen: Boolean, after: Long): Flow<List<AnimeUpdatesWithRelations>> {
|
||||
|
|
|
@ -14,7 +14,7 @@ class GetMangaUpdates(
|
|||
}
|
||||
|
||||
fun subscribe(calendar: Calendar): Flow<List<MangaUpdatesWithRelations>> {
|
||||
return repository.subscribeAllMangaUpdates(calendar.time.time, limit = 250)
|
||||
return repository.subscribeAllMangaUpdates(calendar.time.time, limit = 500)
|
||||
}
|
||||
|
||||
fun subscribe(read: Boolean, after: Long): Flow<List<MangaUpdatesWithRelations>> {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[versions]
|
||||
agp_version = "8.0.2"
|
||||
lifecycle_version = "2.6.1"
|
||||
paging_version = "3.2.0-rc01"
|
||||
|
||||
[libraries]
|
||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
|
||||
|
@ -10,7 +11,7 @@ appcompat = "androidx.appcompat:appcompat:1.6.1"
|
|||
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
corektx = "androidx.core:core-ktx:1.11.0-beta02"
|
||||
splashscreen = "androidx.core:core-splashscreen:1.0.0-alpha02"
|
||||
splashscreen = "androidx.core:core-splashscreen:1.0.1"
|
||||
recyclerview = "androidx.recyclerview:recyclerview:1.3.1-rc01"
|
||||
viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01"
|
||||
glance = "androidx.glance:glance-appwidget:1.0.0-beta01"
|
||||
|
@ -22,10 +23,10 @@ lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.r
|
|||
lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" }
|
||||
|
||||
work-runtime = "androidx.work:work-runtime-ktx:2.8.1"
|
||||
guava = "com.google.guava:guava:31.1-android"
|
||||
guava = "com.google.guava:guava:32.0.1-android"
|
||||
|
||||
paging-runtime = "androidx.paging:paging-runtime:3.1.1"
|
||||
paging-compose = "androidx.paging:paging-compose:1.0.0-alpha20"
|
||||
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
|
||||
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
|
||||
|
||||
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.1.1"
|
||||
test-ext = "androidx.test.ext:junit-ktx:1.1.5"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[versions]
|
||||
compiler = "1.4.7"
|
||||
compose-bom = "2023.04.00-beta02"
|
||||
accompanist = "0.31.2-alpha"
|
||||
compose-bom = "2023.06.00-alpha01"
|
||||
accompanist = "0.31.4-beta"
|
||||
|
||||
[libraries]
|
||||
activity = "androidx.activity:activity-compose:1.7.2"
|
||||
|
|
|
@ -57,12 +57,12 @@ flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013
|
|||
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
||||
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
|
||||
insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
||||
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.0.2"
|
||||
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.0.3"
|
||||
compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0"
|
||||
|
||||
logcat = "com.squareup.logcat:logcat:0.1"
|
||||
|
||||
acra-http = "ch.acra:acra-http:5.9.7"
|
||||
acra-http = "ch.acra:acra-http:5.10.1"
|
||||
|
||||
aboutLibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlib_version" }
|
||||
aboutLibraries-gradle = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlib_version" }
|
||||
|
|
|
@ -363,4 +363,5 @@
|
|||
<string name="bandwidth_data_saver_server">Bandwidth Hero Proxy Server</string>
|
||||
<string name="data_saver_server_summary">Put Bandwidth Hero Proxy server url here</string>
|
||||
<string name="download_slots_info">Will only download concurrently from self-hosted or unmetered sources</string>
|
||||
<string name="unseen">Unseen</string>
|
||||
</resources>
|
|
@ -32,7 +32,8 @@ fun HorizontalPager(
|
|||
reverseLayout: Boolean = false,
|
||||
key: ((index: Int) -> Any)? = null,
|
||||
pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
|
||||
Orientation.Horizontal,
|
||||
state = state,
|
||||
orientation = Orientation.Horizontal,
|
||||
),
|
||||
pageContent: @Composable PagerScope.(page: Int) -> Unit,
|
||||
) {
|
||||
|
|
Loading…
Reference in a new issue