mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
fix:conflict.
Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
commit
70452acdda
@ -66,11 +66,11 @@ android {
|
|||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
buildConfigField("boolean", "PREVIEW", "true")
|
buildConfigField("boolean", "PREVIEW", "true")
|
||||||
|
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
matchingFallbacks.add("release")
|
||||||
val debugType = getByName("debug")
|
val debugType = getByName("debug")
|
||||||
signingConfig = debugType.signingConfig
|
|
||||||
versionNameSuffix = debugType.versionNameSuffix
|
versionNameSuffix = debugType.versionNameSuffix
|
||||||
applicationIdSuffix = debugType.applicationIdSuffix
|
applicationIdSuffix = debugType.applicationIdSuffix
|
||||||
matchingFallbacks.add("release")
|
|
||||||
}
|
}
|
||||||
create("benchmark") {
|
create("benchmark") {
|
||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,7 @@ class PreferenceMutableState<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun component2(): (T) -> Unit {
|
override fun component2(): (T) -> Unit {
|
||||||
return { preference.set(it) }
|
return preference::set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ import tachiyomi.domain.manga.interactor.GetManga
|
|||||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
|
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||||
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
@ -100,10 +101,11 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
|
addFactory { SetFetchInterval(get()) }
|
||||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||||
addFactory { SetMangaViewerFlags(get()) }
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
|
||||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
|
@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
|
|||||||
|
|
||||||
override fun key() = "extension_installer"
|
override fun key() = "extension_installer"
|
||||||
|
|
||||||
val entries get() = ExtensionInstaller.values().run {
|
val entries get() = ExtensionInstaller.entries.run {
|
||||||
if (context.hasMiuiPackageInstaller) {
|
if (context.hasMiuiPackageInstaller) {
|
||||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,9 +72,9 @@ class SetReadStatus(
|
|||||||
suspend fun await(manga: Manga, read: Boolean) =
|
suspend fun await(manga: Manga, read: Boolean) =
|
||||||
await(manga.id, read)
|
await(manga.id, read)
|
||||||
|
|
||||||
sealed class Result {
|
sealed interface Result {
|
||||||
object Success : Result()
|
data object Success : Result
|
||||||
object NoChapters : Result()
|
data object NoChapters : Result
|
||||||
data class InternalError(val error: Throwable) : Result()
|
data class InternalError(val error: Throwable) : Result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import tachiyomi.source.local.isLocal
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.lang.Long.max
|
import java.lang.Long.max
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
|
||||||
@ -48,11 +49,15 @@ class SyncChaptersWithSource(
|
|||||||
rawSourceChapters: List<SChapter>,
|
rawSourceChapters: List<SChapter>,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
manualFetch: Boolean = false,
|
||||||
|
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||||
): List<Chapter> {
|
): List<Chapter> {
|
||||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||||
throw NoChaptersException()
|
throw NoChaptersException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val now = ZonedDateTime.now()
|
||||||
|
|
||||||
val sourceChapters = rawSourceChapters
|
val sourceChapters = rawSourceChapters
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
.mapIndexed { i, sChapter ->
|
.mapIndexed { i, sChapter ->
|
||||||
@ -134,14 +139,21 @@ class SyncChaptersWithSource(
|
|||||||
|
|
||||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||||
|
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||||
|
updateManga.awaitUpdateFetchInterval(
|
||||||
|
manga,
|
||||||
|
now,
|
||||||
|
fetchWindow,
|
||||||
|
)
|
||||||
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val reAdded = mutableListOf<Chapter>()
|
val reAdded = mutableListOf<Chapter>()
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Float>()
|
val deletedChapterNumbers = TreeSet<Double>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
toDelete.forEach { chapter ->
|
toDelete.forEach { chapter ->
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
@ -188,6 +200,7 @@ class SyncChaptersWithSource(
|
|||||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
}
|
}
|
||||||
|
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||||
|
|
||||||
// Set this manga as updated since chapters were changed
|
// Set this manga as updated since chapters were changed
|
||||||
// Note that last_update actually represents last time the chapter list changed at all
|
// Note that last_update actually represents last time the chapter list changed at all
|
||||||
|
@ -12,7 +12,7 @@ fun Chapter.toSChapter(): SChapter {
|
|||||||
it.url = url
|
it.url = url
|
||||||
it.name = name
|
it.name = name
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.scanlator = scanlator
|
it.scanlator = scanlator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
|||||||
name = sChapter.name,
|
name = sChapter.name,
|
||||||
url = sChapter.url,
|
url = sChapter.url,
|
||||||
dateUpload = sChapter.date_upload,
|
dateUpload = sChapter.date_upload,
|
||||||
chapterNumber = sChapter.chapter_number,
|
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
scanlator = sChapter.scanlator?.ifBlank { null },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
|||||||
it.last_page_read = lastPageRead.toInt()
|
it.last_page_read = lastPageRead.toInt()
|
||||||
it.date_fetch = dateFetch
|
it.date_fetch = dateFetch
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.source_order = sourceOrder.toInt()
|
it.source_order = sourceOrder.toInt()
|
||||||
it.last_modified = lastModifiedAt
|
it.last_modified = lastModifiedAt
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ package eu.kanade.domain.manga.interactor
|
|||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||||
import tachiyomi.domain.manga.interactor.getCurrentFetchRange
|
|
||||||
import tachiyomi.domain.manga.interactor.updateIntervalMeta
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
@ -17,6 +15,7 @@ import java.util.Date
|
|||||||
|
|
||||||
class UpdateManga(
|
class UpdateManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
private val setFetchInterval: SetFetchInterval,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||||
@ -77,19 +76,14 @@ class UpdateManga(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateIntervalMeta(
|
suspend fun awaitUpdateFetchInterval(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<Chapter>,
|
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||||
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
|
window: Pair<Long, Long> = setFetchInterval.getWindow(dateTime),
|
||||||
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val newMeta = updateIntervalMeta(manga, chapters, zonedDateTime, setCurrentFetchRange)
|
return setFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
||||||
|
?.let { mangaRepository.update(it) }
|
||||||
return if (newMeta != null) {
|
?: false
|
||||||
mangaRepository.update(newMeta)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||||
|
@ -99,7 +99,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories:
|
|||||||
title = ComicInfo.Title(chapter.name),
|
title = ComicInfo.Title(chapter.name),
|
||||||
series = ComicInfo.Series(manga.title),
|
series = ComicInfo.Series(manga.title),
|
||||||
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
||||||
if ((it.rem(1) == 0.0F)) {
|
if ((it.rem(1) == 0.0)) {
|
||||||
ComicInfo.Number(it.toInt().toString())
|
ComicInfo.Number(it.toInt().toString())
|
||||||
} else {
|
} else {
|
||||||
ComicInfo.Number(it.toString())
|
ComicInfo.Number(it.toString())
|
||||||
|
@ -22,7 +22,7 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId).also {
|
|||||||
it.last_chapter_read = lastChapterRead.toFloat()
|
it.last_chapter_read = lastChapterRead.toFloat()
|
||||||
it.total_chapters = totalChapters.toInt()
|
it.total_chapters = totalChapters.toInt()
|
||||||
it.status = status.toInt()
|
it.status = status.toInt()
|
||||||
it.score = score
|
it.score = score.toFloat()
|
||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_reading_date = startDate
|
it.started_reading_date = startDate
|
||||||
it.finished_reading_date = finishDate
|
it.finished_reading_date = finishDate
|
||||||
@ -40,7 +40,7 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
|||||||
lastChapterRead = last_chapter_read.toDouble(),
|
lastChapterRead = last_chapter_read.toDouble(),
|
||||||
totalChapters = total_chapters.toLong(),
|
totalChapters = total_chapters.toLong(),
|
||||||
status = status.toLong(),
|
status = status.toLong(),
|
||||||
score = score,
|
score = score.toDouble(),
|
||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
|
@ -23,6 +23,7 @@ import androidx.compose.material.icons.outlined.History
|
|||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -30,6 +31,7 @@ import androidx.compose.material3.OutlinedButton
|
|||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -53,11 +55,9 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsState
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.DIVIDER_ALPHA
|
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
@ -65,7 +65,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||||||
@Composable
|
@Composable
|
||||||
fun ExtensionDetailsScreen(
|
fun ExtensionDetailsScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: ExtensionDetailsState,
|
state: ExtensionDetailsScreenModel.State,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickWhatsNew: () -> Unit,
|
onClickWhatsNew: () -> Unit,
|
||||||
onClickReadme: () -> Unit,
|
onClickReadme: () -> Unit,
|
||||||
@ -314,7 +314,7 @@ private fun DetailsHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,11 +356,8 @@ private fun InfoText(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoDivider() {
|
private fun InfoDivider() {
|
||||||
Divider(
|
VerticalDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier.height(20.dp),
|
||||||
.height(20.dp)
|
|
||||||
.width(1.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.browse
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -12,7 +13,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ private fun ExtensionFilterContent(
|
|||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
FastScrollLazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
|
@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsState
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
@ -57,7 +57,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionScreen(
|
fun ExtensionScreen(
|
||||||
state: ExtensionsState,
|
state: ExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
@ -108,7 +108,7 @@ fun ExtensionScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionContent(
|
private fun ExtensionContent(
|
||||||
state: ExtensionsState,
|
state: ExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
@ -1,49 +1,25 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.DoneAll
|
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
|
||||||
import androidx.compose.material.icons.outlined.PushPin
|
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.FilterChipDefaults
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreenModel
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.VerticalDivider
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchScreen(
|
fun GlobalSearchScreen(
|
||||||
state: GlobalSearchScreenModel.State,
|
state: SearchScreenModel.State,
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
@ -56,80 +32,23 @@ fun GlobalSearchScreen(
|
|||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
GlobalSearchToolbar(
|
||||||
GlobalSearchToolbar(
|
searchQuery = state.searchQuery,
|
||||||
searchQuery = state.searchQuery,
|
progress = state.progress,
|
||||||
progress = state.progress,
|
total = state.total,
|
||||||
total = state.total,
|
navigateUp = navigateUp,
|
||||||
navigateUp = navigateUp,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onSearch = onSearch,
|
||||||
onSearch = onSearch,
|
sourceFilter = state.sourceFilter,
|
||||||
scrollBehavior = scrollBehavior,
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
)
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
Row(
|
scrollBehavior = scrollBehavior,
|
||||||
modifier = Modifier
|
)
|
||||||
.horizontalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = MaterialTheme.padding.small),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
|
||||||
) {
|
|
||||||
// TODO: make this UX better; it only applies when triggering a new search
|
|
||||||
FilterChip(
|
|
||||||
selected = state.sourceFilter == SourceFilter.PinnedOnly,
|
|
||||||
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.PushPin,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(id = R.string.pinned_sources))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
FilterChip(
|
|
||||||
selected = state.sourceFilter == SourceFilter.All,
|
|
||||||
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.DoneAll,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(id = R.string.all))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
VerticalDivider()
|
|
||||||
|
|
||||||
FilterChip(
|
|
||||||
selected = state.onlyShowHasResults,
|
|
||||||
onClick = { onToggleResults() },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.FilterList,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(FilterChipDefaults.IconSize),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(text = stringResource(id = R.string.has_results))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
GlobalSearchContent(
|
GlobalSearchContent(
|
||||||
items = items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -140,7 +59,8 @@ fun GlobalSearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GlobalSearchContent(
|
internal fun GlobalSearchContent(
|
||||||
|
fromSourceId: Long? = null,
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
items: Map<CatalogueSource, SearchItemResult>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
@ -154,7 +74,7 @@ private fun GlobalSearchContent(
|
|||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = source.name,
|
title = fromSourceId?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
@ -163,18 +83,6 @@ private fun GlobalSearchContent(
|
|||||||
GlobalSearchLoadingResultItem()
|
GlobalSearchLoadingResultItem()
|
||||||
}
|
}
|
||||||
is SearchItemResult.Success -> {
|
is SearchItemResult.Success -> {
|
||||||
if (result.isEmpty) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.no_results_found),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
vertical = MaterialTheme.padding.small,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return@GlobalSearchResultItem
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalSearchCardRow(
|
GlobalSearchCardRow(
|
||||||
titles = result.result,
|
titles = result.result,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
|
@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@ -18,7 +18,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||||||
fun MigrateMangaScreen(
|
fun MigrateMangaScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
title: String?,
|
title: String?,
|
||||||
state: MigrateMangaState,
|
state: MigrateMangaScreenModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -51,7 +51,7 @@ fun MigrateMangaScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MigrateMangaContent(
|
private fun MigrateMangaContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: MigrateMangaState,
|
state: MigrateMangaScreenModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchEmptyResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSearchScreen(
|
fun MigrateSearchScreen(
|
||||||
|
state: SearchScreenModel.State,
|
||||||
|
fromSourceId: Long?,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: MigrateSearchState,
|
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onLongClickItem: (Manga) -> Unit,
|
onLongClickItem: (Manga) -> Unit,
|
||||||
@ -37,13 +32,17 @@ fun MigrateSearchScreen(
|
|||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
MigrateSearchContent(
|
GlobalSearchContent(
|
||||||
sourceId = state.manga?.source ?: -1,
|
fromSourceId = fromSourceId,
|
||||||
items = state.items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -52,50 +51,3 @@ fun MigrateSearchScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MigrateSearchContent(
|
|
||||||
sourceId: Long,
|
|
||||||
items: Map<CatalogueSource, SearchItemResult>,
|
|
||||||
contentPadding: PaddingValues,
|
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
|
||||||
onClickItem: (Manga) -> Unit,
|
|
||||||
onLongClickItem: (Manga) -> Unit,
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
items.forEach { (source, result) ->
|
|
||||||
item(key = source.id) {
|
|
||||||
GlobalSearchResultItem(
|
|
||||||
title = if (source.id == sourceId) "▶ ${source.name}" else source.name,
|
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
|
||||||
onClick = { onClickSource(source) },
|
|
||||||
) {
|
|
||||||
when (result) {
|
|
||||||
SearchItemResult.Loading -> {
|
|
||||||
GlobalSearchLoadingResultItem()
|
|
||||||
}
|
|
||||||
is SearchItemResult.Success -> {
|
|
||||||
if (result.isEmpty) {
|
|
||||||
GlobalSearchEmptyResultItem()
|
|
||||||
return@GlobalSearchResultItem
|
|
||||||
}
|
|
||||||
|
|
||||||
GlobalSearchCardRow(
|
|
||||||
titles = result.result,
|
|
||||||
getManga = getManga,
|
|
||||||
onClick = onClickItem,
|
|
||||||
onLongClick = onLongClickItem,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is SearchItemResult.Error -> {
|
|
||||||
GlobalSearchErrorResultItem(message = result.throwable.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -26,7 +26,7 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
|
|||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.presentation.core.components.Badge
|
import tachiyomi.presentation.core.components.Badge
|
||||||
@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
state: MigrateSourceState,
|
state: MigrateSourceScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
|
@ -12,7 +12,7 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
|
|||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterState
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
@ -22,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||||||
@Composable
|
@Composable
|
||||||
fun SourcesFilterScreen(
|
fun SourcesFilterScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: SourcesFilterState.Success,
|
state: SourcesFilterScreenModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -54,7 +54,7 @@ fun SourcesFilterScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterContent(
|
private fun SourcesFilterContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: SourcesFilterState.Success,
|
state: SourcesFilterScreenModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesState
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Pin
|
import tachiyomi.domain.source.model.Pin
|
||||||
@ -40,7 +40,7 @@ import tachiyomi.source.local.isLocal
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesScreen(
|
fun SourcesScreen(
|
||||||
state: SourcesState,
|
state: SourcesScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source, Listing) -> Unit,
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
@ -192,7 +192,7 @@ fun SourceOptionsDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SourceUiModel {
|
sealed interface SourceUiModel {
|
||||||
data class Item(val source: Source) : SourceUiModel()
|
data class Item(val source: Source) : SourceUiModel
|
||||||
data class Header(val language: String) : SourceUiModel()
|
data class Header(val language: String) : SourceUiModel
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed class Result<out T> {
|
sealed class Result<out T> {
|
||||||
object Loading : Result<Nothing>()
|
data object Loading : Result<Nothing>()
|
||||||
object Error : Result<Nothing>()
|
data object Error : Result<Nothing>()
|
||||||
data class Success<out T>(val value: T) : Result<T>()
|
data class Success<out T>(val value: T) : Result<T>()
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,21 @@ package eu.kanade.presentation.browse.components
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
import eu.kanade.presentation.library.components.MangaComfortableGridItem
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import tachiyomi.domain.manga.model.asMangaCover
|
import tachiyomi.domain.manga.model.asMangaCover
|
||||||
@ -26,13 +30,18 @@ fun GlobalSearchCardRow(
|
|||||||
onClick: (Manga) -> Unit,
|
onClick: (Manga) -> Unit,
|
||||||
onLongClick: (Manga) -> Unit,
|
onLongClick: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (titles.isEmpty()) {
|
||||||
|
EmptyResultItem()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
||||||
) {
|
) {
|
||||||
items(titles) {
|
items(titles) {
|
||||||
val title by getManga(it)
|
val title by getManga(it)
|
||||||
GlobalSearchCard(
|
MangaItem(
|
||||||
title = title.title,
|
title = title.title,
|
||||||
cover = title.asMangaCover(),
|
cover = title.asMangaCover(),
|
||||||
isFavorite = title.favorite,
|
isFavorite = title.favorite,
|
||||||
@ -44,7 +53,7 @@ fun GlobalSearchCardRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GlobalSearchCard(
|
private fun MangaItem(
|
||||||
title: String,
|
title: String,
|
||||||
cover: MangaCover,
|
cover: MangaCover,
|
||||||
isFavorite: Boolean,
|
isFavorite: Boolean,
|
||||||
@ -54,6 +63,7 @@ private fun GlobalSearchCard(
|
|||||||
Box(modifier = Modifier.width(96.dp)) {
|
Box(modifier = Modifier.width(96.dp)) {
|
||||||
MangaComfortableGridItem(
|
MangaComfortableGridItem(
|
||||||
title = title,
|
title = title,
|
||||||
|
titleMaxLines = 3,
|
||||||
coverData = cover,
|
coverData = cover,
|
||||||
coverBadgeStart = {
|
coverBadgeStart = {
|
||||||
InLibraryBadge(enabled = isFavorite)
|
InLibraryBadge(enabled = isFavorite)
|
||||||
@ -64,3 +74,15 @@ private fun GlobalSearchCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EmptyResultItem() {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_results_found),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -61,18 +61,6 @@ fun GlobalSearchResultItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GlobalSearchEmptyResultItem() {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.no_results_found),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
vertical = MaterialTheme.padding.small,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchLoadingResultItem() {
|
fun GlobalSearchLoadingResultItem() {
|
||||||
Box(
|
Box(
|
||||||
|
@ -1,13 +1,36 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.PushPin
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchToolbar(
|
fun GlobalSearchToolbar(
|
||||||
@ -17,24 +40,89 @@ fun GlobalSearchToolbar(
|
|||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
sourceFilter: SourceFilter,
|
||||||
|
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||||
|
onlyShowHasResults: Boolean,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior,
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
) {
|
) {
|
||||||
Box {
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
SearchToolbar(
|
Box {
|
||||||
searchQuery = searchQuery,
|
SearchToolbar(
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
searchQuery = searchQuery,
|
||||||
onSearch = onSearch,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onClickCloseSearch = navigateUp,
|
onSearch = onSearch,
|
||||||
navigateUp = navigateUp,
|
onClickCloseSearch = navigateUp,
|
||||||
scrollBehavior = scrollBehavior,
|
navigateUp = navigateUp,
|
||||||
)
|
scrollBehavior = scrollBehavior,
|
||||||
if (progress in 1 until total) {
|
)
|
||||||
LinearProgressIndicator(
|
if (progress in 1..<total) {
|
||||||
progress = progress / total.toFloat(),
|
LinearProgressIndicator(
|
||||||
modifier = Modifier
|
progress = progress / total.toFloat(),
|
||||||
.align(Alignment.BottomStart)
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.align(Alignment.BottomStart)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.horizontalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
// TODO: make this UX better; it only applies when triggering a new search
|
||||||
|
FilterChip(
|
||||||
|
selected = sourceFilter == SourceFilter.PinnedOnly,
|
||||||
|
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.PushPin,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.pinned_sources))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
FilterChip(
|
||||||
|
selected = sourceFilter == SourceFilter.All,
|
||||||
|
onClick = { onChangeSearchFilter(SourceFilter.All) },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.DoneAll,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.all))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
VerticalDivider()
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = onlyShowHasResults,
|
||||||
|
onClick = { onToggleResults() },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.FilterList,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(FilterChipDefaults.IconSize),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.has_results))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
@ -70,6 +71,7 @@ fun NavigatorAdaptiveSheet(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AdaptiveSheet(
|
fun AdaptiveSheet(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
tonalElevation: Dp = 1.dp,
|
tonalElevation: Dp = 1.dp,
|
||||||
enableSwipeDismiss: Boolean = true,
|
enableSwipeDismiss: Boolean = true,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
@ -82,6 +84,7 @@ fun AdaptiveSheet(
|
|||||||
properties = dialogProperties,
|
properties = dialogProperties,
|
||||||
) {
|
) {
|
||||||
AdaptiveSheetImpl(
|
AdaptiveSheetImpl(
|
||||||
|
modifier = modifier,
|
||||||
isTabletUi = isTabletUi,
|
isTabletUi = isTabletUi,
|
||||||
tonalElevation = tonalElevation,
|
tonalElevation = tonalElevation,
|
||||||
enableSwipeDismiss = enableSwipeDismiss,
|
enableSwipeDismiss = enableSwipeDismiss,
|
||||||
|
@ -13,18 +13,13 @@ import java.util.Date
|
|||||||
fun RelativeDateHeader(
|
fun RelativeDateHeader(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
date: Date,
|
date: Date,
|
||||||
relativeTime: Int,
|
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
ListGroupHeader(
|
ListGroupHeader(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
text = remember {
|
text = remember {
|
||||||
date.toRelativeString(
|
date.toRelativeString(context, dateFormat)
|
||||||
context,
|
|
||||||
relativeTime,
|
|
||||||
dateFormat,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.pager.PagerState
|
|||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -30,7 +31,6 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||||
|
|
||||||
object TabbedDialogPaddings {
|
object TabbedDialogPaddings {
|
||||||
@ -40,6 +40,7 @@ object TabbedDialogPaddings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TabbedDialog(
|
fun TabbedDialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
tabTitles: List<String>,
|
tabTitles: List<String>,
|
||||||
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
|
||||||
@ -47,6 +48,7 @@ fun TabbedDialog(
|
|||||||
content: @Composable (Int) -> Unit,
|
content: @Composable (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
AdaptiveSheet(
|
AdaptiveSheet(
|
||||||
|
modifier = modifier,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@ -80,7 +82,7 @@ fun TabbedDialog(
|
|||||||
|
|
||||||
tabOverflowMenuContent?.let { MoreMenu(it) }
|
tabOverflowMenuContent?.let { MoreMenu(it) }
|
||||||
}
|
}
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
|
@ -20,7 +20,6 @@ import eu.kanade.presentation.components.SearchToolbar
|
|||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryState
|
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
@ -33,7 +32,7 @@ import java.util.Date
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
fun HistoryScreen(
|
||||||
state: HistoryState,
|
state: HistoryScreenModel.State,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
onSearchQueryChange: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
onClickCover: (mangaId: Long) -> Unit,
|
onClickCover: (mangaId: Long) -> Unit,
|
||||||
@ -99,7 +98,6 @@ private fun HistoryScreenContent(
|
|||||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||||
preferences: UiPreferences = Injekt.get(),
|
preferences: UiPreferences = Injekt.get(),
|
||||||
) {
|
) {
|
||||||
val relativeTime: Int = remember { preferences.relativeTime().get() }
|
|
||||||
val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||||
|
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
@ -120,7 +118,6 @@ private fun HistoryScreenContent(
|
|||||||
RelativeDateHeader(
|
RelativeDateHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
date = item.date,
|
date = item.date,
|
||||||
relativeTime = relativeTime,
|
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -139,7 +136,7 @@ private fun HistoryScreenContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class HistoryUiModel {
|
sealed interface HistoryUiModel {
|
||||||
data class Header(val date: Date) : HistoryUiModel()
|
data class Header(val date: Date) : HistoryUiModel
|
||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel()
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,11 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
|
import eu.kanade.presentation.util.formatChapterNumber
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
|
|
||||||
private val HISTORY_ITEM_HEIGHT = 96.dp
|
private val HISTORY_ITEM_HEIGHT = 96.dp
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ fun HistoryItem(
|
|||||||
text = if (history.chapterNumber > -1) {
|
text = if (history.chapterNumber > -1) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.recent_manga_time,
|
R.string.recent_manga_time,
|
||||||
chapterFormatter.format(history.chapterNumber),
|
formatChapterNumber(history.chapterNumber),
|
||||||
readAt,
|
readAt,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -88,8 +87,3 @@ fun HistoryItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val chapterFormatter = DecimalFormat(
|
|
||||||
"#.###",
|
|
||||||
DecimalFormatSymbols().apply { decimalSeparator = '.' },
|
|
||||||
)
|
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.ColumnScope
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -15,7 +16,6 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
@ -26,11 +26,11 @@ import tachiyomi.domain.library.model.sort
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
import tachiyomi.presentation.core.components.SettingsFlowRow
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import tachiyomi.presentation.core.components.SortItem
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
import tachiyomi.presentation.core.components.material.ChoiceChip
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibrarySettingsDialog(
|
fun LibrarySettingsDialog(
|
||||||
@ -181,12 +181,12 @@ private fun ColumnScope.DisplayPage(
|
|||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
|
||||||
SettingsFlowRow(R.string.action_display_mode) {
|
SettingsChipRow(R.string.action_display_mode) {
|
||||||
displayModes.map { (titleRes, mode) ->
|
displayModes.map { (titleRes, mode) ->
|
||||||
ChoiceChip(
|
FilterChip(
|
||||||
isSelected = displayMode == mode,
|
selected = displayMode == mode,
|
||||||
onClick = { screenModel.setDisplayMode(mode) },
|
onClick = { screenModel.setDisplayMode(mode) },
|
||||||
content = { Text(stringResource(titleRes)) },
|
label = { Text(stringResource(titleRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,59 +211,35 @@ private fun ColumnScope.DisplayPage(
|
|||||||
} else {
|
} else {
|
||||||
stringResource(R.string.label_default)
|
stringResource(R.string.label_default)
|
||||||
},
|
},
|
||||||
onChange = { columnPreference.set(it) },
|
onChange = columnPreference::set,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HeadingItem(R.string.overlay_header)
|
HeadingItem(R.string.overlay_header)
|
||||||
val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_download_badge),
|
label = stringResource(R.string.action_display_download_badge),
|
||||||
checked = downloadBadge,
|
pref = screenModel.libraryPreferences.downloadBadge(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(LibraryPreferences::downloadBadge)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
val localBadge by screenModel.libraryPreferences.localBadge().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_local_badge),
|
label = stringResource(R.string.action_display_local_badge),
|
||||||
checked = localBadge,
|
pref = screenModel.libraryPreferences.localBadge(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(LibraryPreferences::localBadge)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
val languageBadge by screenModel.libraryPreferences.languageBadge().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_language_badge),
|
label = stringResource(R.string.action_display_language_badge),
|
||||||
checked = languageBadge,
|
pref = screenModel.libraryPreferences.languageBadge(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(LibraryPreferences::languageBadge)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
val showContinueReadingButton by screenModel.libraryPreferences.showContinueReadingButton().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_show_continue_reading_button),
|
label = stringResource(R.string.action_display_show_continue_reading_button),
|
||||||
checked = showContinueReadingButton,
|
pref = screenModel.libraryPreferences.showContinueReadingButton(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(LibraryPreferences::showContinueReadingButton)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
HeadingItem(R.string.tabs_header)
|
HeadingItem(R.string.tabs_header)
|
||||||
val categoryTabs by screenModel.libraryPreferences.categoryTabs().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_show_tabs),
|
label = stringResource(R.string.action_display_show_tabs),
|
||||||
checked = categoryTabs,
|
pref = screenModel.libraryPreferences.categoryTabs(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(LibraryPreferences::categoryTabs)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
val categoryNumberOfItems by screenModel.libraryPreferences.categoryNumberOfItems().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.action_display_show_number_of_items),
|
label = stringResource(R.string.action_display_show_number_of_items),
|
||||||
checked = categoryNumberOfItems,
|
pref = screenModel.libraryPreferences.categoryNumberOfItems(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(LibraryPreferences::categoryNumberOfItems)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,7 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
fun MangaComfortableGridItem(
|
fun MangaComfortableGridItem(
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
title: String,
|
title: String,
|
||||||
|
titleMaxLines: Int = 2,
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: tachiyomi.domain.manga.model.MangaCover,
|
||||||
coverAlpha: Float = 1f,
|
coverAlpha: Float = 1f,
|
||||||
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
@ -204,6 +205,7 @@ fun MangaComfortableGridItem(
|
|||||||
title = title,
|
title = title,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
minLines = 2,
|
minLines = 2,
|
||||||
|
maxLines = titleMaxLines,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,6 +255,7 @@ private fun GridItemTitle(
|
|||||||
title: String,
|
title: String,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
minLines: Int,
|
minLines: Int,
|
||||||
|
maxLines: Int = 2,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -260,7 +263,7 @@ private fun GridItemTitle(
|
|||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 18.sp,
|
lineHeight = 18.sp,
|
||||||
minLines = minLines,
|
minLines = minLines,
|
||||||
maxLines = 2,
|
maxLines = maxLines,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = style,
|
style = style,
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.library.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
@ -9,7 +10,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
|
|
||||||
@ -44,6 +44,6 @@ internal fun LibraryTabs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -8,6 +9,7 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -18,15 +20,27 @@ fun DuplicateMangaDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.are_you_sure))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
|
||||||
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Row {
|
FlowRow(
|
||||||
TextButton(onClick = {
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
onDismissRequest()
|
) {
|
||||||
onOpenManga()
|
TextButton(
|
||||||
},) {
|
onClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
onOpenManga()
|
||||||
|
},
|
||||||
|
) {
|
||||||
Text(text = stringResource(R.string.action_show_manga))
|
Text(text = stringResource(R.string.action_show_manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
@ -40,11 +54,5 @@ fun DuplicateMangaDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
|
||||||
Text(text = stringResource(R.string.are_you_sure))
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -57,12 +57,12 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
|||||||
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
||||||
import eu.kanade.presentation.manga.components.MangaInfoBox
|
import eu.kanade.presentation.manga.components.MangaInfoBox
|
||||||
import eu.kanade.presentation.manga.components.MangaToolbar
|
import eu.kanade.presentation.manga.components.MangaToolbar
|
||||||
|
import eu.kanade.presentation.util.formatChapterNumber
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapterDecimalFormat
|
|
||||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
@ -82,9 +82,9 @@ import java.util.Date
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreen(
|
fun MangaScreen(
|
||||||
state: MangaScreenState.Success,
|
state: MangaScreenModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
dateRelativeTime: Int,
|
fetchInterval: Int?,
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
@ -112,6 +112,7 @@ fun MangaScreen(
|
|||||||
onShareClicked: (() -> Unit)?,
|
onShareClicked: (() -> Unit)?,
|
||||||
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
|
onEditFetchIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
|
|
||||||
// For bottom action menu
|
// For bottom action menu
|
||||||
@ -139,8 +140,8 @@ fun MangaScreen(
|
|||||||
MangaScreenSmallImpl(
|
MangaScreenSmallImpl(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
|
fetchInterval = fetchInterval,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
@ -160,6 +161,7 @@ fun MangaScreen(
|
|||||||
onShareClicked = onShareClicked,
|
onShareClicked = onShareClicked,
|
||||||
onDownloadActionClicked = onDownloadActionClicked,
|
onDownloadActionClicked = onDownloadActionClicked,
|
||||||
onEditCategoryClicked = onEditCategoryClicked,
|
onEditCategoryClicked = onEditCategoryClicked,
|
||||||
|
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||||
onMigrateClicked = onMigrateClicked,
|
onMigrateClicked = onMigrateClicked,
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||||
@ -174,10 +176,10 @@ fun MangaScreen(
|
|||||||
MangaScreenLargeImpl(
|
MangaScreenLargeImpl(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
|
fetchInterval = fetchInterval,
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
@ -195,6 +197,7 @@ fun MangaScreen(
|
|||||||
onShareClicked = onShareClicked,
|
onShareClicked = onShareClicked,
|
||||||
onDownloadActionClicked = onDownloadActionClicked,
|
onDownloadActionClicked = onDownloadActionClicked,
|
||||||
onEditCategoryClicked = onEditCategoryClicked,
|
onEditCategoryClicked = onEditCategoryClicked,
|
||||||
|
onEditIntervalClicked = onEditFetchIntervalClicked,
|
||||||
onMigrateClicked = onMigrateClicked,
|
onMigrateClicked = onMigrateClicked,
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||||
@ -210,10 +213,10 @@ fun MangaScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaScreenSmallImpl(
|
private fun MangaScreenSmallImpl(
|
||||||
state: MangaScreenState.Success,
|
state: MangaScreenModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
dateRelativeTime: Int,
|
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
|
fetchInterval: Int?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
@ -240,6 +243,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
onShareClicked: (() -> Unit)?,
|
onShareClicked: (() -> Unit)?,
|
||||||
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
|
|
||||||
// For bottom action menu
|
// For bottom action menu
|
||||||
@ -279,9 +283,11 @@ private fun MangaScreenSmallImpl(
|
|||||||
}
|
}
|
||||||
val animatedTitleAlpha by animateFloatAsState(
|
val animatedTitleAlpha by animateFloatAsState(
|
||||||
if (firstVisibleItemIndex > 0) 1f else 0f,
|
if (firstVisibleItemIndex > 0) 1f else 0f,
|
||||||
|
label = "titleAlpha",
|
||||||
)
|
)
|
||||||
val animatedBgAlpha by animateFloatAsState(
|
val animatedBgAlpha by animateFloatAsState(
|
||||||
if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f,
|
if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f,
|
||||||
|
label = "bgAlpha",
|
||||||
)
|
)
|
||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
@ -383,10 +389,13 @@ private fun MangaScreenSmallImpl(
|
|||||||
MangaActionRow(
|
MangaActionRow(
|
||||||
favorite = state.manga.favorite,
|
favorite = state.manga.favorite,
|
||||||
trackingCount = state.trackingCount,
|
trackingCount = state.trackingCount,
|
||||||
|
fetchInterval = fetchInterval,
|
||||||
|
isUserIntervalMode = state.manga.fetchInterval < 0,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
onWebViewClicked = onWebViewClicked,
|
onWebViewClicked = onWebViewClicked,
|
||||||
onWebViewLongClicked = onWebViewLongClicked,
|
onWebViewLongClicked = onWebViewLongClicked,
|
||||||
onTrackingClicked = onTrackingClicked,
|
onTrackingClicked = onTrackingClicked,
|
||||||
|
onEditIntervalClicked = onEditIntervalClicked,
|
||||||
onEditCategory = onEditCategoryClicked,
|
onEditCategory = onEditCategoryClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -419,7 +428,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
@ -436,10 +444,10 @@ private fun MangaScreenSmallImpl(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreenLargeImpl(
|
fun MangaScreenLargeImpl(
|
||||||
state: MangaScreenState.Success,
|
state: MangaScreenModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
dateRelativeTime: Int,
|
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
|
fetchInterval: Int?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
@ -466,6 +474,7 @@ fun MangaScreenLargeImpl(
|
|||||||
onShareClicked: (() -> Unit)?,
|
onShareClicked: (() -> Unit)?,
|
||||||
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
||||||
onEditCategoryClicked: (() -> Unit)?,
|
onEditCategoryClicked: (() -> Unit)?,
|
||||||
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onMigrateClicked: (() -> Unit)?,
|
onMigrateClicked: (() -> Unit)?,
|
||||||
|
|
||||||
// For bottom action menu
|
// For bottom action menu
|
||||||
@ -596,10 +605,13 @@ fun MangaScreenLargeImpl(
|
|||||||
MangaActionRow(
|
MangaActionRow(
|
||||||
favorite = state.manga.favorite,
|
favorite = state.manga.favorite,
|
||||||
trackingCount = state.trackingCount,
|
trackingCount = state.trackingCount,
|
||||||
|
fetchInterval = fetchInterval,
|
||||||
|
isUserIntervalMode = state.manga.fetchInterval < 0,
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
onWebViewClicked = onWebViewClicked,
|
onWebViewClicked = onWebViewClicked,
|
||||||
onWebViewLongClicked = onWebViewLongClicked,
|
onWebViewLongClicked = onWebViewLongClicked,
|
||||||
onTrackingClicked = onTrackingClicked,
|
onTrackingClicked = onTrackingClicked,
|
||||||
|
onEditIntervalClicked = onEditIntervalClicked,
|
||||||
onEditCategory = onEditCategoryClicked,
|
onEditCategory = onEditCategoryClicked,
|
||||||
)
|
)
|
||||||
ExpandableMangaDescription(
|
ExpandableMangaDescription(
|
||||||
@ -639,7 +651,6 @@ fun MangaScreenLargeImpl(
|
|||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = chapters,
|
chapters = chapters,
|
||||||
dateRelativeTime = dateRelativeTime,
|
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
@ -701,7 +712,6 @@ private fun SharedMangaBottomActionMenu(
|
|||||||
private fun LazyListScope.sharedChapterItems(
|
private fun LazyListScope.sharedChapterItems(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<ChapterItem>,
|
chapters: List<ChapterItem>,
|
||||||
dateRelativeTime: Int,
|
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
@ -722,7 +732,7 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.display_mode_chapter,
|
R.string.display_mode_chapter,
|
||||||
chapterDecimalFormat.format(chapterItem.chapter.chapterNumber.toDouble()),
|
formatChapterNumber(chapterItem.chapter.chapterNumber),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
chapterItem.chapter.name
|
chapterItem.chapter.name
|
||||||
@ -730,11 +740,7 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
date = chapterItem.chapter.dateUpload
|
date = chapterItem.chapter.dateUpload
|
||||||
.takeIf { it > 0L }
|
.takeIf { it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
Date(it).toRelativeString(
|
Date(it).toRelativeString(context, dateFormat)
|
||||||
context,
|
|
||||||
dateRelativeTime,
|
|
||||||
dateFormat,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
readProgress = chapterItem.chapter.lastPageRead
|
readProgress = chapterItem.chapter.lastPageRead
|
||||||
.takeIf { !chapterItem.chapter.read && it > 0L }
|
.takeIf { !chapterItem.chapter.read && it > 0L }
|
||||||
|
@ -88,7 +88,7 @@ fun MangaBottomActionMenu(
|
|||||||
var resetJob: Job? = remember { null }
|
var resetJob: Job? = remember { null }
|
||||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
(0 until 7).forEach { i -> confirm[i] = i == toConfirmIndex }
|
(0..<7).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||||
resetJob?.cancel()
|
resetJob?.cancel()
|
||||||
resetJob = scope.launch {
|
resetJob = scope.launch {
|
||||||
delay(1.seconds)
|
delay(1.seconds)
|
||||||
@ -241,7 +241,7 @@ fun LibraryBottomActionMenu(
|
|||||||
var resetJob: Job? = remember { null }
|
var resetJob: Job? = remember { null }
|
||||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
(0 until 5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
(0..<5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||||
resetJob?.cancel()
|
resetJob?.cancel()
|
||||||
resetJob = scope.launch {
|
resetJob = scope.launch {
|
||||||
delay(1.seconds)
|
delay(1.seconds)
|
||||||
|
@ -1,11 +1,23 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.domain.manga.interactor.MAX_FETCH_INTERVAL
|
||||||
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteChaptersDialog(
|
fun DeleteChaptersDialog(
|
||||||
@ -37,3 +49,51 @@ fun DeleteChaptersDialog(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SetIntervalDialog(
|
||||||
|
interval: Int,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onValueChanged: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
var selectedInterval by rememberSaveable { mutableIntStateOf(if (interval < 0) -interval else 0) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(R.string.manga_modify_calculated_interval_title)) },
|
||||||
|
text = {
|
||||||
|
BoxWithConstraints(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
||||||
|
val items = (0..MAX_FETCH_INTERVAL).map {
|
||||||
|
if (it == 0) {
|
||||||
|
stringResource(R.string.label_default)
|
||||||
|
} else {
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WheelTextPicker(
|
||||||
|
size = size,
|
||||||
|
items = items,
|
||||||
|
startIndex = selectedInterval,
|
||||||
|
onSelectionChanged = { selectedInterval = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onValueChanged(selectedInterval)
|
||||||
|
onDismissRequest()
|
||||||
|
},) {
|
||||||
|
Text(text = stringResource(R.string.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -25,6 +25,7 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
|
import androidx.compose.material.icons.filled.HourglassEmpty
|
||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.AttachMoney
|
import androidx.compose.material.icons.outlined.AttachMoney
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.Block
|
||||||
@ -43,7 +44,6 @@ import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
import androidx.compose.material3.SuggestionChip
|
import androidx.compose.material3.SuggestionChip
|
||||||
import androidx.compose.material3.SuggestionChipDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@ -84,6 +84,7 @@ import tachiyomi.presentation.core.components.material.TextButton
|
|||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
||||||
@ -165,14 +166,18 @@ fun MangaActionRow(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
trackingCount: Int,
|
trackingCount: Int,
|
||||||
|
fetchInterval: Int?,
|
||||||
|
isUserIntervalMode: Boolean,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
onTrackingClicked: (() -> Unit)?,
|
onTrackingClicked: (() -> Unit)?,
|
||||||
|
onEditIntervalClicked: (() -> Unit)?,
|
||||||
onEditCategory: (() -> Unit)?,
|
onEditCategory: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
|
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||||
|
|
||||||
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
||||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = if (favorite) {
|
title = if (favorite) {
|
||||||
stringResource(R.string.in_library)
|
stringResource(R.string.in_library)
|
||||||
@ -184,6 +189,14 @@ fun MangaActionRow(
|
|||||||
onClick = onAddToLibraryClicked,
|
onClick = onAddToLibraryClicked,
|
||||||
onLongClick = onEditCategory,
|
onLongClick = onEditCategory,
|
||||||
)
|
)
|
||||||
|
if (onEditIntervalClicked != null && fetchInterval != null) {
|
||||||
|
MangaActionButton(
|
||||||
|
title = pluralStringResource(id = R.plurals.day, count = fetchInterval.absoluteValue, fetchInterval.absoluteValue),
|
||||||
|
icon = Icons.Default.HourglassEmpty,
|
||||||
|
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||||
|
onClick = onEditIntervalClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (onTrackingClicked != null) {
|
if (onTrackingClicked != null) {
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = if (trackingCount == 0) {
|
title = if (trackingCount == 0) {
|
||||||
@ -652,11 +665,6 @@ private fun TagsChip(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
label = { Text(text = text, style = MaterialTheme.typography.bodySmall) },
|
label = { Text(text = text, style = MaterialTheme.typography.bodySmall) },
|
||||||
border = null,
|
|
||||||
colors = SuggestionChipDefaults.suggestionChipColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
|
||||||
labelColor = MaterialTheme.colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -12,7 +13,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LogoHeader() {
|
fun LogoHeader() {
|
||||||
@ -29,6 +29,6 @@ fun LogoHeader() {
|
|||||||
.size(64.dp),
|
.size(64.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import androidx.compose.material.icons.outlined.Label
|
|||||||
import androidx.compose.material.icons.outlined.QueryStats
|
import androidx.compose.material.icons.outlined.QueryStats
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -31,7 +32,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
||||||
import tachiyomi.core.Constants
|
import tachiyomi.core.Constants
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -94,7 +94,7 @@ fun MoreScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item { Divider() }
|
item { HorizontalDivider() }
|
||||||
|
|
||||||
item {
|
item {
|
||||||
val downloadQueueState = downloadQueueStateProvider()
|
val downloadQueueState = downloadQueueStateProvider()
|
||||||
@ -147,7 +147,7 @@ fun MoreScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item { Divider() }
|
item { HorizontalDivider() }
|
||||||
|
|
||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
|
@ -13,8 +13,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.structuralEqualityPolicy
|
import androidx.compose.runtime.structuralEqualityPolicy
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
|
||||||
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
|
||||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
||||||
@ -22,10 +20,10 @@ import eu.kanade.presentation.more.settings.widget.MultiSelectListPreferenceWidg
|
|||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@ -158,16 +156,6 @@ internal fun PreferenceItem(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.AppThemePreference -> {
|
|
||||||
val value by item.pref.collectAsState()
|
|
||||||
val amoled by Injekt.get<UiPreferences>().themeDarkAmoled().collectAsState()
|
|
||||||
AppThemePreferenceWidget(
|
|
||||||
title = item.title,
|
|
||||||
value = value,
|
|
||||||
amoled = amoled,
|
|
||||||
onItemClick = { scope.launch { item.pref.set(it) } },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is Preference.PreferenceItem.TrackingPreference -> {
|
is Preference.PreferenceItem.TrackingPreference -> {
|
||||||
val uName by Injekt.get<PreferenceStore>()
|
val uName by Injekt.get<PreferenceStore>()
|
||||||
.getString(TrackPreferences.trackUsername(item.service.id))
|
.getString(TrackPreferences.trackUsername(item.service.id))
|
||||||
@ -183,6 +171,9 @@ internal fun PreferenceItem(
|
|||||||
is Preference.PreferenceItem.InfoPreference -> {
|
is Preference.PreferenceItem.InfoPreference -> {
|
||||||
InfoWidget(text = item.title)
|
InfoWidget(text = item.title)
|
||||||
}
|
}
|
||||||
|
is Preference.PreferenceItem.CustomPreference -> {
|
||||||
|
item.content(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
|
||||||
import eu.kanade.presentation.more.settings.Preference.PreferenceItem
|
import eu.kanade.presentation.more.settings.Preference.PreferenceItem
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
@ -133,19 +132,6 @@ sealed class Preference {
|
|||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
||||||
) : PreferenceItem<String>()
|
) : PreferenceItem<String>()
|
||||||
|
|
||||||
/**
|
|
||||||
* A [PreferenceItem] that shows previews of [AppTheme] selection.
|
|
||||||
*/
|
|
||||||
data class AppThemePreference(
|
|
||||||
val pref: PreferenceData<AppTheme>,
|
|
||||||
override val title: String,
|
|
||||||
) : PreferenceItem<AppTheme>() {
|
|
||||||
override val enabled: Boolean = true
|
|
||||||
override val subtitle: String? = null
|
|
||||||
override val icon: ImageVector? = null
|
|
||||||
override val onValueChanged: suspend (newValue: AppTheme) -> Boolean = { true }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [PreferenceItem] for individual tracking service.
|
* A [PreferenceItem] for individual tracking service.
|
||||||
*/
|
*/
|
||||||
@ -169,6 +155,16 @@ sealed class Preference {
|
|||||||
override val icon: ImageVector? = null
|
override val icon: ImageVector? = null
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
|
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class CustomPreference(
|
||||||
|
override val title: String,
|
||||||
|
val content: @Composable (PreferenceItem<String>) -> Unit,
|
||||||
|
) : PreferenceItem<String>() {
|
||||||
|
override val enabled: Boolean = true
|
||||||
|
override val subtitle: String? = null
|
||||||
|
override val icon: ImageVector? = null
|
||||||
|
override val onValueChanged: suspend (newValue: String) -> Boolean = { true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PreferenceGroup(
|
data class PreferenceGroup(
|
||||||
|
@ -30,7 +30,6 @@ import eu.kanade.domain.base.BasePreferences
|
|||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
|
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
|
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||||
@ -65,6 +64,7 @@ import tachiyomi.core.util.lang.withUIContext
|
|||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -114,7 +114,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_debug_info),
|
title = stringResource(R.string.pref_debug_info),
|
||||||
onClick = { navigator.push(DebugInfoScreen) },
|
onClick = { navigator.push(DebugInfoScreen()) },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -221,7 +221,10 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_invalidate_download_cache),
|
title = stringResource(R.string.pref_invalidate_download_cache),
|
||||||
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
|
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
|
||||||
onClick = { Injekt.get<DownloadCache>().invalidateCache() },
|
onClick = {
|
||||||
|
Injekt.get<DownloadCache>().invalidateCache()
|
||||||
|
context.toast(R.string.download_cache_invalidated)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_clear_database),
|
title = stringResource(R.string.pref_clear_database),
|
||||||
|
@ -21,7 +21,7 @@ import eu.kanade.domain.ui.model.TabletUiMode
|
|||||||
import eu.kanade.domain.ui.model.ThemeMode
|
import eu.kanade.domain.ui.model.ThemeMode
|
||||||
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -48,7 +49,6 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
return listOf(
|
return listOf(
|
||||||
getThemeGroup(context = context, uiPreferences = uiPreferences),
|
getThemeGroup(context = context, uiPreferences = uiPreferences),
|
||||||
getDisplayGroup(context = context, uiPreferences = uiPreferences),
|
getDisplayGroup(context = context, uiPreferences = uiPreferences),
|
||||||
getTimestampGroup(uiPreferences = uiPreferences),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,8 +59,11 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
val themeModePref = uiPreferences.themeMode()
|
val themeModePref = uiPreferences.themeMode()
|
||||||
val themeMode by themeModePref.collectAsState()
|
val themeMode by themeModePref.collectAsState()
|
||||||
|
|
||||||
val appThemePref = uiPreferences.appTheme()
|
val appThemePref = uiPreferences.appTheme()
|
||||||
|
|
||||||
val amoledPref = uiPreferences.themeDarkAmoled()
|
val amoledPref = uiPreferences.themeDarkAmoled()
|
||||||
|
val amoled by amoledPref.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(themeMode) {
|
LaunchedEffect(themeMode) {
|
||||||
setAppCompatDelegateThemeMode(themeMode)
|
setAppCompatDelegateThemeMode(themeMode)
|
||||||
@ -91,10 +94,17 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.AppThemePreference(
|
Preference.PreferenceItem.CustomPreference(
|
||||||
title = stringResource(R.string.pref_app_theme),
|
title = stringResource(R.string.pref_app_theme),
|
||||||
pref = appThemePref,
|
) { item ->
|
||||||
),
|
val value by appThemePref.collectAsState()
|
||||||
|
AppThemePreferenceWidget(
|
||||||
|
title = item.title,
|
||||||
|
value = value,
|
||||||
|
amoled = amoled,
|
||||||
|
onItemClick = { appThemePref.set(it) },
|
||||||
|
)
|
||||||
|
},
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = amoledPref,
|
pref = amoledPref,
|
||||||
title = stringResource(R.string.pref_dark_theme_pure_black),
|
title = stringResource(R.string.pref_dark_theme_pure_black),
|
||||||
@ -111,6 +121,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
val langs = remember { getLangs(context) }
|
val langs = remember { getLangs(context) }
|
||||||
var currentLanguage by remember { mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") }
|
var currentLanguage by remember { mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") }
|
||||||
|
val now = remember { Date().time }
|
||||||
|
|
||||||
LaunchedEffect(currentLanguage) {
|
LaunchedEffect(currentLanguage) {
|
||||||
val locale = if (currentLanguage.isEmpty()) {
|
val locale = if (currentLanguage.isEmpty()) {
|
||||||
@ -136,31 +147,12 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = uiPreferences.tabletUiMode(),
|
pref = uiPreferences.tabletUiMode(),
|
||||||
title = stringResource(R.string.pref_tablet_ui_mode),
|
title = stringResource(R.string.pref_tablet_ui_mode),
|
||||||
entries = TabletUiMode.values().associateWith { stringResource(it.titleResId) },
|
entries = TabletUiMode.entries.associateWith { stringResource(it.titleResId) },
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
context.toast(R.string.requires_app_restart)
|
context.toast(R.string.requires_app_restart)
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getTimestampGroup(uiPreferences: UiPreferences): Preference.PreferenceGroup {
|
|
||||||
val now = remember { Date().time }
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(R.string.pref_category_timestamps),
|
|
||||||
preferenceItems = listOf(
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = uiPreferences.relativeTime(),
|
|
||||||
title = stringResource(R.string.pref_relative_format),
|
|
||||||
entries = mapOf(
|
|
||||||
0 to stringResource(R.string.off),
|
|
||||||
2 to stringResource(R.string.pref_relative_time_short),
|
|
||||||
7 to stringResource(R.string.pref_relative_time_long),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = uiPreferences.dateFormat(),
|
pref = uiPreferences.dateFormat(),
|
||||||
title = stringResource(R.string.pref_date_format),
|
title = stringResource(R.string.pref_date_format),
|
||||||
@ -173,14 +165,13 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLangs(context: Context): Map<String, String> {
|
private fun getLangs(context: Context): Map<String, String> {
|
||||||
val langs = mutableListOf<Pair<String, String>>()
|
val langs = mutableListOf<Pair<String, String>>()
|
||||||
val parser = context.resources.getXml(R.xml.locales_config)
|
val parser = context.resources.getXml(R.xml.locales_config)
|
||||||
var eventType = parser.eventType
|
var eventType = parser.eventType
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||||
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
||||||
for (i in 0 until parser.attributeCount) {
|
for (i in 0..<parser.attributeCount) {
|
||||||
if (parser.getAttributeName(i) == "name") {
|
if (parser.getAttributeName(i) == "name") {
|
||||||
val langTag = parser.getAttributeValue(i)
|
val langTag = parser.getAttributeValue(i)
|
||||||
val displayName = LocaleHelper.getDisplayName(langTag)
|
val displayName = LocaleHelper.getDisplayName(langTag)
|
||||||
|
@ -21,6 +21,7 @@ import androidx.compose.foundation.rememberScrollState
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -43,7 +44,6 @@ import androidx.core.net.toUri
|
|||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
import eu.kanade.presentation.extensions.RequestStoragePermission
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
import eu.kanade.tachiyomi.data.backup.BackupConst
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||||
@ -60,7 +60,7 @@ import kotlinx.coroutines.launch
|
|||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
import tachiyomi.domain.sync.SyncPreferences
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -280,8 +280,8 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
|
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
|
@ -22,12 +22,12 @@ import com.hippo.unifile.UniFile
|
|||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.download.service.DownloadPreferences
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -1,33 +1,18 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.DpSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
@ -36,12 +21,10 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
|||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
@ -56,8 +39,7 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
||||||
import tachiyomi.domain.manga.interactor.MAX_GRACE_PERIOD
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@ -142,13 +124,10 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
|
val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
|
||||||
val libraryUpdateDeviceRestrictionPref = libraryPreferences.libraryUpdateDeviceRestriction()
|
|
||||||
val libraryUpdateMangaRestrictionPref = libraryPreferences.libraryUpdateMangaRestriction()
|
|
||||||
val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
|
val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
|
||||||
val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
|
val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
|
||||||
|
|
||||||
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
|
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
|
||||||
val libraryUpdateMangaRestriction by libraryUpdateMangaRestrictionPref.collectAsState()
|
|
||||||
|
|
||||||
val included by libraryUpdateCategoriesPref.collectAsState()
|
val included by libraryUpdateCategoriesPref.collectAsState()
|
||||||
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
||||||
@ -169,25 +148,10 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val leadRange by libraryPreferences.leadingExpectedDays().collectAsState()
|
|
||||||
val followRange by libraryPreferences.followingExpectedDays().collectAsState()
|
|
||||||
|
|
||||||
var showFetchRangesDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
if (showFetchRangesDialog) {
|
|
||||||
LibraryExpectedRangeDialog(
|
|
||||||
initialLead = leadRange,
|
|
||||||
initialFollow = followRange,
|
|
||||||
onDismissRequest = { showFetchRangesDialog = false },
|
|
||||||
onValueChanged = { leadValue, followValue ->
|
|
||||||
libraryPreferences.leadingExpectedDays().set(leadValue)
|
|
||||||
libraryPreferences.followingExpectedDays().set(followValue)
|
|
||||||
showFetchRangesDialog = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.pref_category_library_update),
|
title = stringResource(R.string.pref_category_library_update),
|
||||||
preferenceItems = listOfNotNull(
|
preferenceItems = listOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryUpdateIntervalPref,
|
pref = libraryUpdateIntervalPref,
|
||||||
title = stringResource(R.string.pref_library_update_interval),
|
title = stringResource(R.string.pref_library_update_interval),
|
||||||
@ -205,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryUpdateDeviceRestrictionPref,
|
pref = libraryPreferences.libraryUpdateDeviceRestriction(),
|
||||||
enabled = libraryUpdateInterval > 0,
|
enabled = libraryUpdateInterval > 0,
|
||||||
title = stringResource(R.string.pref_library_update_restriction),
|
title = stringResource(R.string.pref_library_update_restriction),
|
||||||
subtitle = stringResource(R.string.restrictions),
|
subtitle = stringResource(R.string.restrictions),
|
||||||
@ -241,30 +205,16 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
title = stringResource(R.string.pref_library_update_refresh_trackers),
|
title = stringResource(R.string.pref_library_update_refresh_trackers),
|
||||||
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
|
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
|
||||||
),
|
),
|
||||||
// TODO: remove isDevFlavor checks once functionality is available
|
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryUpdateMangaRestrictionPref,
|
pref = libraryPreferences.libraryUpdateMangaRestriction(),
|
||||||
title = stringResource(R.string.pref_library_update_manga_restriction),
|
title = stringResource(R.string.pref_library_update_manga_restriction),
|
||||||
entries = buildMap {
|
entries = mapOf(
|
||||||
put(MANGA_HAS_UNREAD, stringResource(R.string.pref_update_only_completely_read))
|
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
|
||||||
put(MANGA_NON_READ, stringResource(R.string.pref_update_only_started))
|
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
|
||||||
put(MANGA_NON_COMPLETED, stringResource(R.string.pref_update_only_non_completed))
|
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
|
||||||
if (isDevFlavor) {
|
MANGA_OUTSIDE_RELEASE_PERIOD to stringResource(R.string.pref_update_only_in_release_period),
|
||||||
put(MANGA_OUTSIDE_RELEASE_PERIOD, stringResource(R.string.pref_update_only_in_release_period))
|
),
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_update_release_grace_period),
|
|
||||||
subtitle = listOf(
|
|
||||||
pluralStringResource(R.plurals.pref_update_release_leading_days, leadRange, leadRange),
|
|
||||||
pluralStringResource(R.plurals.pref_update_release_following_days, followRange, followRange),
|
|
||||||
).joinToString(),
|
|
||||||
onClick = { showFetchRangesDialog = true },
|
|
||||||
).takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction && isDevFlavor },
|
|
||||||
Preference.PreferenceItem.InfoPreference(
|
|
||||||
title = stringResource(R.string.pref_update_release_grace_period_info),
|
|
||||||
).takeIf { MANGA_OUTSIDE_RELEASE_PERIOD in libraryUpdateMangaRestriction && isDevFlavor },
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = libraryPreferences.newShowUpdatesCount(),
|
pref = libraryPreferences.newShowUpdatesCount(),
|
||||||
title = stringResource(R.string.pref_library_update_show_tab_badge),
|
title = stringResource(R.string.pref_library_update_show_tab_badge),
|
||||||
@ -303,79 +253,4 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun LibraryExpectedRangeDialog(
|
|
||||||
initialLead: Int,
|
|
||||||
initialFollow: Int,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onValueChanged: (portrait: Int, landscape: Int) -> Unit,
|
|
||||||
) {
|
|
||||||
var leadValue by rememberSaveable { mutableIntStateOf(initialLead) }
|
|
||||||
var followValue by rememberSaveable { mutableIntStateOf(initialFollow) }
|
|
||||||
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(R.string.pref_update_release_grace_period)) },
|
|
||||||
text = {
|
|
||||||
Column {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
text = pluralStringResource(R.plurals.pref_update_release_leading_days, leadValue, leadValue),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
maxLines = 1,
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
text = pluralStringResource(R.plurals.pref_update_release_following_days, followValue, followValue),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
maxLines = 1,
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BoxWithConstraints(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
|
||||||
val items = (0..MAX_GRACE_PERIOD).map(Int::toString)
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
WheelTextPicker(
|
|
||||||
size = size,
|
|
||||||
items = items,
|
|
||||||
startIndex = leadValue,
|
|
||||||
onSelectionChanged = {
|
|
||||||
leadValue = it
|
|
||||||
},
|
|
||||||
)
|
|
||||||
WheelTextPicker(
|
|
||||||
size = size,
|
|
||||||
items = items,
|
|
||||||
startIndex = followValue,
|
|
||||||
onSelectionChanged = {
|
|
||||||
followValue = it
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { onValueChanged(leadValue, followValue) }) {
|
|
||||||
Text(text = stringResource(R.string.action_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,12 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
@ -33,7 +33,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPref.defaultReadingMode(),
|
pref = readerPref.defaultReadingMode(),
|
||||||
title = stringResource(R.string.pref_viewer_type),
|
title = stringResource(R.string.pref_viewer_type),
|
||||||
entries = ReadingModeType.values().drop(1)
|
entries = ReadingModeType.entries.drop(1)
|
||||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
@ -84,7 +84,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.defaultOrientationType(),
|
pref = readerPreferences.defaultOrientationType(),
|
||||||
title = stringResource(R.string.pref_rotation_type),
|
title = stringResource(R.string.pref_rotation_type),
|
||||||
entries = OrientationType.values().drop(1)
|
entries = OrientationType.entries.drop(1)
|
||||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
@ -17,6 +17,7 @@ import androidx.compose.foundation.text.KeyboardActions
|
|||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -53,7 +54,6 @@ import eu.kanade.presentation.components.UpIcon
|
|||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||||
@ -139,7 +139,7 @@ class SettingsSearchScreen : Screen() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
|
@ -10,11 +10,11 @@ import androidx.compose.ui.res.pluralStringResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = securityPreferences.secureScreen(),
|
pref = securityPreferences.secureScreen(),
|
||||||
title = stringResource(R.string.secure_screen),
|
title = stringResource(R.string.secure_screen),
|
||||||
entries = SecurityPreferences.SecureScreenMode.values()
|
entries = SecurityPreferences.SecureScreenMode.entries
|
||||||
.associateWith { stringResource(it.titleResId) },
|
.associateWith { stringResource(it.titleResId) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.InfoPreference(stringResource(R.string.secure_screen_summary)),
|
Preference.PreferenceItem.InfoPreference(stringResource(R.string.secure_screen_summary)),
|
||||||
|
@ -15,6 +15,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
@ -45,6 +46,7 @@ class OpenSourceLibraryLicenseScreen(
|
|||||||
Text(
|
Text(
|
||||||
text = name,
|
text = name,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.FlipToBack
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
@ -14,10 +15,12 @@ import androidx.compose.material.icons.outlined.SelectAll
|
|||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@ -47,8 +50,6 @@ import tachiyomi.data.Database
|
|||||||
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
|
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.domain.source.model.SourceWithCount
|
import tachiyomi.domain.source.model.SourceWithCount
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
@ -135,7 +136,7 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
FastScrollLazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
items(s.items) { sourceWithCount ->
|
items(s.items) { sourceWithCount ->
|
||||||
@ -148,7 +149,7 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -269,12 +270,15 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
|
|||||||
state.copy(showConfirmation = false)
|
state.copy(showConfirmation = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class State {
|
sealed interface State {
|
||||||
object Loading : State()
|
@Immutable
|
||||||
|
data object Loading : State
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class Ready(
|
data class Ready(
|
||||||
val items: List<SourceWithCount>,
|
val items: List<SourceWithCount>,
|
||||||
val selection: List<Long> = emptyList(),
|
val selection: List<Long> = emptyList(),
|
||||||
val showConfirmation: Boolean = false,
|
val showConfirmation: Boolean = false,
|
||||||
) : State()
|
) : State
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,11 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
|
|||||||
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
|
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
object BackupSchemaScreen : Screen() {
|
class BackupSchemaScreen : Screen() {
|
||||||
|
|
||||||
const val title = "Backup file schema"
|
companion object {
|
||||||
|
const val title = "Backup file schema"
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
@ -18,7 +18,8 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import kotlinx.coroutines.guava.await
|
import kotlinx.coroutines.guava.await
|
||||||
|
|
||||||
object DebugInfoScreen : Screen() {
|
class DebugInfoScreen : Screen() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
@ -29,11 +30,11 @@ object DebugInfoScreen : Screen() {
|
|||||||
listOf(
|
listOf(
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = WorkerInfoScreen.title,
|
title = WorkerInfoScreen.title,
|
||||||
onClick = { navigator.push(WorkerInfoScreen) },
|
onClick = { navigator.push(WorkerInfoScreen()) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = BackupSchemaScreen.title,
|
title = BackupSchemaScreen.title,
|
||||||
onClick = { navigator.push(BackupSchemaScreen) },
|
onClick = { navigator.push(BackupSchemaScreen()) },
|
||||||
),
|
),
|
||||||
getAppInfoGroup(),
|
getAppInfoGroup(),
|
||||||
getDeviceInfoGroup(),
|
getDeviceInfoGroup(),
|
||||||
@ -67,11 +68,15 @@ object DebugInfoScreen : Screen() {
|
|||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
private fun getWebViewVersion(): String {
|
private fun getWebViewVersion(): String {
|
||||||
val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?"
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val pm = LocalContext.current.packageManager
|
val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?"
|
||||||
val label = webView.applicationInfo.loadLabel(pm)
|
val pm = LocalContext.current.packageManager
|
||||||
val version = webView.versionName
|
val label = webView.applicationInfo.loadLabel(pm)
|
||||||
return "$label $version"
|
val version = webView.versionName
|
||||||
|
return "$label $version"
|
||||||
|
} else {
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -43,9 +43,11 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
object WorkerInfoScreen : Screen() {
|
class WorkerInfoScreen : Screen() {
|
||||||
|
|
||||||
const val title = "Worker info"
|
companion object {
|
||||||
|
const val title = "Worker info"
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
@ -22,6 +22,7 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material3.DividerDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@ -44,7 +45,6 @@ import eu.kanade.presentation.theme.TachiyomiTheme
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||||
import tachiyomi.presentation.core.components.material.DIVIDER_ALPHA
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
import tachiyomi.presentation.core.util.ThemePreviews
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
@ -75,7 +75,7 @@ private fun AppThemesList(
|
|||||||
onItemClick: (AppTheme) -> Unit,
|
onItemClick: (AppTheme) -> Unit,
|
||||||
) {
|
) {
|
||||||
val appThemes = remember {
|
val appThemes = remember {
|
||||||
AppTheme.values()
|
AppTheme.entries
|
||||||
.filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
|
.filterNot { it.titleResId == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
|
||||||
}
|
}
|
||||||
LazyRow(
|
LazyRow(
|
||||||
@ -123,7 +123,6 @@ fun AppThemePreviewItem(
|
|||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val dividerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA)
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -133,7 +132,7 @@ fun AppThemePreviewItem(
|
|||||||
color = if (selected) {
|
color = if (selected) {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
} else {
|
} else {
|
||||||
dividerColor
|
DividerDefaults.color
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(17.dp),
|
shape = RoundedCornerShape(17.dp),
|
||||||
)
|
)
|
||||||
@ -180,7 +179,7 @@ fun AppThemePreviewItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp, top = 2.dp)
|
.padding(start = 8.dp, top = 2.dp)
|
||||||
.background(
|
.background(
|
||||||
color = dividerColor,
|
color = DividerDefaults.color,
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
)
|
)
|
||||||
.fillMaxWidth(0.5f)
|
.fillMaxWidth(0.5f)
|
||||||
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -25,7 +26,6 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
|
|
||||||
@ -69,8 +69,8 @@ fun <T> ListPreferenceWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
|
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -15,6 +15,7 @@ import androidx.compose.material.icons.rounded.CheckBox
|
|||||||
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||||
import androidx.compose.material.icons.rounded.DisabledByDefault
|
import androidx.compose.material.icons.rounded.DisabledByDefault
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -29,7 +30,6 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
|
|
||||||
@ -115,8 +115,8 @@ fun <T> TriStateListDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listState.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
|
if (!listState.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!listState.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (!listState.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3,9 +3,9 @@ package eu.kanade.presentation.more.stats
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import eu.kanade.presentation.more.stats.data.StatsData
|
import eu.kanade.presentation.more.stats.data.StatsData
|
||||||
|
|
||||||
sealed class StatsScreenState {
|
sealed interface StatsScreenState {
|
||||||
@Immutable
|
@Immutable
|
||||||
object Loading : StatsScreenState()
|
data object Loading : StatsScreenState
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Success(
|
data class Success(
|
||||||
@ -13,5 +13,5 @@ sealed class StatsScreenState {
|
|||||||
val titles: StatsData.Titles,
|
val titles: StatsData.Titles,
|
||||||
val chapters: StatsData.Chapters,
|
val chapters: StatsData.Chapters,
|
||||||
val trackers: StatsData.Trackers,
|
val trackers: StatsData.Trackers,
|
||||||
) : StatsScreenState()
|
) : StatsScreenState
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
package eu.kanade.presentation.more.stats.data
|
package eu.kanade.presentation.more.stats.data
|
||||||
|
|
||||||
sealed class StatsData {
|
sealed interface StatsData {
|
||||||
|
|
||||||
data class Overview(
|
data class Overview(
|
||||||
val libraryMangaCount: Int,
|
val libraryMangaCount: Int,
|
||||||
val completedMangaCount: Int,
|
val completedMangaCount: Int,
|
||||||
val totalReadDuration: Long,
|
val totalReadDuration: Long,
|
||||||
) : StatsData()
|
) : StatsData
|
||||||
|
|
||||||
data class Titles(
|
data class Titles(
|
||||||
val globalUpdateItemCount: Int,
|
val globalUpdateItemCount: Int,
|
||||||
val startedMangaCount: Int,
|
val startedMangaCount: Int,
|
||||||
val localMangaCount: Int,
|
val localMangaCount: Int,
|
||||||
) : StatsData()
|
) : StatsData
|
||||||
|
|
||||||
data class Chapters(
|
data class Chapters(
|
||||||
val totalChapterCount: Int,
|
val totalChapterCount: Int,
|
||||||
val readChapterCount: Int,
|
val readChapterCount: Int,
|
||||||
val downloadCount: Int,
|
val downloadCount: Int,
|
||||||
) : StatsData()
|
) : StatsData
|
||||||
|
|
||||||
data class Trackers(
|
data class Trackers(
|
||||||
val trackedTitleCount: Int,
|
val trackedTitleCount: Int,
|
||||||
val meanScore: Double,
|
val meanScore: Double,
|
||||||
val trackerCount: Int,
|
val trackerCount: Int,
|
||||||
) : StatsData()
|
) : StatsData
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.reader.settings
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -10,15 +11,13 @@ import androidx.core.graphics.alpha
|
|||||||
import androidx.core.graphics.blue
|
import androidx.core.graphics.blue
|
||||||
import androidx.core.graphics.green
|
import androidx.core.graphics.green
|
||||||
import androidx.core.graphics.red
|
import androidx.core.graphics.red
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import tachiyomi.core.preference.getAndSet
|
import tachiyomi.core.preference.getAndSet
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.SettingsFlowRow
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import tachiyomi.presentation.core.components.material.ChoiceChip
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
|
||||||
@ -44,10 +43,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
|
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_custom_brightness),
|
label = stringResource(R.string.pref_custom_brightness),
|
||||||
checked = customBrightness,
|
pref = screenModel.preferences.customBrightness(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::customBrightness)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,10 +67,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
|
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_custom_color_filter),
|
label = stringResource(R.string.pref_custom_color_filter),
|
||||||
checked = colorFilter,
|
pref = screenModel.preferences.colorFilter(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::colorFilter)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if (colorFilter) {
|
if (colorFilter) {
|
||||||
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
|
||||||
@ -124,32 +117,24 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
)
|
)
|
||||||
|
|
||||||
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
|
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
|
||||||
SettingsFlowRow(R.string.pref_color_filter_mode) {
|
SettingsChipRow(R.string.pref_color_filter_mode) {
|
||||||
colorFilterModes.mapIndexed { index, it ->
|
colorFilterModes.mapIndexed { index, it ->
|
||||||
ChoiceChip(
|
FilterChip(
|
||||||
isSelected = colorFilterMode == index,
|
selected = colorFilterMode == index,
|
||||||
onClick = { screenModel.preferences.colorFilterMode().set(index) },
|
onClick = { screenModel.preferences.colorFilterMode().set(index) },
|
||||||
content = { Text(it) },
|
label = { Text(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val grayscale by screenModel.preferences.grayscale().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_grayscale),
|
label = stringResource(R.string.pref_grayscale),
|
||||||
checked = grayscale,
|
pref = screenModel.preferences.grayscale(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::grayscale)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
val invertedColors by screenModel.preferences.invertedColors().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_inverted_colors),
|
label = stringResource(R.string.pref_inverted_colors),
|
||||||
checked = invertedColors,
|
pref = screenModel.preferences.invertedColors(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::invertedColors)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
package eu.kanade.presentation.reader.settings
|
package eu.kanade.presentation.reader.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.SettingsFlowRow
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
import tachiyomi.presentation.core.components.material.ChoiceChip
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
private val themes = listOf(
|
private val themes = listOf(
|
||||||
R.string.black_background to 1,
|
R.string.black_background to 1,
|
||||||
@ -23,77 +22,49 @@ private val themes = listOf(
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
||||||
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
|
||||||
SettingsFlowRow(R.string.pref_reader_theme) {
|
SettingsChipRow(R.string.pref_reader_theme) {
|
||||||
themes.map { (labelRes, value) ->
|
themes.map { (labelRes, value) ->
|
||||||
ChoiceChip(
|
FilterChip(
|
||||||
isSelected = readerTheme == value,
|
selected = readerTheme == value,
|
||||||
onClick = { screenModel.preferences.readerTheme().set(value) },
|
onClick = { screenModel.preferences.readerTheme().set(value) },
|
||||||
content = { Text(stringResource(labelRes)) },
|
label = { Text(stringResource(labelRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val showPageNumber by screenModel.preferences.showPageNumber().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_show_page_number),
|
label = stringResource(R.string.pref_show_page_number),
|
||||||
checked = showPageNumber,
|
pref = screenModel.preferences.showPageNumber(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::showPageNumber)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val fullscreen by screenModel.preferences.fullscreen().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_fullscreen),
|
label = stringResource(R.string.pref_fullscreen),
|
||||||
checked = fullscreen,
|
pref = screenModel.preferences.fullscreen(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::fullscreen)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: hide if there's no cutout
|
// TODO: hide if there's no cutout
|
||||||
val cutoutShort by screenModel.preferences.cutoutShort().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_cutout_short),
|
label = stringResource(R.string.pref_cutout_short),
|
||||||
checked = cutoutShort,
|
pref = screenModel.preferences.cutoutShort(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::cutoutShort)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val keepScreenOn by screenModel.preferences.keepScreenOn().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_keep_screen_on),
|
label = stringResource(R.string.pref_keep_screen_on),
|
||||||
checked = keepScreenOn,
|
pref = screenModel.preferences.keepScreenOn(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::keepScreenOn)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val readWithLongTap by screenModel.preferences.readWithLongTap().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_read_with_long_tap),
|
label = stringResource(R.string.pref_read_with_long_tap),
|
||||||
checked = readWithLongTap,
|
pref = screenModel.preferences.readWithLongTap(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::readWithLongTap)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val alwaysShowChapterTransition by screenModel.preferences.alwaysShowChapterTransition().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_always_show_chapter_transition),
|
label = stringResource(R.string.pref_always_show_chapter_transition),
|
||||||
checked = alwaysShowChapterTransition,
|
pref = screenModel.preferences.alwaysShowChapterTransition(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::alwaysShowChapterTransition)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val pageTransitions by screenModel.preferences.pageTransitions().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_page_transitions),
|
label = stringResource(R.string.pref_page_transitions),
|
||||||
checked = pageTransitions,
|
pref = screenModel.preferences.pageTransitions(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::pageTransitions)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.presentation.reader.settings
|
package eu.kanade.presentation.reader.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@ -30,35 +32,38 @@ fun ReaderSettingsDialog(
|
|||||||
)
|
)
|
||||||
val pagerState = rememberPagerState { tabTitles.size }
|
val pagerState = rememberPagerState { tabTitles.size }
|
||||||
|
|
||||||
TabbedDialog(
|
BoxWithConstraints {
|
||||||
onDismissRequest = {
|
TabbedDialog(
|
||||||
onDismissRequest()
|
modifier = Modifier.heightIn(max = maxHeight * 0.75f),
|
||||||
onShowMenus()
|
onDismissRequest = {
|
||||||
},
|
onDismissRequest()
|
||||||
tabTitles = tabTitles,
|
|
||||||
pagerState = pagerState,
|
|
||||||
) { page ->
|
|
||||||
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
|
|
||||||
|
|
||||||
LaunchedEffect(pagerState.currentPage) {
|
|
||||||
if (pagerState.currentPage == 2) {
|
|
||||||
window?.setDimAmount(0f)
|
|
||||||
onHideMenus()
|
|
||||||
} else {
|
|
||||||
window?.setDimAmount(0.5f)
|
|
||||||
onShowMenus()
|
onShowMenus()
|
||||||
}
|
},
|
||||||
}
|
tabTitles = tabTitles,
|
||||||
|
pagerState = pagerState,
|
||||||
|
) { page ->
|
||||||
|
val window = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||||
|
|
||||||
Column(
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
modifier = Modifier
|
if (pagerState.currentPage == 2) {
|
||||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
window?.setDimAmount(0f)
|
||||||
.verticalScroll(rememberScrollState()),
|
onHideMenus()
|
||||||
) {
|
} else {
|
||||||
when (page) {
|
window?.setDimAmount(0.5f)
|
||||||
0 -> ReadingModePage(screenModel)
|
onShowMenus()
|
||||||
1 -> GeneralPage(screenModel)
|
}
|
||||||
2 -> ColorFilterPage(screenModel)
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = TabbedDialogPaddings.Vertical)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
when (page) {
|
||||||
|
0 -> ReadingModePage(screenModel)
|
||||||
|
1 -> GeneralPage(screenModel)
|
||||||
|
2 -> ColorFilterPage(screenModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.presentation.reader.settings
|
package eu.kanade.presentation.reader.settings
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -8,7 +10,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.domain.manga.model.orientationType
|
import eu.kanade.domain.manga.model.orientationType
|
||||||
import eu.kanade.domain.manga.model.readingModeType
|
import eu.kanade.domain.manga.model.readingModeType
|
||||||
import eu.kanade.presentation.util.collectAsState
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
@ -18,13 +19,14 @@ import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
|||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
import tachiyomi.presentation.core.components.SelectItem
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
private val readingModeOptions = ReadingModeType.values().map { it.stringRes to it }
|
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
|
||||||
private val orientationTypeOptions = OrientationType.values().map { it.stringRes to it }
|
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
|
||||||
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.values().map { it.titleResId to it }
|
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.entries.map { it.titleResId to it }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
||||||
@ -32,21 +34,25 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
|
|||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by screenModel.mangaFlow.collectAsState()
|
||||||
|
|
||||||
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
|
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
|
||||||
SelectItem(
|
SettingsChipRow(R.string.pref_category_reading_mode) {
|
||||||
label = stringResource(R.string.pref_category_reading_mode),
|
readingModeOptions.map { (stringRes, it) ->
|
||||||
options = readingModeOptions.map { stringResource(it.first) }.toTypedArray(),
|
FilterChip(
|
||||||
selectedIndex = readingModeOptions.indexOfFirst { it.second == readingMode },
|
selected = it == readingMode,
|
||||||
) {
|
onClick = { screenModel.onChangeReadingMode(it) },
|
||||||
screenModel.onChangeReadingMode(readingModeOptions[it].second)
|
label = { Text(stringResource(stringRes)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
|
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
|
||||||
SelectItem(
|
SettingsChipRow(R.string.rotation_type) {
|
||||||
label = stringResource(R.string.rotation_type),
|
orientationTypeOptions.map { (stringRes, it) ->
|
||||||
options = orientationTypeOptions.map { stringResource(it.first) }.toTypedArray(),
|
FilterChip(
|
||||||
selectedIndex = orientationTypeOptions.indexOfFirst { it.second == orientationType },
|
selected = it == orientationType,
|
||||||
) {
|
onClick = { screenModel.onChangeOrientation(it) },
|
||||||
screenModel.onChangeOrientation(orientationTypeOptions[it].second)
|
label = { Text(stringResource(stringRes)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewer by screenModel.viewerFlow.collectAsState()
|
val viewer by screenModel.viewerFlow.collectAsState()
|
||||||
@ -62,105 +68,74 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
|
|||||||
HeadingItem(R.string.pager_viewer)
|
HeadingItem(R.string.pager_viewer)
|
||||||
|
|
||||||
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
|
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
|
||||||
SelectItem(
|
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
|
||||||
label = stringResource(R.string.pref_viewer_nav),
|
TapZonesItems(
|
||||||
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(),
|
selected = navigationModePager,
|
||||||
selectedIndex = navigationModePager,
|
onSelect = screenModel.preferences.navigationModePager()::set,
|
||||||
onSelect = { screenModel.preferences.navigationModePager().set(it) },
|
invertMode = pagerNavInverted,
|
||||||
|
onSelectInvertMode = screenModel.preferences.pagerNavInverted()::set,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (navigationModePager != 5) {
|
|
||||||
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
|
|
||||||
SelectItem(
|
|
||||||
label = stringResource(R.string.pref_read_with_tapping_inverted),
|
|
||||||
options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(),
|
|
||||||
selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == pagerNavInverted },
|
|
||||||
onSelect = {
|
|
||||||
screenModel.preferences.pagerNavInverted().set(tappingInvertModeOptions[it].second)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
|
val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
|
||||||
SelectItem(
|
SettingsChipRow(R.string.pref_image_scale_type) {
|
||||||
label = stringResource(R.string.pref_image_scale_type),
|
ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
|
||||||
options = ReaderPreferences.ImageScaleType.map { stringResource(it) }.toTypedArray(),
|
FilterChip(
|
||||||
selectedIndex = imageScaleType - 1,
|
selected = imageScaleType == index + 1,
|
||||||
onSelect = { screenModel.preferences.imageScaleType().set(it + 1) },
|
onClick = { screenModel.preferences.imageScaleType().set(index + 1) },
|
||||||
)
|
label = { Text(stringResource(it)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val zoomStart by screenModel.preferences.zoomStart().collectAsState()
|
val zoomStart by screenModel.preferences.zoomStart().collectAsState()
|
||||||
SelectItem(
|
SettingsChipRow(R.string.pref_zoom_start) {
|
||||||
label = stringResource(R.string.pref_zoom_start),
|
ReaderPreferences.ZoomStart.mapIndexed { index, it ->
|
||||||
options = ReaderPreferences.ZoomStart.map { stringResource(it) }.toTypedArray(),
|
FilterChip(
|
||||||
selectedIndex = zoomStart - 1,
|
selected = zoomStart == index + 1,
|
||||||
onSelect = { screenModel.preferences.zoomStart().set(it + 1) },
|
onClick = { screenModel.preferences.zoomStart().set(index + 1) },
|
||||||
)
|
label = { Text(stringResource(it)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val cropBorders by screenModel.preferences.cropBorders().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_crop_borders),
|
label = stringResource(R.string.pref_crop_borders),
|
||||||
checked = cropBorders,
|
pref = screenModel.preferences.cropBorders(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::cropBorders)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val landscapeZoom by screenModel.preferences.landscapeZoom().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_landscape_zoom),
|
label = stringResource(R.string.pref_landscape_zoom),
|
||||||
checked = landscapeZoom,
|
pref = screenModel.preferences.landscapeZoom(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::landscapeZoom)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val navigateToPan by screenModel.preferences.navigateToPan().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_navigate_pan),
|
label = stringResource(R.string.pref_navigate_pan),
|
||||||
checked = navigateToPan,
|
pref = screenModel.preferences.navigateToPan(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::navigateToPan)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
|
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_dual_page_split),
|
label = stringResource(R.string.pref_dual_page_split),
|
||||||
checked = dualPageSplitPaged,
|
pref = screenModel.preferences.dualPageSplitPaged(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::dualPageSplitPaged)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageSplitPaged) {
|
if (dualPageSplitPaged) {
|
||||||
val dualPageInvertPaged by screenModel.preferences.dualPageInvertPaged().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_dual_page_invert),
|
label = stringResource(R.string.pref_dual_page_invert),
|
||||||
checked = dualPageInvertPaged,
|
pref = screenModel.preferences.dualPageInvertPaged(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::dualPageInvertPaged)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
|
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_page_rotate),
|
label = stringResource(R.string.pref_page_rotate),
|
||||||
checked = dualPageRotateToFit,
|
pref = screenModel.preferences.dualPageRotateToFit(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFit)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageRotateToFit) {
|
if (dualPageRotateToFit) {
|
||||||
val dualPageRotateToFitInvert by screenModel.preferences.dualPageRotateToFitInvert().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_page_rotate_invert),
|
label = stringResource(R.string.pref_page_rotate_invert),
|
||||||
checked = dualPageRotateToFitInvert,
|
pref = screenModel.preferences.dualPageRotateToFitInvert(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::dualPageRotateToFitInvert)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,25 +147,14 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
|
|||||||
HeadingItem(R.string.webtoon_viewer)
|
HeadingItem(R.string.webtoon_viewer)
|
||||||
|
|
||||||
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
|
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
|
||||||
SelectItem(
|
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
|
||||||
label = stringResource(R.string.pref_viewer_nav),
|
TapZonesItems(
|
||||||
options = ReaderPreferences.TapZones.map { stringResource(it) }.toTypedArray(),
|
selected = navigationModeWebtoon,
|
||||||
selectedIndex = navigationModeWebtoon,
|
onSelect = screenModel.preferences.navigationModeWebtoon()::set,
|
||||||
onSelect = { screenModel.preferences.navigationModeWebtoon().set(it) },
|
invertMode = webtoonNavInverted,
|
||||||
|
onSelectInvertMode = screenModel.preferences.webtoonNavInverted()::set,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (navigationModeWebtoon != 5) {
|
|
||||||
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
|
|
||||||
SelectItem(
|
|
||||||
label = stringResource(R.string.pref_read_with_tapping_inverted),
|
|
||||||
options = tappingInvertModeOptions.map { stringResource(it.first) }.toTypedArray(),
|
|
||||||
selectedIndex = tappingInvertModeOptions.indexOfFirst { it.second == webtoonNavInverted },
|
|
||||||
onSelect = {
|
|
||||||
screenModel.preferences.webtoonNavInverted().set(tappingInvertModeOptions[it].second)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
label = stringResource(R.string.pref_webtoon_side_padding),
|
label = stringResource(R.string.pref_webtoon_side_padding),
|
||||||
@ -203,54 +167,63 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val cropBordersWebtoon by screenModel.preferences.cropBordersWebtoon().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_crop_borders),
|
label = stringResource(R.string.pref_crop_borders),
|
||||||
checked = cropBordersWebtoon,
|
pref = screenModel.preferences.cropBordersWebtoon(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::cropBordersWebtoon)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
|
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_dual_page_split),
|
label = stringResource(R.string.pref_dual_page_split),
|
||||||
checked = dualPageSplitWebtoon,
|
pref = screenModel.preferences.dualPageSplitWebtoon(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::dualPageSplitWebtoon)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageSplitWebtoon) {
|
if (dualPageSplitWebtoon) {
|
||||||
val dualPageInvertWebtoon by screenModel.preferences.dualPageInvertWebtoon()
|
|
||||||
.collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_dual_page_invert),
|
label = stringResource(R.string.pref_dual_page_invert),
|
||||||
checked = dualPageInvertWebtoon,
|
pref = screenModel.preferences.dualPageInvertWebtoon(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::dualPageInvertWebtoon)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isReleaseBuildType) {
|
if (!isReleaseBuildType) {
|
||||||
val longStripSplitWebtoon by screenModel.preferences.longStripSplitWebtoon()
|
|
||||||
.collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_long_strip_split),
|
label = stringResource(R.string.pref_long_strip_split),
|
||||||
checked = longStripSplitWebtoon,
|
pref = screenModel.preferences.longStripSplitWebtoon(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::longStripSplitWebtoon)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val webtoonDoubleTapZoomEnabled by screenModel.preferences.webtoonDoubleTapZoomEnabled().collectAsState()
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(R.string.pref_double_tap_zoom),
|
label = stringResource(R.string.pref_double_tap_zoom),
|
||||||
checked = webtoonDoubleTapZoomEnabled,
|
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(),
|
||||||
onClick = {
|
|
||||||
screenModel.togglePreference(ReaderPreferences::webtoonDoubleTapZoomEnabled)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.TapZonesItems(
|
||||||
|
selected: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
invertMode: ReaderPreferences.TappingInvertMode,
|
||||||
|
onSelectInvertMode: (ReaderPreferences.TappingInvertMode) -> Unit,
|
||||||
|
) {
|
||||||
|
SettingsChipRow(R.string.pref_viewer_nav) {
|
||||||
|
ReaderPreferences.TapZones.mapIndexed { index, it ->
|
||||||
|
FilterChip(
|
||||||
|
selected = selected == index,
|
||||||
|
onClick = { onSelect(index) },
|
||||||
|
label = { Text(stringResource(it)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected != 5) {
|
||||||
|
SettingsChipRow(R.string.pref_read_with_tapping_inverted) {
|
||||||
|
tappingInvertModeOptions.map { (stringRes, mode) ->
|
||||||
|
FilterChip(
|
||||||
|
selected = mode == invertMode,
|
||||||
|
onClick = { onSelectInvertMode(mode) },
|
||||||
|
label = { Text(stringResource(stringRes)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,11 +24,13 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -50,8 +52,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.VerticalDivider
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
|
|
||||||
private const val UnsetStatusTextAlpha = 0.5F
|
private const val UnsetStatusTextAlpha = 0.5F
|
||||||
@ -98,7 +98,7 @@ fun TrackInfoDialogHome(
|
|||||||
},
|
},
|
||||||
onChaptersClick = { onChapterClick(item) },
|
onChaptersClick = { onChapterClick(item) },
|
||||||
score = item.service.displayScore(item.track.toDbTrack())
|
score = item.service.displayScore(item.track.toDbTrack())
|
||||||
.takeIf { supportsScoring && item.track.score != 0F },
|
.takeIf { supportsScoring && item.track.score != 0.0 },
|
||||||
onScoreClick = { onScoreClick(item) }
|
onScoreClick = { onScoreClick(item) }
|
||||||
.takeIf { supportsScoring },
|
.takeIf { supportsScoring },
|
||||||
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) }
|
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) }
|
||||||
@ -211,7 +211,7 @@ private fun TrackInfoItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onStartDateClick != null && onEndDateClick != null) {
|
if (onStartDateClick != null && onEndDateClick != null) {
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
|
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
|
||||||
TrackDetailsItem(
|
TrackDetailsItem(
|
||||||
modifier = Modifier.weight(1F),
|
modifier = Modifier.weight(1F),
|
||||||
|
@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.DatePicker
|
import androidx.compose.material3.DatePicker
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.SelectableDates
|
import androidx.compose.material3.SelectableDates
|
||||||
@ -34,7 +35,6 @@ import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
|||||||
import tachiyomi.presentation.core.components.WheelNumberPicker
|
import tachiyomi.presentation.core.components.WheelNumberPicker
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
@ -79,8 +79,8 @@ fun TrackStatusSelector(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) Divider(modifier = Modifier.align(Alignment.TopCenter))
|
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) Divider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
},
|
},
|
||||||
onConfirm = onConfirm,
|
onConfirm = onConfirm,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
@ -33,6 +33,7 @@ import androidx.compose.material.icons.filled.CheckCircle
|
|||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -60,7 +61,6 @@ import eu.kanade.presentation.manga.components.MangaCover
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Divider
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
@ -143,7 +143,7 @@ fun TrackServiceSearch(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
@ -28,7 +28,7 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesState
|
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
@ -40,10 +40,9 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdateScreen(
|
fun UpdateScreen(
|
||||||
state: UpdatesState,
|
state: UpdatesScreenModel.State,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
lastUpdated: Long,
|
lastUpdated: Long,
|
||||||
relativeTime: Int,
|
|
||||||
onClickCover: (UpdatesItem) -> Unit,
|
onClickCover: (UpdatesItem) -> Unit,
|
||||||
onSelectAll: (Boolean) -> Unit,
|
onSelectAll: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
@ -114,7 +113,7 @@ fun UpdateScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatesUiItems(
|
updatesUiItems(
|
||||||
uiModels = state.getUiModel(context, relativeTime),
|
uiModels = state.getUiModel(context),
|
||||||
selectionMode = state.selectionMode,
|
selectionMode = state.selectionMode,
|
||||||
onUpdateSelected = onUpdateSelected,
|
onUpdateSelected = onUpdateSelected,
|
||||||
onClickCover = onClickCover,
|
onClickCover = onClickCover,
|
||||||
@ -209,7 +208,7 @@ private fun UpdatesBottomBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class UpdatesUiModel {
|
sealed interface UpdatesUiModel {
|
||||||
data class Header(val date: String) : UpdatesUiModel()
|
data class Header(val date: String) : UpdatesUiModel
|
||||||
data class Item(val item: UpdatesItem) : UpdatesUiModel()
|
data class Item(val item: UpdatesItem) : UpdatesUiModel
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.DecimalFormatSymbols
|
||||||
|
|
||||||
|
private val formatter = DecimalFormat(
|
||||||
|
"#.###",
|
||||||
|
DecimalFormatSymbols().apply { decimalSeparator = '.' },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun formatChapterNumber(chapterNumber: Double): String {
|
||||||
|
return formatter.format(chapterNumber)
|
||||||
|
}
|
@ -4,8 +4,9 @@ import android.content.pm.ApplicationInfo
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -17,9 +18,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.google.accompanist.web.AccompanistWebViewClient
|
import com.google.accompanist.web.AccompanistWebViewClient
|
||||||
import com.google.accompanist.web.LoadingState
|
import com.google.accompanist.web.LoadingState
|
||||||
@ -28,9 +31,12 @@ import com.google.accompanist.web.rememberWebViewNavigator
|
|||||||
import com.google.accompanist.web.rememberWebViewState
|
import com.google.accompanist.web.rememberWebViewState
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.system.getHtml
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -46,7 +52,53 @@ fun WebViewScreenContent(
|
|||||||
) {
|
) {
|
||||||
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers)
|
||||||
val navigator = rememberWebViewNavigator()
|
val navigator = rememberWebViewNavigator()
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var currentUrl by remember { mutableStateOf(url) }
|
var currentUrl by remember { mutableStateOf(url) }
|
||||||
|
var showCloudflareHelp by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val webClient = remember {
|
||||||
|
object : AccompanistWebViewClient() {
|
||||||
|
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
||||||
|
super.onPageStarted(view, url, favicon)
|
||||||
|
url?.let {
|
||||||
|
currentUrl = it
|
||||||
|
onUrlChange(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
scope.launch {
|
||||||
|
val html = view.getHtml()
|
||||||
|
showCloudflareHelp = "window._cf_chl_opt" in html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doUpdateVisitedHistory(
|
||||||
|
view: WebView,
|
||||||
|
url: String?,
|
||||||
|
isReload: Boolean,
|
||||||
|
) {
|
||||||
|
super.doUpdateVisitedHistory(view, url, isReload)
|
||||||
|
url?.let {
|
||||||
|
currentUrl = it
|
||||||
|
onUrlChange(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldOverrideUrlLoading(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?,
|
||||||
|
): Boolean {
|
||||||
|
request?.let {
|
||||||
|
view?.loadUrl(it.url.toString(), headers)
|
||||||
|
}
|
||||||
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -116,61 +168,38 @@ fun WebViewScreenContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
val webClient = remember {
|
Column(
|
||||||
object : AccompanistWebViewClient() {
|
modifier = Modifier.padding(contentPadding),
|
||||||
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
) {
|
||||||
super.onPageStarted(view, url, favicon)
|
if (showCloudflareHelp) {
|
||||||
url?.let {
|
WarningBanner(
|
||||||
currentUrl = it
|
textRes = R.string.information_cloudflare_help,
|
||||||
onUrlChange(it)
|
modifier = Modifier.clickable {
|
||||||
}
|
uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
|
||||||
}
|
},
|
||||||
|
)
|
||||||
override fun doUpdateVisitedHistory(
|
|
||||||
view: WebView,
|
|
||||||
url: String?,
|
|
||||||
isReload: Boolean,
|
|
||||||
) {
|
|
||||||
super.doUpdateVisitedHistory(view, url, isReload)
|
|
||||||
url?.let {
|
|
||||||
currentUrl = it
|
|
||||||
onUrlChange(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shouldOverrideUrlLoading(
|
|
||||||
view: WebView?,
|
|
||||||
request: WebResourceRequest?,
|
|
||||||
): Boolean {
|
|
||||||
request?.let {
|
|
||||||
view?.loadUrl(it.url.toString(), headers)
|
|
||||||
}
|
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebView(
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
navigator = navigator,
|
||||||
|
onCreated = { webView ->
|
||||||
|
webView.setDefaultSettings()
|
||||||
|
|
||||||
|
// Debug mode (chrome://inspect/#devices)
|
||||||
|
if (BuildConfig.DEBUG &&
|
||||||
|
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
||||||
|
) {
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers["user-agent"]?.let {
|
||||||
|
webView.settings.userAgentString = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client = webClient,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
WebView(
|
|
||||||
state = state,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(contentPadding)
|
|
||||||
.fillMaxSize(),
|
|
||||||
navigator = navigator,
|
|
||||||
onCreated = { webView ->
|
|
||||||
webView.setDefaultSettings()
|
|
||||||
|
|
||||||
// Debug mode (chrome://inspect/#devices)
|
|
||||||
if (BuildConfig.DEBUG &&
|
|
||||||
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
|
||||||
) {
|
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers["user-agent"]?.let {
|
|
||||||
webView.settings.userAgentString = it
|
|
||||||
}
|
|
||||||
},
|
|
||||||
client = webClient,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,5 +29,5 @@ object AppInfo {
|
|||||||
*
|
*
|
||||||
* @since extension-lib 1.5
|
* @since extension-lib 1.5
|
||||||
*/
|
*/
|
||||||
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.values().map { it.mime }
|
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.entries.map { it.mime }
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ import android.os.Build
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
||||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
import app.cash.sqldelight.db.SqlDriver
|
||||||
import com.squareup.sqldelight.db.SqlDriver
|
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
@ -39,11 +39,11 @@ import tachiyomi.core.provider.AndroidDownloadFolderProvider
|
|||||||
import tachiyomi.data.AndroidDatabaseHandler
|
import tachiyomi.data.AndroidDatabaseHandler
|
||||||
import tachiyomi.data.Database
|
import tachiyomi.data.Database
|
||||||
import tachiyomi.data.DatabaseHandler
|
import tachiyomi.data.DatabaseHandler
|
||||||
|
import tachiyomi.data.DateColumnAdapter
|
||||||
import tachiyomi.data.History
|
import tachiyomi.data.History
|
||||||
import tachiyomi.data.Mangas
|
import tachiyomi.data.Mangas
|
||||||
import tachiyomi.data.dateAdapter
|
import tachiyomi.data.StringListColumnAdapter
|
||||||
import tachiyomi.data.listOfStringsAdapter
|
import tachiyomi.data.UpdateStrategyColumnAdapter
|
||||||
import tachiyomi.data.updateStrategyAdapter
|
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
import tachiyomi.domain.download.service.DownloadPreferences
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
@ -92,11 +92,11 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
Database(
|
Database(
|
||||||
driver = get(),
|
driver = get(),
|
||||||
historyAdapter = History.Adapter(
|
historyAdapter = History.Adapter(
|
||||||
last_readAdapter = dateAdapter,
|
last_readAdapter = DateColumnAdapter,
|
||||||
),
|
),
|
||||||
mangasAdapter = Mangas.Adapter(
|
mangasAdapter = Mangas.Adapter(
|
||||||
genreAdapter = listOfStringsAdapter,
|
genreAdapter = StringListColumnAdapter,
|
||||||
update_strategyAdapter = updateStrategyAdapter,
|
update_strategyAdapter = UpdateStrategyColumnAdapter,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,6 @@ import eu.kanade.tachiyomi.network.NetworkPreferences
|
|||||||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.util.preference.minusAssign
|
|
||||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -23,6 +21,8 @@ import eu.kanade.tachiyomi.util.system.workManager
|
|||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
|
import tachiyomi.core.preference.minusAssign
|
||||||
|
import tachiyomi.core.preference.plusAssign
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
// Filter options
|
||||||
|
internal object BackupConst {
|
||||||
object BackupConst {
|
const val BACKUP_CATEGORY = 0x1
|
||||||
|
const val BACKUP_CATEGORY_MASK = 0x1
|
||||||
private const val NAME = "BackupRestoreServices"
|
const val BACKUP_CHAPTER = 0x2
|
||||||
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
|
const val BACKUP_CHAPTER_MASK = 0x2
|
||||||
|
const val BACKUP_HISTORY = 0x4
|
||||||
// Filter options
|
const val BACKUP_HISTORY_MASK = 0x4
|
||||||
internal const val BACKUP_CATEGORY = 0x1
|
const val BACKUP_TRACK = 0x8
|
||||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
const val BACKUP_TRACK_MASK = 0x8
|
||||||
internal const val BACKUP_CHAPTER = 0x2
|
const val BACKUP_ALL = 0xF
|
||||||
internal const val BACKUP_CHAPTER_MASK = 0x2
|
|
||||||
internal const val BACKUP_HISTORY = 0x4
|
|
||||||
internal const val BACKUP_HISTORY_MASK = 0x4
|
|
||||||
internal const val BACKUP_TRACK = 0x8
|
|
||||||
internal const val BACKUP_TRACK_MASK = 0x8
|
|
||||||
internal const val BACKUP_ALL = 0xF
|
|
||||||
}
|
}
|
||||||
|
@ -30,15 +30,15 @@ import logcat.LogPriority
|
|||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.gzip
|
import okio.gzip
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import tachiyomi.core.util.lang.toLong
|
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.data.DatabaseHandler
|
import tachiyomi.data.DatabaseHandler
|
||||||
import tachiyomi.data.Manga_sync
|
import tachiyomi.data.Manga_sync
|
||||||
import tachiyomi.data.Mangas
|
import tachiyomi.data.Mangas
|
||||||
import tachiyomi.data.updateStrategyAdapter
|
import tachiyomi.data.UpdateStrategyColumnAdapter
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
import tachiyomi.domain.history.model.HistoryUpdate
|
import tachiyomi.domain.history.model.HistoryUpdate
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
@ -60,6 +60,7 @@ class BackupManager(
|
|||||||
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
||||||
private val getCategories: GetCategories = Injekt.get()
|
private val getCategories: GetCategories = Injekt.get()
|
||||||
private val getFavorites: GetFavorites = Injekt.get()
|
private val getFavorites: GetFavorites = Injekt.get()
|
||||||
|
private val getHistory: GetHistory = Injekt.get()
|
||||||
|
|
||||||
internal val parser = ProtoBuf
|
internal val parser = ProtoBuf
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ class BackupManager(
|
|||||||
backupMangas(databaseManga, flags),
|
backupMangas(databaseManga, flags),
|
||||||
backupCategories(flags),
|
backupCategories(flags),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
backupExtensionInfo(databaseManga),
|
prepExtensionInfoForSync(databaseManga),
|
||||||
)
|
)
|
||||||
|
|
||||||
var file: UniFile? = null
|
var file: UniFile? = null
|
||||||
@ -133,7 +134,7 @@ class BackupManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
|
fun prepExtensionInfoForSync(mangas: List<Manga>): List<BackupSource> {
|
||||||
return mangas
|
return mangas
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map(Manga::source)
|
.map(Manga::source)
|
||||||
@ -204,11 +205,11 @@ class BackupManager(
|
|||||||
|
|
||||||
// Check if user wants history information in backup
|
// Check if user wants history information in backup
|
||||||
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
||||||
val historyByMangaId = handler.awaitList(true) { historyQueries.getHistoryByMangaId(manga.id) }
|
val historyByMangaId = getHistory.await(manga.id)
|
||||||
if (historyByMangaId.isNotEmpty()) {
|
if (historyByMangaId.isNotEmpty()) {
|
||||||
val history = historyByMangaId.map { history ->
|
val history = historyByMangaId.map { history ->
|
||||||
val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapter_id) }
|
val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapterId) }
|
||||||
BackupHistory(chapter.url, history.last_read?.time ?: 0L, history.time_read)
|
BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration)
|
||||||
}
|
}
|
||||||
if (history.isNotEmpty()) {
|
if (history.isNotEmpty()) {
|
||||||
mangaObject.history = history
|
mangaObject.history = history
|
||||||
@ -262,7 +263,7 @@ class BackupManager(
|
|||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
// Let the db assign the id
|
// Let the db assign the id
|
||||||
val id = handler.awaitOne {
|
val id = handler.awaitOneExecutable {
|
||||||
categoriesQueries.insert(category.name, category.order, category.flags)
|
categoriesQueries.insert(category.name, category.order, category.flags)
|
||||||
categoriesQueries.selectLastInsertedRowId()
|
categoriesQueries.selectLastInsertedRowId()
|
||||||
}
|
}
|
||||||
@ -413,7 +414,7 @@ class BackupManager(
|
|||||||
track.last_chapter_read,
|
track.last_chapter_read,
|
||||||
track.total_chapters,
|
track.total_chapters,
|
||||||
track.status,
|
track.status,
|
||||||
track.score.toDouble(),
|
track.score,
|
||||||
track.remote_url,
|
track.remote_url,
|
||||||
track.start_date,
|
track.start_date,
|
||||||
track.finish_date,
|
track.finish_date,
|
||||||
@ -486,7 +487,7 @@ class BackupManager(
|
|||||||
* @return id of [Manga], null if not found
|
* @return id of [Manga], null if not found
|
||||||
*/
|
*/
|
||||||
private suspend fun insertManga(manga: Manga): Long {
|
private suspend fun insertManga(manga: Manga): Long {
|
||||||
return handler.awaitOne(true) {
|
return handler.awaitOneExecutable(true) {
|
||||||
mangasQueries.insert(
|
mangasQueries.insert(
|
||||||
source = manga.source,
|
source = manga.source,
|
||||||
url = manga.url,
|
url = manga.url,
|
||||||
@ -524,17 +525,17 @@ class BackupManager(
|
|||||||
title = manga.title,
|
title = manga.title,
|
||||||
status = manga.status,
|
status = manga.status,
|
||||||
thumbnailUrl = manga.thumbnailUrl,
|
thumbnailUrl = manga.thumbnailUrl,
|
||||||
favorite = manga.favorite.toLong(),
|
favorite = manga.favorite,
|
||||||
lastUpdate = manga.lastUpdate,
|
lastUpdate = manga.lastUpdate,
|
||||||
nextUpdate = null,
|
nextUpdate = null,
|
||||||
calculateInterval = null,
|
calculateInterval = null,
|
||||||
initialized = manga.initialized.toLong(),
|
initialized = manga.initialized,
|
||||||
viewer = manga.viewerFlags,
|
viewer = manga.viewerFlags,
|
||||||
chapterFlags = manga.chapterFlags,
|
chapterFlags = manga.chapterFlags,
|
||||||
coverLastModified = manga.coverLastModified,
|
coverLastModified = manga.coverLastModified,
|
||||||
dateAdded = manga.dateAdded,
|
dateAdded = manga.dateAdded,
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
updateStrategy = manga.updateStrategy.let(updateStrategyAdapter::encode),
|
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return manga.id
|
return manga.id
|
||||||
@ -574,8 +575,8 @@ class BackupManager(
|
|||||||
url = null,
|
url = null,
|
||||||
name = null,
|
name = null,
|
||||||
scanlator = null,
|
scanlator = null,
|
||||||
read = chapter.read.toLong(),
|
read = chapter.read,
|
||||||
bookmark = chapter.bookmark.toLong(),
|
bookmark = chapter.bookmark,
|
||||||
lastPageRead = chapter.lastPageRead,
|
lastPageRead = chapter.lastPageRead,
|
||||||
chapterNumber = null,
|
chapterNumber = null,
|
||||||
sourceOrder = null,
|
sourceOrder = null,
|
||||||
|
@ -36,7 +36,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
|
|
||||||
return try {
|
return try {
|
||||||
val restorer = BackupRestorer(context, notifier)
|
val restorer = BackupRestorer(context, notifier)
|
||||||
restorer.restoreBackup(uri, sync)
|
restorer.syncFromBackup(uri, sync)
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) {
|
if (e is CancellationException) {
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
|
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
|
||||||
@ -12,10 +13,15 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@ -23,6 +29,12 @@ class BackupRestorer(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val notifier: BackupNotifier,
|
private val notifier: BackupNotifier,
|
||||||
) {
|
) {
|
||||||
|
private val updateManga: UpdateManga = Injekt.get()
|
||||||
|
private val chapterRepository: ChapterRepository = Injekt.get()
|
||||||
|
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
||||||
|
|
||||||
|
private var now = ZonedDateTime.now()
|
||||||
|
private var currentFetchWindow = setFetchInterval.getWindow(now)
|
||||||
|
|
||||||
private var backupManager = BackupManager(context)
|
private var backupManager = BackupManager(context)
|
||||||
|
|
||||||
@ -36,7 +48,7 @@ class BackupRestorer(
|
|||||||
|
|
||||||
private val errors = mutableListOf<Pair<Date, String>>()
|
private val errors = mutableListOf<Pair<Date, String>>()
|
||||||
|
|
||||||
suspend fun restoreBackup(uri: Uri, sync: Boolean): Boolean {
|
suspend fun syncFromBackup(uri: Uri, sync: Boolean): Boolean {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
restoreProgress = 0
|
restoreProgress = 0
|
||||||
errors.clear()
|
errors.clear()
|
||||||
@ -51,7 +63,7 @@ class BackupRestorer(
|
|||||||
val logFile = writeErrorLog()
|
val logFile = writeErrorLog()
|
||||||
|
|
||||||
if (sync) {
|
if (sync) {
|
||||||
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name, contentTitle = context.getString(R.string.sync_complete))
|
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name, contentTitle = context.getString(R.string.library_sync_complete))
|
||||||
} else {
|
} else {
|
||||||
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
||||||
}
|
}
|
||||||
@ -90,6 +102,8 @@ class BackupRestorer(
|
|||||||
// Store source mapping for error messages
|
// Store source mapping for error messages
|
||||||
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
||||||
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||||
|
now = ZonedDateTime.now()
|
||||||
|
currentFetchWindow = setFetchInterval.getWindow(now)
|
||||||
|
|
||||||
return coroutineScope {
|
return coroutineScope {
|
||||||
// Restore individual manga
|
// Restore individual manga
|
||||||
@ -122,7 +136,7 @@ class BackupRestorer(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
|
val dbManga = backupManager.getMangaFromDatabase(manga.url, manga.source)
|
||||||
if (dbManga == null) {
|
val restoredManga = if (dbManga == null) {
|
||||||
// Manga not in database
|
// Manga not in database
|
||||||
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
|
restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories)
|
||||||
} else {
|
} else {
|
||||||
@ -132,6 +146,7 @@ class BackupRestorer(
|
|||||||
// Fetch rest of manga information
|
// Fetch rest of manga information
|
||||||
restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
|
restoreNewManga(updatedManga, chapters, categories, history, tracks, backupCategories)
|
||||||
}
|
}
|
||||||
|
updateManga.awaitUpdateFetchInterval(restoredManga, now, currentFetchWindow)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||||
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
||||||
@ -139,7 +154,7 @@ class BackupRestorer(
|
|||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
if (sync) {
|
if (sync) {
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.syncing_data))
|
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.syncing_library))
|
||||||
} else {
|
} else {
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.restoring_backup))
|
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.restoring_backup))
|
||||||
}
|
}
|
||||||
@ -159,10 +174,11 @@ class BackupRestorer(
|
|||||||
history: List<BackupHistory>,
|
history: List<BackupHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
) {
|
): Manga {
|
||||||
val fetchedManga = backupManager.restoreNewManga(manga)
|
val fetchedManga = backupManager.restoreNewManga(manga)
|
||||||
backupManager.restoreChapters(fetchedManga, chapters)
|
backupManager.restoreChapters(fetchedManga, chapters)
|
||||||
restoreExtras(fetchedManga, categories, history, tracks, backupCategories)
|
restoreExtras(fetchedManga, categories, history, tracks, backupCategories)
|
||||||
|
return fetchedManga
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun restoreNewManga(
|
private suspend fun restoreNewManga(
|
||||||
@ -172,9 +188,10 @@ class BackupRestorer(
|
|||||||
history: List<BackupHistory>,
|
history: List<BackupHistory>,
|
||||||
tracks: List<Track>,
|
tracks: List<Track>,
|
||||||
backupCategories: List<BackupCategory>,
|
backupCategories: List<BackupCategory>,
|
||||||
) {
|
): Manga {
|
||||||
backupManager.restoreChapters(backupManga, chapters)
|
backupManager.restoreChapters(backupManga, chapters)
|
||||||
restoreExtras(backupManga, categories, history, tracks, backupCategories)
|
restoreExtras(backupManga, categories, history, tracks, backupCategories)
|
||||||
|
return backupManga
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun restoreExtras(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
|
private suspend fun restoreExtras(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
|
||||||
|
@ -26,7 +26,7 @@ data class BackupChapter(
|
|||||||
return Chapter.create().copy(
|
return Chapter.create().copy(
|
||||||
url = this@BackupChapter.url,
|
url = this@BackupChapter.url,
|
||||||
name = this@BackupChapter.name,
|
name = this@BackupChapter.name,
|
||||||
chapterNumber = this@BackupChapter.chapterNumber,
|
chapterNumber = this@BackupChapter.chapterNumber.toDouble(),
|
||||||
scanlator = this@BackupChapter.scanlator,
|
scanlator = this@BackupChapter.scanlator,
|
||||||
read = this@BackupChapter.read,
|
read = this@BackupChapter.read,
|
||||||
bookmark = this@BackupChapter.bookmark,
|
bookmark = this@BackupChapter.bookmark,
|
||||||
@ -39,11 +39,11 @@ data class BackupChapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupChapterMapper = { _: Long, _: Long, url: String, name: String, scanlator: String?, read: Boolean, bookmark: Boolean, lastPageRead: Long, chapterNumber: Float, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long ->
|
val backupChapterMapper = { _: Long, _: Long, url: String, name: String, scanlator: String?, read: Boolean, bookmark: Boolean, lastPageRead: Long, chapterNumber: Double, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long ->
|
||||||
BackupChapter(
|
BackupChapter(
|
||||||
url = url,
|
url = url,
|
||||||
name = name,
|
name = name,
|
||||||
chapterNumber = chapterNumber,
|
chapterNumber = chapterNumber.toFloat(),
|
||||||
scanlator = scanlator,
|
scanlator = scanlator,
|
||||||
read = read,
|
read = read,
|
||||||
bookmark = bookmark,
|
bookmark = bookmark,
|
||||||
|
@ -44,7 +44,7 @@ data class BackupTracking(
|
|||||||
title = this@BackupTracking.title,
|
title = this@BackupTracking.title,
|
||||||
lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(),
|
lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(),
|
||||||
totalChapters = this@BackupTracking.totalChapters.toLong(),
|
totalChapters = this@BackupTracking.totalChapters.toLong(),
|
||||||
score = this@BackupTracking.score,
|
score = this@BackupTracking.score.toDouble(),
|
||||||
status = this@BackupTracking.status.toLong(),
|
status = this@BackupTracking.status.toLong(),
|
||||||
startDate = this@BackupTracking.startedReadingDate,
|
startDate = this@BackupTracking.startedReadingDate,
|
||||||
finishDate = this@BackupTracking.finishedReadingDate,
|
finishDate = this@BackupTracking.finishedReadingDate,
|
||||||
@ -54,7 +54,7 @@ data class BackupTracking(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val backupTrackMapper = {
|
val backupTrackMapper = {
|
||||||
_: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Float, remoteUrl: String, startDate: Long, finishDate: Long ->
|
_: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Double, remoteUrl: String, startDate: Long, finishDate: Long ->
|
||||||
BackupTracking(
|
BackupTracking(
|
||||||
syncId = syncId.toInt(),
|
syncId = syncId.toInt(),
|
||||||
mediaId = mediaId,
|
mediaId = mediaId,
|
||||||
@ -63,7 +63,7 @@ val backupTrackMapper = {
|
|||||||
title = title,
|
title = title,
|
||||||
lastChapterRead = lastChapterRead.toFloat(),
|
lastChapterRead = lastChapterRead.toFloat(),
|
||||||
totalChapters = totalChapters.toInt(),
|
totalChapters = totalChapters.toInt(),
|
||||||
score = score,
|
score = score.toFloat(),
|
||||||
status = status.toInt(),
|
status = status.toInt(),
|
||||||
startedReadingDate = startDate,
|
startedReadingDate = startDate,
|
||||||
finishedReadingDate = finishDate,
|
finishedReadingDate = finishDate,
|
||||||
|
@ -6,7 +6,6 @@ import com.jakewharton.disklrucache.DiskLruCache
|
|||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -36,7 +36,7 @@ fun Chapter.toDomainChapter(): DomainChapter? {
|
|||||||
url = url,
|
url = url,
|
||||||
name = name,
|
name = name,
|
||||||
dateUpload = date_upload,
|
dateUpload = date_upload,
|
||||||
chapterNumber = chapter_number,
|
chapterNumber = chapter_number.toDouble(),
|
||||||
scanlator = scanlator,
|
scanlator = scanlator,
|
||||||
lastModifiedAt = last_modified,
|
lastModifiedAt = last_modified,
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
|||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapter
|
import tachiyomi.domain.chapter.interactor.GetChapter
|
||||||
|
@ -66,8 +66,10 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY
|
|||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_UNREAD
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_UNREAD
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
||||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
|
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||||
import tachiyomi.domain.source.model.SourceNotInstalledException
|
import tachiyomi.domain.source.model.SourceNotInstalledException
|
||||||
@ -77,6 +79,7 @@ import tachiyomi.domain.track.interactor.InsertTrack
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -101,6 +104,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
private val getTracks: GetTracks = Injekt.get()
|
private val getTracks: GetTracks = Injekt.get()
|
||||||
private val insertTrack: InsertTrack = Injekt.get()
|
private val insertTrack: InsertTrack = Injekt.get()
|
||||||
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
|
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
|
||||||
|
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
||||||
|
|
||||||
private val notifier = LibraryUpdateNotifier(context)
|
private val notifier = LibraryUpdateNotifier(context)
|
||||||
|
|
||||||
@ -227,6 +231,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val hasDownloads = AtomicBoolean(false)
|
val hasDownloads = AtomicBoolean(false)
|
||||||
val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get()
|
val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get()
|
||||||
|
|
||||||
|
val fetchWindow by lazy { setFetchInterval.getWindow(ZonedDateTime.now()) }
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
mangaToUpdate.groupBy { it.manga.source }.values
|
mangaToUpdate.groupBy { it.manga.source }.values
|
||||||
.map { mangaInSource ->
|
.map { mangaInSource ->
|
||||||
@ -247,6 +253,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
manga,
|
manga,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
|
manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE ->
|
||||||
|
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update))
|
||||||
|
|
||||||
MANGA_NON_COMPLETED in restrictions && manga.status.toInt() == SManga.COMPLETED ->
|
MANGA_NON_COMPLETED in restrictions && manga.status.toInt() == SManga.COMPLETED ->
|
||||||
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_completed))
|
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_completed))
|
||||||
|
|
||||||
@ -256,12 +265,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
MANGA_NON_READ in restrictions && libraryManga.totalChapters > 0L && !libraryManga.hasStarted ->
|
MANGA_NON_READ in restrictions && libraryManga.totalChapters > 0L && !libraryManga.hasStarted ->
|
||||||
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started))
|
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started))
|
||||||
|
|
||||||
manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE ->
|
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && manga.nextUpdate !in fetchWindow.first.rangeTo(fetchWindow.second) ->
|
||||||
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update))
|
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_in_release_period))
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
try {
|
try {
|
||||||
val newChapters = updateManga(manga)
|
val newChapters = updateManga(manga, fetchWindow)
|
||||||
.sortedByDescending { it.sourceOrder }
|
.sortedByDescending { it.sourceOrder }
|
||||||
|
|
||||||
if (newChapters.isNotEmpty()) {
|
if (newChapters.isNotEmpty()) {
|
||||||
@ -317,6 +326,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (skippedUpdates.isNotEmpty()) {
|
if (skippedUpdates.isNotEmpty()) {
|
||||||
|
// TODO: surface skipped reasons to user
|
||||||
|
logcat {
|
||||||
|
skippedUpdates
|
||||||
|
.groupBy { it.second }
|
||||||
|
.map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
|
||||||
|
.joinToString()
|
||||||
|
}
|
||||||
notifier.showUpdateSkippedNotification(skippedUpdates.size)
|
notifier.showUpdateSkippedNotification(skippedUpdates.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +349,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
* @return a pair of the inserted and removed chapters.
|
* @return a pair of the inserted and removed chapters.
|
||||||
*/
|
*/
|
||||||
private suspend fun updateManga(manga: Manga): List<Chapter> {
|
private suspend fun updateManga(manga: Manga, fetchWindow: Pair<Long, Long>): List<Chapter> {
|
||||||
val source = sourceManager.getOrStub(manga.source)
|
val source = sourceManager.getOrStub(manga.source)
|
||||||
|
|
||||||
// Update manga metadata if needed
|
// Update manga metadata if needed
|
||||||
@ -348,7 +364,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
// to get latest data so it doesn't get overwritten later on
|
// to get latest data so it doesn't get overwritten later on
|
||||||
val dbManga = getManga.await(manga.id)?.takeIf { it.favorite } ?: return emptyList()
|
val dbManga = getManga.await(manga.id)?.takeIf { it.favorite } ?: return emptyList()
|
||||||
|
|
||||||
return syncChaptersWithSource.await(chapters, dbManga, source)
|
return syncChaptersWithSource.await(chapters, dbManga, source, false, fetchWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateCovers() {
|
private suspend fun updateCovers() {
|
||||||
|
@ -6,13 +6,13 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.drawable.BitmapDrawable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import coil.transform.CircleCropTransformation
|
import coil.transform.CircleCropTransformation
|
||||||
|
import eu.kanade.presentation.util.formatChapterNumber
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.data.download.Downloader
|
import eu.kanade.tachiyomi.data.download.Downloader
|
||||||
@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
|||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||||
|
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notify
|
import eu.kanade.tachiyomi.util.system.notify
|
||||||
import tachiyomi.core.Constants
|
import tachiyomi.core.Constants
|
||||||
@ -29,8 +30,6 @@ import tachiyomi.core.util.lang.launchUI
|
|||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
|
|
||||||
class LibraryUpdateNotifier(private val context: Context) {
|
class LibraryUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
@ -275,20 +274,14 @@ class LibraryUpdateNotifier(private val context: Context) {
|
|||||||
.size(NOTIF_ICON_SIZE)
|
.size(NOTIF_ICON_SIZE)
|
||||||
.build()
|
.build()
|
||||||
val drawable = context.imageLoader.execute(request).drawable
|
val drawable = context.imageLoader.execute(request).drawable
|
||||||
return (drawable as? BitmapDrawable)?.bitmap
|
return drawable?.getBitmapOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
||||||
val formatter = DecimalFormat(
|
|
||||||
"#.###",
|
|
||||||
DecimalFormatSymbols()
|
|
||||||
.apply { decimalSeparator = '.' },
|
|
||||||
)
|
|
||||||
|
|
||||||
val displayableChapterNumbers = chapters
|
val displayableChapterNumbers = chapters
|
||||||
.filter { it.isRecognizedNumber }
|
.filter { it.isRecognizedNumber }
|
||||||
.sortedBy { it.chapterNumber }
|
.sortedBy { it.chapterNumber }
|
||||||
.map { formatter.format(it.chapterNumber) }
|
.map { formatChapterNumber(it.chapterNumber) }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
return when (displayableChapterNumbers.size) {
|
return when (displayableChapterNumbers.size) {
|
||||||
|
@ -152,8 +152,8 @@ sealed class Image(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Location {
|
sealed interface Location {
|
||||||
data class Pictures private constructor(val relativePath: String) : Location() {
|
data class Pictures private constructor(val relativePath: String) : Location {
|
||||||
companion object {
|
companion object {
|
||||||
fun create(relativePath: String = ""): Pictures {
|
fun create(relativePath: String = ""): Pictures {
|
||||||
return Pictures(relativePath)
|
return Pictures(relativePath)
|
||||||
@ -161,7 +161,7 @@ sealed class Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Cache : Location()
|
data object Cache : Location
|
||||||
|
|
||||||
fun directory(context: Context): File {
|
fun directory(context: Context): File {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
@ -13,6 +13,7 @@ import eu.kanade.domain.track.service.TrackPreferences
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -20,10 +21,12 @@ import tachiyomi.core.util.lang.withIOContext
|
|||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.time.ZoneOffset
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
abstract class TrackService(val id: Long) {
|
abstract class TrackService(val id: Long) {
|
||||||
@ -62,7 +65,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
abstract fun getScoreList(): List<String>
|
abstract fun getScoreList(): List<String>
|
||||||
|
|
||||||
// TODO: Store all scores as 10 point in the future maybe?
|
// TODO: Store all scores as 10 point in the future maybe?
|
||||||
open fun get10PointScore(track: DomainTrack): Float {
|
open fun get10PointScore(track: DomainTrack): Double {
|
||||||
return track.score
|
return track.score
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +110,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
val hasReadChapters = allChapters.any { it.read }
|
val hasReadChapters = allChapters.any { it.read }
|
||||||
bind(item, hasReadChapters)
|
bind(item, hasReadChapters)
|
||||||
|
|
||||||
val track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
||||||
|
|
||||||
Injekt.get<InsertTrack>().await(track)
|
Injekt.get<InsertTrack>().await(track)
|
||||||
|
|
||||||
@ -120,10 +123,25 @@ abstract class TrackService(val id: Long) {
|
|||||||
?.chapterNumber?.toDouble() ?: -1.0
|
?.chapterNumber?.toDouble() ?: -1.0
|
||||||
|
|
||||||
if (latestLocalReadChapterNumber > track.lastChapterRead) {
|
if (latestLocalReadChapterNumber > track.lastChapterRead) {
|
||||||
val updatedTrack = track.copy(
|
track = track.copy(
|
||||||
lastChapterRead = latestLocalReadChapterNumber,
|
lastChapterRead = latestLocalReadChapterNumber,
|
||||||
)
|
)
|
||||||
setRemoteLastChapterRead(updatedTrack.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track.startDate <= 0) {
|
||||||
|
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
|
||||||
|
.sortedBy { it.readAt }
|
||||||
|
.firstOrNull()
|
||||||
|
?.readAt
|
||||||
|
|
||||||
|
firstReadChapterDate?.let {
|
||||||
|
val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
||||||
|
track = track.copy(
|
||||||
|
startDate = startDate,
|
||||||
|
)
|
||||||
|
setRemoteStartDate(track.toDbTrack(), startDate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +163,7 @@ abstract class TrackService(val id: Long) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
||||||
if (track.last_chapter_read == 0F && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
|
if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
|
||||||
track.status = getReadingStatus()
|
track.status = getReadingStatus()
|
||||||
}
|
}
|
||||||
track.last_chapter_read = chapterNumber.toFloat()
|
track.last_chapter_read = chapterNumber.toFloat()
|
||||||
|
@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -94,9 +93,9 @@ class Anilist(id: Long) : TrackService(id), DeletableTrackService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun get10PointScore(track: DomainTrack): Float {
|
override fun get10PointScore(track: DomainTrack): Double {
|
||||||
// Score is stored in 100 point format
|
// Score is stored in 100 point format
|
||||||
return track.score / 10f
|
return track.score / 10.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun indexToScore(index: Int): Float {
|
override fun indexToScore(index: Int): Float {
|
||||||
|
@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.network.GET
|
|||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.bangumi
|
package eu.kanade.tachiyomi.data.track.bangumi
|
||||||
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
@ -67,7 +66,7 @@ class BangumiInterceptor(val bangumi: Bangumi) : Interceptor {
|
|||||||
|
|
||||||
private fun addToken(token: String, oidFormBody: FormBody): FormBody {
|
private fun addToken(token: String, oidFormBody: FormBody): FormBody {
|
||||||
val newFormBody = FormBody.Builder()
|
val newFormBody = FormBody.Builder()
|
||||||
for (i in 0 until oidFormBody.size) {
|
for (i in 0..<oidFormBody.size) {
|
||||||
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
|
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
|
||||||
}
|
}
|
||||||
newFormBody.add("access_token", token)
|
newFormBody.add("access_token", token)
|
||||||
|
@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.kitsu
|
package eu.kanade.tachiyomi.data.track.kitsu
|
||||||
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
import eu.kanade.tachiyomi.data.track.DeletableTrackService
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikimori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.extension.model.LoadResult
|
|||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -19,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.preference.plusAssign
|
||||||
import tachiyomi.core.util.lang.launchNow
|
import tachiyomi.core.util.lang.launchNow
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
@ -238,10 +238,10 @@ class ExtensionManager(
|
|||||||
/**
|
/**
|
||||||
* Uninstalls the extension that matches the given package name.
|
* Uninstalls the extension that matches the given package name.
|
||||||
*
|
*
|
||||||
* @param pkgName The package name of the application to uninstall.
|
* @param extension The extension to uninstall.
|
||||||
*/
|
*/
|
||||||
fun uninstallExtension(pkgName: String) {
|
fun uninstallExtension(extension: Extension) {
|
||||||
installer.uninstallApk(pkgName)
|
installer.uninstallApk(extension.pkgName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -260,18 +260,13 @@ class ExtensionManager(
|
|||||||
val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
|
val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature }
|
||||||
_untrustedExtensionsFlow.value -= nowTrustedExtensions
|
_untrustedExtensionsFlow.value -= nowTrustedExtensions
|
||||||
|
|
||||||
val ctx = context
|
|
||||||
launchNow {
|
launchNow {
|
||||||
nowTrustedExtensions
|
nowTrustedExtensions
|
||||||
.map { extension ->
|
.map { extension ->
|
||||||
async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
|
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
|
||||||
}
|
|
||||||
.map { it.await() }
|
|
||||||
.forEach { result ->
|
|
||||||
if (result is LoadResult.Success) {
|
|
||||||
registerNewExtension(result.extension)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.filterIsInstance<LoadResult.Success>()
|
||||||
|
.forEach { registerNewExtension(it.extension) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ internal class ExtensionGithubApi {
|
|||||||
hasChangelog = it.hasChangelog == 1,
|
hasChangelog = it.hasChangelog == 1,
|
||||||
sources = it.sources?.map(extensionSourceMapper).orEmpty(),
|
sources = it.sources?.map(extensionSourceMapper).orEmpty(),
|
||||||
apkName = it.apk,
|
apkName = it.apk,
|
||||||
iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}",
|
iconUrl = "${getUrlPrefix()}icon/${it.pkg}.png",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.extension.model
|
package eu.kanade.tachiyomi.extension.model
|
||||||
|
|
||||||
sealed class LoadResult {
|
sealed interface LoadResult {
|
||||||
class Success(val extension: Extension.Installed) : LoadResult()
|
data class Success(val extension: Extension.Installed) : LoadResult
|
||||||
class Untrusted(val extension: Extension.Untrusted) : LoadResult()
|
data class Untrusted(val extension: Extension.Untrusted) : LoadResult
|
||||||
object Error : LoadResult()
|
data object Error : LoadResult
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,6 @@ fun SChapter.copyFrom(other: Chapters) {
|
|||||||
name = other.name
|
name = other.name
|
||||||
url = other.url
|
url = other.url
|
||||||
date_upload = other.date_upload
|
date_upload = other.date_upload
|
||||||
chapter_number = other.chapter_number
|
chapter_number = other.chapter_number.toFloat()
|
||||||
scanlator = other.scanlator
|
scanlator = other.scanlator
|
||||||
}
|
}
|
||||||
|
@ -54,20 +54,20 @@ class ExtensionFilterScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ExtensionFilterEvent {
|
sealed interface ExtensionFilterEvent {
|
||||||
object FailedFetchingLanguages : ExtensionFilterEvent()
|
data object FailedFetchingLanguages : ExtensionFilterEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ExtensionFilterState {
|
sealed interface ExtensionFilterState {
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
object Loading : ExtensionFilterState()
|
data object Loading : ExtensionFilterState
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Success(
|
data class Success(
|
||||||
val languages: List<String>,
|
val languages: List<String>,
|
||||||
val enabledLanguages: Set<String> = emptySet(),
|
val enabledLanguages: Set<String> = emptySet(),
|
||||||
) : ExtensionFilterState() {
|
) : ExtensionFilterState {
|
||||||
|
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
get() = languages.isEmpty()
|
get() = languages.isEmpty()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user