Last commit merged:  7f0ed58b54
This commit is contained in:
LuftVerbot 2023-10-28 23:05:13 +02:00
parent 6dd16ca07d
commit 03ee4bc5f4
36 changed files with 583 additions and 569 deletions

10
.github/renovate.json vendored
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -60,7 +60,6 @@ class ImageSaver(
try {
data().use { input ->
@Suppress("BlockingMethodInNonBlockingContext")
context.contentResolver.openOutputStream(picture, "w").use { output ->
input.copyTo(output!!)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -75,7 +75,7 @@ class WebtoonFrame(context: Context) : FrameLayout(context) {
}
override fun onFling(
e1: MotionEvent,
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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