mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-01 21:47:50 +02:00
Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
3222247969 | |||
dd6c9ce2fe | |||
7446b28ff1 | |||
38c6702b8f | |||
afcf4b2988 | |||
ebb96a6ff4 | |||
8b0affe9bd | |||
1a25cea0d6 | |||
2ecbcdf4bd | |||
642b392d44 | |||
8dce7b3e9e | |||
33e90d6449 | |||
50b17d5d34 | |||
7818885406 | |||
26af7ccc77 | |||
5d1f79012e | |||
cac80daa71 | |||
fc184f1cfa | |||
725fcbba0e | |||
bdeb209d43 | |||
a078f1ab1b | |||
86c3d8c064 | |||
156191af44 | |||
57bba9e5ab | |||
dd1923fe88 | |||
df773ee15c | |||
f5451a6881 | |||
fcec1581b7 | |||
11cc789e36 | |||
16f9fb2f40 | |||
6bfaa85e84 | |||
8f43fb9530 | |||
04d2a3399b | |||
054bf8ec5d | |||
8417f5a63c | |||
26b46cace0 | |||
0849111247 | |||
f9c25b350e | |||
5b12c144da | |||
f38130d086 | |||
4b60138d41 | |||
fde7bfa3d1 | |||
69635ee66a | |||
224f29077d | |||
e1ab1fdb65 | |||
3e86cb094b | |||
9fbd3fe33f | |||
073e9f94ff | |||
64c0d9506d | |||
f638092ab9 | |||
d0c4463ab3 | |||
ad107860b9 | |||
5efb31bd71 | |||
e4a2f35907 | |||
e49781de7a | |||
37cb4ec0c2 | |||
401134fa8e | |||
87391832ba | |||
e36d31bf0f | |||
37b7efbc87 | |||
6e4a30e593 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -3,7 +3,7 @@
|
|||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated:
|
- I have updated:
|
||||||
- To the latest version of the app (stable is v0.14.0)
|
- To the latest version of the app (stable is v0.14.2)
|
||||||
- All extensions
|
- All extensions
|
||||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
label: Tachiyomi version
|
label: Tachiyomi version
|
||||||
description: You can find your Tachiyomi version in **More → About**.
|
description: You can find your Tachiyomi version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.14.0"
|
Example: "0.14.2"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.0](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.14.2](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -33,7 +33,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.0](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.14.2](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
@ -27,8 +27,8 @@ android {
|
|||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "eu.kanade.tachiyomi"
|
||||||
minSdk = AndroidConfig.minSdk
|
minSdk = AndroidConfig.minSdk
|
||||||
targetSdk = AndroidConfig.targetSdk
|
targetSdk = AndroidConfig.targetSdk
|
||||||
versionCode = 89
|
versionCode = 91
|
||||||
versionName = "0.14.0"
|
versionName = "0.14.2"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -113,7 +113,6 @@ android {
|
|||||||
"META-INF/README.md",
|
"META-INF/README.md",
|
||||||
"META-INF/NOTICE",
|
"META-INF/NOTICE",
|
||||||
"META-INF/*.kotlin_module",
|
"META-INF/*.kotlin_module",
|
||||||
"META-INF/*.version",
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,8 +175,6 @@ dependencies {
|
|||||||
implementation(compose.accompanist.webview)
|
implementation(compose.accompanist.webview)
|
||||||
implementation(compose.accompanist.swiperefresh)
|
implementation(compose.accompanist.swiperefresh)
|
||||||
implementation(compose.accompanist.flowlayout)
|
implementation(compose.accompanist.flowlayout)
|
||||||
implementation(compose.accompanist.pager.core)
|
|
||||||
implementation(compose.accompanist.pager.indicators)
|
|
||||||
implementation(compose.accompanist.permissions)
|
implementation(compose.accompanist.permissions)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
@ -190,6 +187,8 @@ dependencies {
|
|||||||
implementation(libs.sqldelight.android.paging)
|
implementation(libs.sqldelight.android.paging)
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
|
|
||||||
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
implementation(kotlinx.bundles.coroutines)
|
implementation(kotlinx.bundles.coroutines)
|
||||||
|
|
||||||
// AndroidX libraries
|
// AndroidX libraries
|
||||||
@ -298,6 +297,11 @@ androidComponents {
|
|||||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
|
// Only excluding in standard flavor because this breaks
|
||||||
|
// Layout Inspector's Compose tree
|
||||||
|
it.packaging.resources.excludes.add("META-INF/*.version")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
@ -329,6 +333,19 @@ tasks {
|
|||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
|
project.buildDir.absolutePath + "/compose_metrics"
|
||||||
|
)
|
||||||
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-P",
|
||||||
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
|
project.buildDir.absolutePath + "/compose_metrics"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preBuild {
|
preBuild {
|
||||||
|
@ -34,7 +34,7 @@ import eu.kanade.domain.extension.interactor.GetExtensionSources
|
|||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.domain.history.interactor.DeleteAllHistory
|
import eu.kanade.domain.history.interactor.DeleteAllHistory
|
||||||
import eu.kanade.domain.history.interactor.GetHistory
|
import eu.kanade.domain.history.interactor.GetHistory
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapter
|
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
||||||
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||||
@ -63,6 +63,7 @@ import eu.kanade.domain.source.repository.SourceDataRepository
|
|||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.domain.track.interactor.DeleteTrack
|
import eu.kanade.domain.track.interactor.DeleteTrack
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.GetTracks
|
||||||
|
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
import eu.kanade.domain.track.interactor.InsertTrack
|
||||||
import eu.kanade.domain.track.repository.TrackRepository
|
import eu.kanade.domain.track.repository.TrackRepository
|
||||||
import eu.kanade.domain.updates.interactor.GetUpdates
|
import eu.kanade.domain.updates.interactor.GetUpdates
|
||||||
@ -93,7 +94,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetLibraryManga(get()) }
|
addFactory { GetLibraryManga(get()) }
|
||||||
addFactory { GetMangaWithChapters(get(), get()) }
|
addFactory { GetMangaWithChapters(get(), get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapter(get(), get(), get(), get()) }
|
addFactory { GetNextUnreadChapters(get(), get(), get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||||
@ -104,6 +105,7 @@ class DomainModule : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||||
addFactory { DeleteTrack(get()) }
|
addFactory { DeleteTrack(get()) }
|
||||||
|
addFactory { GetTracksPerManga(get()) }
|
||||||
addFactory { GetTracks(get()) }
|
addFactory { GetTracks(get()) }
|
||||||
addFactory { InsertTrack(get()) }
|
addFactory { InsertTrack(get()) }
|
||||||
|
|
||||||
|
@ -27,7 +27,12 @@ class SetReadStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
suspend fun await(read: Boolean, vararg chapters: Chapter): Result = withNonCancellableContext {
|
||||||
val chaptersToUpdate = chapters.filterNot { it.read == read }
|
val chaptersToUpdate = chapters.filter {
|
||||||
|
when (read) {
|
||||||
|
true -> !it.read
|
||||||
|
false -> it.read || it.lastPageRead > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
if (chaptersToUpdate.isEmpty()) {
|
if (chaptersToUpdate.isEmpty()) {
|
||||||
return@withNonCancellableContext Result.NoChapters
|
return@withNonCancellableContext Result.NoChapters
|
||||||
}
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package eu.kanade.domain.history.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
|
||||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
|
||||||
import eu.kanade.domain.history.repository.HistoryRepository
|
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
|
||||||
|
|
||||||
class GetNextChapter(
|
|
||||||
private val getChapter: GetChapter,
|
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
|
||||||
private val getManga: GetManga,
|
|
||||||
private val historyRepository: HistoryRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun await(): Chapter? {
|
|
||||||
val history = historyRepository.getLastHistory() ?: return null
|
|
||||||
return await(history.mangaId, history.chapterId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
|
|
||||||
val chapter = getChapter.await(chapterId)!!
|
|
||||||
val manga = getManga.await(mangaId)!!
|
|
||||||
|
|
||||||
if (!chapter.read) return chapter
|
|
||||||
|
|
||||||
val chapters = getChapterByMangaId.await(mangaId)
|
|
||||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
|
||||||
|
|
||||||
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
|
|
||||||
return when (manga.sorting) {
|
|
||||||
Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
|
|
||||||
Manga.CHAPTER_SORTING_NUMBER -> {
|
|
||||||
val chapterNumber = chapter.chapterNumber
|
|
||||||
|
|
||||||
((currChapterIndex + 1) until chapters.size)
|
|
||||||
.map { chapters[it] }
|
|
||||||
.firstOrNull {
|
|
||||||
it.chapterNumber > chapterNumber && it.chapterNumber <= chapterNumber + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Manga.CHAPTER_SORTING_UPLOAD_DATE -> {
|
|
||||||
chapters.drop(currChapterIndex + 1)
|
|
||||||
.firstOrNull { it.dateUpload >= chapter.dateUpload }
|
|
||||||
}
|
|
||||||
else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package eu.kanade.domain.history.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||||
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
|
import eu.kanade.domain.history.repository.HistoryRepository
|
||||||
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class GetNextUnreadChapters(
|
||||||
|
private val getChapterByMangaId: GetChapterByMangaId,
|
||||||
|
private val getManga: GetManga,
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(): Chapter? {
|
||||||
|
val history = historyRepository.getLastHistory() ?: return null
|
||||||
|
return await(history.mangaId, history.chapterId).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long): List<Chapter> {
|
||||||
|
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||||
|
return getChapterByMangaId.await(mangaId)
|
||||||
|
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||||
|
.filterNot { it.read }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, fromChapterId: Long): List<Chapter> {
|
||||||
|
val unreadChapters = await(mangaId)
|
||||||
|
val currChapterIndex = unreadChapters.indexOfFirst { it.id == fromChapterId }
|
||||||
|
return unreadChapters.subList(max(0, currChapterIndex), unreadChapters.size)
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,11 @@ class NetworkToLocalManga(
|
|||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(manga: Manga, sourceId: Long): Manga {
|
suspend fun await(manga: Manga): Manga {
|
||||||
val localManga = getManga(manga.url, sourceId)
|
val localManga = getManga(manga.url, manga.source)
|
||||||
return when {
|
return when {
|
||||||
localManga == null -> {
|
localManga == null -> {
|
||||||
val id = insertManga(manga.copy(source = sourceId))
|
val id = insertManga(manga)
|
||||||
manga.copy(id = id!!)
|
manga.copy(id = id!!)
|
||||||
}
|
}
|
||||||
!localManga.favorite -> {
|
!localManga.favorite -> {
|
||||||
|
@ -232,7 +232,7 @@ fun Manga.toMangaUpdate(): MangaUpdate {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SManga.toDomainManga(): Manga {
|
fun SManga.toDomainManga(sourceId: Long): Manga {
|
||||||
return Manga.create().copy(
|
return Manga.create().copy(
|
||||||
url = url,
|
url = url,
|
||||||
title = title,
|
title = title,
|
||||||
@ -244,6 +244,7 @@ fun SManga.toDomainManga(): Manga {
|
|||||||
thumbnailUrl = thumbnail_url,
|
thumbnailUrl = thumbnail_url,
|
||||||
updateStrategy = update_strategy,
|
updateStrategy = update_strategy,
|
||||||
initialized = initialized,
|
initialized = initialized,
|
||||||
|
source = sourceId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,6 @@ class GetTracks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribe(): Flow<List<Track>> {
|
|
||||||
return trackRepository.getTracksAsFlow()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun subscribe(mangaId: Long): Flow<List<Track>> {
|
fun subscribe(mangaId: Long): Flow<List<Track>> {
|
||||||
return trackRepository.getTracksByMangaIdAsFlow(mangaId)
|
return trackRepository.getTracksByMangaIdAsFlow(mangaId)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.repository.TrackRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class GetTracksPerManga(
|
||||||
|
private val trackRepository: TrackRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun subscribe(): Flow<Map<Long, List<Long>>> {
|
||||||
|
return trackRepository.getTracksAsFlow().map { tracks ->
|
||||||
|
tracks
|
||||||
|
.groupBy { it.mangaId }
|
||||||
|
.mapValues { entry ->
|
||||||
|
entry.value.map { it.syncId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,12 +13,12 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.HelpOutline
|
|
||||||
import androidx.compose.material.icons.filled.Public
|
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
|
||||||
import androidx.compose.material.icons.outlined.Favorite
|
import androidx.compose.material.icons.outlined.Favorite
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.NewReleases
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.FilterChipDefaults
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -89,7 +89,7 @@ fun BrowseSourceScreen(
|
|||||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
BrowseSourceToolbar(
|
BrowseSourceToolbar(
|
||||||
state = presenter,
|
state = presenter,
|
||||||
source = presenter.source!!,
|
source = presenter.source,
|
||||||
displayMode = presenter.displayMode,
|
displayMode = presenter.displayMode,
|
||||||
onDisplayModeChange = { presenter.displayMode = it },
|
onDisplayModeChange = { presenter.displayMode = it },
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
@ -253,7 +253,7 @@ fun BrowseSourceContent(
|
|||||||
listOf(
|
listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringResId = R.string.local_source_help_guide,
|
||||||
icon = Icons.Default.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onLocalSourceHelpClick,
|
onClick = onLocalSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -261,17 +261,17 @@ fun BrowseSourceContent(
|
|||||||
listOf(
|
listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringResId = R.string.action_retry,
|
||||||
icon = Icons.Default.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
onClick = mangaList::refresh,
|
onClick = mangaList::refresh,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_open_in_web_view,
|
stringResId = R.string.action_open_in_web_view,
|
||||||
icon = Icons.Default.Public,
|
icon = Icons.Outlined.Public,
|
||||||
onClick = onWebViewClick,
|
onClick = onWebViewClick,
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringResId = R.string.label_help,
|
||||||
icon = Icons.Default.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -4,12 +4,9 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Row
|
||||||
@ -55,9 +52,11 @@ import eu.kanade.presentation.components.DIVIDER_ALPHA
|
|||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
@ -195,23 +194,6 @@ private fun ExtensionDetails(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun WarningBanner(@StringRes textRes: Int) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(MaterialTheme.colorScheme.error)
|
|
||||||
.padding(16.dp),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(textRes),
|
|
||||||
color = MaterialTheme.colorScheme.onError,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DetailsHeader(
|
private fun DetailsHeader(
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
@ -380,15 +362,14 @@ private fun SourceSwitchPreference(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
PreferenceRow(
|
TextPreferenceWidget(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = if (source.labelAsName) {
|
title = if (source.labelAsName) {
|
||||||
source.source.toString()
|
source.source.toString()
|
||||||
} else {
|
} else {
|
||||||
LocaleHelper.getSourceDisplayName(source.source.lang, context)
|
LocaleHelper.getSourceDisplayName(source.source.lang, context)
|
||||||
},
|
},
|
||||||
onClick = { onClickSource(source.source.id) },
|
widget = {
|
||||||
action = {
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@ -402,9 +383,14 @@ private fun SourceSwitchPreference(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Switch(checked = source.enabled, onCheckedChange = null)
|
Switch(
|
||||||
|
checked = source.enabled,
|
||||||
|
onCheckedChange = null,
|
||||||
|
modifier = Modifier.padding(start = TrailingWidgetBuffer),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onPreferenceClick = { onClickSource(source.source.id) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ 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.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -11,10 +10,10 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LazyColumn
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
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.ExtensionFilterPresenter
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterPresenter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
@ -42,8 +41,7 @@ fun ExtensionFilterScreen(
|
|||||||
textResource = R.string.empty_screen,
|
textResource = R.string.empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
)
|
)
|
||||||
else -> {
|
else -> ExtensionFilterContent(
|
||||||
SourceFilterContent(
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
state = presenter,
|
state = presenter,
|
||||||
onClickLang = {
|
onClickLang = {
|
||||||
@ -52,7 +50,6 @@ fun ExtensionFilterScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
presenter.events.collectLatest {
|
presenter.events.collectLatest {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -65,40 +62,24 @@ fun ExtensionFilterScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceFilterContent(
|
private fun ExtensionFilterContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: ExtensionFilterState,
|
state: ExtensionFilterState,
|
||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = state.items,
|
items = state.items,
|
||||||
) { model ->
|
) { model ->
|
||||||
ExtensionFilterItem(
|
val lang = model.lang
|
||||||
|
SwitchPreferenceWidget(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
lang = model.lang,
|
|
||||||
enabled = model.enabled,
|
|
||||||
onClickItem = onClickLang,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ExtensionFilterItem(
|
|
||||||
modifier: Modifier,
|
|
||||||
lang: String,
|
|
||||||
enabled: Boolean,
|
|
||||||
onClickItem: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
PreferenceRow(
|
|
||||||
modifier = modifier,
|
|
||||||
title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
|
title = LocaleHelper.getSourceDisplayName(lang, LocalContext.current),
|
||||||
action = {
|
checked = model.enabled,
|
||||||
Switch(checked = enabled, onCheckedChange = null)
|
onCheckedChanged = { onClickLang(lang) },
|
||||||
},
|
|
||||||
onClick = { onClickItem(lang) },
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
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.filled.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@ -368,7 +368,7 @@ private fun ExtensionItemActions(
|
|||||||
} else {
|
} else {
|
||||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_cancel),
|
contentDescription = stringResource(R.string.action_cancel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
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.MigrateMangaPresenter
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaPresenter
|
||||||
@ -72,7 +72,7 @@ private fun MigrateMangaContent(
|
|||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(state.items) { manga ->
|
items(state.items) { manga ->
|
||||||
|
@ -6,12 +6,10 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceSearchToolbar
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
import eu.kanade.tachiyomi.ui.more.MoreController
|
||||||
@ -38,13 +36,11 @@ fun SourceSearchScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
BrowseSourceSearchToolbar(
|
SearchToolbar(
|
||||||
searchQuery = presenter.searchQuery ?: "",
|
searchQuery = presenter.searchQuery ?: "",
|
||||||
onSearchQueryChanged = { presenter.searchQuery = it },
|
onChangeSearchQuery = { presenter.searchQuery = it },
|
||||||
placeholderText = stringResource(R.string.action_search_hint),
|
onClickCloseSearch = navigateUp,
|
||||||
navigateUp = navigateUp,
|
onSearch = { presenter.search(it) },
|
||||||
onResetClick = { presenter.searchQuery = "" },
|
|
||||||
onSearchClick = { presenter.search(it) },
|
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -14,10 +13,10 @@ import eu.kanade.domain.source.model.Source
|
|||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
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.FilterUiModel
|
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterPresenter
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterPresenter
|
||||||
@ -76,7 +75,7 @@ private fun SourcesFilterContent(
|
|||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
ScrollbarLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
@ -95,14 +94,12 @@ private fun SourcesFilterContent(
|
|||||||
},
|
},
|
||||||
) { model ->
|
) { model ->
|
||||||
when (model) {
|
when (model) {
|
||||||
is FilterUiModel.Header -> {
|
is FilterUiModel.Header -> SourcesFilterHeader(
|
||||||
SourcesFilterHeader(
|
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
language = model.language,
|
language = model.language,
|
||||||
enabled = model.enabled,
|
enabled = model.enabled,
|
||||||
onClickItem = onClickLang,
|
onClickItem = onClickLang,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
is FilterUiModel.Item -> SourcesFilterItem(
|
is FilterUiModel.Item -> SourcesFilterItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
source = model.source,
|
source = model.source,
|
||||||
@ -121,13 +118,11 @@ private fun SourcesFilterHeader(
|
|||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClickItem: (String) -> Unit,
|
onClickItem: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
PreferenceRow(
|
SwitchPreferenceWidget(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = LocaleHelper.getSourceDisplayName(language, LocalContext.current),
|
title = LocaleHelper.getSourceDisplayName(language, LocalContext.current),
|
||||||
action = {
|
checked = enabled,
|
||||||
Switch(checked = enabled, onCheckedChange = null)
|
onCheckedChanged = { onClickItem(language) },
|
||||||
},
|
|
||||||
onClick = { onClickItem(language) },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.Image
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Dangerous
|
import androidx.compose.material.icons.filled.Dangerous
|
||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
@ -48,7 +47,7 @@ fun SourceIcon(
|
|||||||
when {
|
when {
|
||||||
source.isStub && icon == null -> {
|
source.isStub && icon == null -> {
|
||||||
Image(
|
Image(
|
||||||
imageVector = Icons.Default.Warning,
|
imageVector = Icons.Filled.Warning,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||||
modifier = modifier.then(defaultModifier),
|
modifier = modifier.then(defaultModifier),
|
||||||
@ -85,7 +84,7 @@ fun ExtensionIcon(
|
|||||||
placeholder = ColorPainter(Color(0x1F888888)),
|
placeholder = ColorPainter(Color(0x1F888888)),
|
||||||
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(4.dp)),
|
.clip(MaterialTheme.shapes.extraSmall),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Extension.Installed -> {
|
is Extension.Installed -> {
|
||||||
@ -105,7 +104,7 @@ fun ExtensionIcon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Extension.Untrusted -> Image(
|
is Extension.Untrusted -> Image(
|
||||||
imageVector = Icons.Default.Dangerous,
|
imageVector = Icons.Filled.Dangerous,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||||
modifier = modifier.then(defaultModifier),
|
modifier = modifier.then(defaultModifier),
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
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.draw.drawWithContent
|
|
||||||
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 androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.domain.manga.model.MangaCover
|
||||||
import eu.kanade.presentation.components.Badge
|
import eu.kanade.presentation.components.Badge
|
||||||
import eu.kanade.presentation.components.MangaCover
|
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.library.components.MangaGridComfortableText
|
import eu.kanade.presentation.components.MangaComfortableGridItem
|
||||||
import eu.kanade.presentation.library.components.MangaGridCover
|
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@ -37,9 +31,9 @@ fun BrowseSourceComfortableGrid(
|
|||||||
) {
|
) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding,
|
contentPadding = contentPadding + PaddingValues(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||||
) {
|
) {
|
||||||
if (mangaList.loadState.prepend is LoadState.Loading) {
|
if (mangaList.loadState.prepend is LoadState.Loading) {
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
@ -71,36 +65,22 @@ fun BrowseSourceComfortableGridItem(
|
|||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = onClick,
|
onLongClick: () -> Unit = onClick,
|
||||||
) {
|
) {
|
||||||
val overlayColor = MaterialTheme.colorScheme.background.copy(alpha = 0.66f)
|
MangaComfortableGridItem(
|
||||||
Column(
|
title = manga.title,
|
||||||
modifier = Modifier
|
coverData = MangaCover(
|
||||||
.combinedClickable(
|
mangaId = manga.id,
|
||||||
onClick = onClick,
|
sourceId = manga.source,
|
||||||
onLongClick = onLongClick,
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
) {
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
MangaGridCover(
|
coverBadgeStart = {
|
||||||
cover = {
|
|
||||||
MangaCover.Book(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.drawWithContent {
|
|
||||||
drawContent()
|
|
||||||
if (manga.favorite) {
|
|
||||||
drawRect(overlayColor)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data = manga.thumbnailUrl,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badgesStart = {
|
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
Badge(text = stringResource(R.string.in_library))
|
Badge(text = stringResource(R.string.in_library))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onClick = onClick,
|
||||||
)
|
)
|
||||||
MangaGridComfortableText(
|
|
||||||
text = manga.title,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,22 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
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.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
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 androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.domain.manga.model.MangaCover
|
||||||
import eu.kanade.presentation.components.Badge
|
import eu.kanade.presentation.components.Badge
|
||||||
import eu.kanade.presentation.components.MangaCover
|
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.library.components.MangaGridCompactText
|
import eu.kanade.presentation.components.MangaCompactGridItem
|
||||||
import eu.kanade.presentation.library.components.MangaGridCover
|
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@ -44,12 +31,12 @@ fun BrowseSourceCompactGrid(
|
|||||||
) {
|
) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = columns,
|
columns = columns,
|
||||||
contentPadding = PaddingValues(8.dp, 4.dp) + contentPadding,
|
contentPadding = contentPadding + PaddingValues(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||||
) {
|
) {
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
|
||||||
if (mangaList.loadState.prepend is LoadState.Loading) {
|
if (mangaList.loadState.prepend is LoadState.Loading) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
BrowseSourceLoadingItem()
|
BrowseSourceLoadingItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,8 +51,8 @@ fun BrowseSourceCompactGrid(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
|
||||||
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
|
if (mangaList.loadState.refresh is LoadState.Loading || mangaList.loadState.append is LoadState.Loading) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
BrowseSourceLoadingItem()
|
BrowseSourceLoadingItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,57 +60,27 @@ fun BrowseSourceCompactGrid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceCompactGridItem(
|
private fun BrowseSourceCompactGridItem(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = onClick,
|
onLongClick: () -> Unit = onClick,
|
||||||
) {
|
) {
|
||||||
val overlayColor = MaterialTheme.colorScheme.background.copy(alpha = 0.66f)
|
MangaCompactGridItem(
|
||||||
MangaGridCover(
|
title = manga.title,
|
||||||
modifier = Modifier
|
coverData = MangaCover(
|
||||||
.combinedClickable(
|
mangaId = manga.id,
|
||||||
onClick = onClick,
|
sourceId = manga.source,
|
||||||
onLongClick = onLongClick,
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
cover = {
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
MangaCover.Book(
|
coverBadgeStart = {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.drawWithContent {
|
|
||||||
drawContent()
|
|
||||||
if (manga.favorite) {
|
|
||||||
drawRect(overlayColor)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data = eu.kanade.domain.manga.model.MangaCover(
|
|
||||||
manga.id,
|
|
||||||
manga.source,
|
|
||||||
manga.favorite,
|
|
||||||
manga.thumbnailUrl,
|
|
||||||
manga.coverLastModified,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badgesStart = {
|
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
Badge(text = stringResource(R.string.in_library))
|
Badge(text = stringResource(R.string.in_library))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = {
|
onLongClick = onLongClick,
|
||||||
Box(
|
onClick = onClick,
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp))
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
0f to Color.Transparent,
|
|
||||||
1f to Color(0xAA000000),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.fillMaxHeight(0.33f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.BottomCenter),
|
|
||||||
)
|
|
||||||
MangaGridCompactText(manga.title)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ fun RemoveMangaDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
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.draw.drawWithContent
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import androidx.paging.compose.items
|
import androidx.paging.compose.items
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
|
import eu.kanade.domain.manga.model.MangaCover
|
||||||
import eu.kanade.presentation.components.Badge
|
import eu.kanade.presentation.components.Badge
|
||||||
|
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.components.LazyColumn
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
import eu.kanade.presentation.components.MangaCover
|
import eu.kanade.presentation.components.MangaListItem
|
||||||
import eu.kanade.presentation.library.components.MangaListItem
|
|
||||||
import eu.kanade.presentation.library.components.MangaListItemContent
|
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.presentation.util.verticalPadding
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -32,7 +27,7 @@ fun BrowseSourceList(
|
|||||||
onMangaLongClick: (Manga) -> Unit,
|
onMangaLongClick: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
if (mangaList.loadState.prepend is LoadState.Loading) {
|
if (mangaList.loadState.prepend is LoadState.Loading) {
|
||||||
@ -64,31 +59,22 @@ fun BrowseSourceListItem(
|
|||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
onLongClick: () -> Unit = onClick,
|
onLongClick: () -> Unit = onClick,
|
||||||
) {
|
) {
|
||||||
val overlayColor = MaterialTheme.colorScheme.background.copy(alpha = 0.66f)
|
|
||||||
MangaListItem(
|
MangaListItem(
|
||||||
coverContent = {
|
title = manga.title,
|
||||||
MangaCover.Square(
|
coverData = MangaCover(
|
||||||
modifier = Modifier
|
mangaId = manga.id,
|
||||||
.padding(vertical = verticalPadding)
|
sourceId = manga.source,
|
||||||
.fillMaxHeight()
|
isMangaFavorite = manga.favorite,
|
||||||
.drawWithContent {
|
url = manga.thumbnailUrl,
|
||||||
drawContent()
|
lastModified = manga.coverLastModified,
|
||||||
if (manga.favorite) {
|
),
|
||||||
drawRect(overlayColor)
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
}
|
badge = {
|
||||||
},
|
|
||||||
data = manga.thumbnailUrl,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
badges = {
|
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
Badge(text = stringResource(R.string.in_library))
|
Badge(text = stringResource(R.string.in_library))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = {
|
onLongClick = onLongClick,
|
||||||
MangaListItemContent(text = manga.title)
|
onClick = onClick,
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ViewList
|
import androidx.compose.material.icons.filled.ViewList
|
||||||
import androidx.compose.material.icons.filled.ViewModule
|
import androidx.compose.material.icons.filled.ViewModule
|
||||||
import androidx.compose.material.icons.outlined.Help
|
import androidx.compose.material.icons.outlined.Help
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Search
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -15,14 +12,12 @@ 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.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||||
import eu.kanade.presentation.browse.BrowseSourceState
|
import eu.kanade.presentation.browse.BrowseSourceState
|
||||||
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.AppBarTitle
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.presentation.components.RadioMenuItem
|
import eu.kanade.presentation.components.RadioMenuItem
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
@ -33,7 +28,7 @@ import eu.kanade.tachiyomi.source.LocalSource
|
|||||||
@Composable
|
@Composable
|
||||||
fun BrowseSourceToolbar(
|
fun BrowseSourceToolbar(
|
||||||
state: BrowseSourceState,
|
state: BrowseSourceState,
|
||||||
source: CatalogueSource,
|
source: CatalogueSource?,
|
||||||
displayMode: LibraryDisplayMode,
|
displayMode: LibraryDisplayMode,
|
||||||
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
@ -42,59 +37,21 @@ fun BrowseSourceToolbar(
|
|||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
) {
|
) {
|
||||||
if (state.searchQuery == null) {
|
// Avoid capturing unstable source in actions lambda
|
||||||
BrowseSourceRegularToolbar(
|
val title = source?.name
|
||||||
title = if (state.isUserQuery) state.currentFilter.query else source.name,
|
val isLocalSource = source is LocalSource
|
||||||
isLocalSource = source is LocalSource,
|
|
||||||
displayMode = displayMode,
|
|
||||||
onDisplayModeChange = onDisplayModeChange,
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
onSearchClick = { state.searchQuery = if (state.isUserQuery) state.currentFilter.query else "" },
|
|
||||||
onWebViewClick = onWebViewClick,
|
|
||||||
onHelpClick = onHelpClick,
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val cancelSearch = { state.searchQuery = null }
|
|
||||||
BrowseSourceSearchToolbar(
|
|
||||||
searchQuery = state.searchQuery!!,
|
|
||||||
onSearchQueryChanged = { state.searchQuery = it },
|
|
||||||
placeholderText = stringResource(R.string.action_search_hint),
|
|
||||||
navigateUp = cancelSearch,
|
|
||||||
onResetClick = { state.searchQuery = "" },
|
|
||||||
onSearchClick = {
|
|
||||||
onSearch(it)
|
|
||||||
cancelSearch()
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
SearchToolbar(
|
||||||
fun BrowseSourceRegularToolbar(
|
|
||||||
title: String,
|
|
||||||
isLocalSource: Boolean,
|
|
||||||
displayMode: LibraryDisplayMode,
|
|
||||||
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
onSearchClick: () -> Unit,
|
|
||||||
onWebViewClick: () -> Unit,
|
|
||||||
onHelpClick: () -> Unit,
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior?,
|
|
||||||
) {
|
|
||||||
AppBar(
|
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
title = title,
|
titleContent = { AppBarTitle(title) },
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
onChangeSearchQuery = { state.searchQuery = it },
|
||||||
|
onSearch = onSearch,
|
||||||
|
onClickCloseSearch = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
var selectingDisplayMode by remember { mutableStateOf(false) }
|
var selectingDisplayMode by remember { mutableStateOf(false) }
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = listOf(
|
actions = listOf(
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(R.string.action_search),
|
|
||||||
icon = Icons.Outlined.Search,
|
|
||||||
onClick = onSearchClick,
|
|
||||||
),
|
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_display_mode),
|
title = stringResource(R.string.action_display_mode),
|
||||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
||||||
@ -123,18 +80,21 @@ fun BrowseSourceRegularToolbar(
|
|||||||
text = { Text(text = stringResource(R.string.action_display_comfortable_grid)) },
|
text = { Text(text = stringResource(R.string.action_display_comfortable_grid)) },
|
||||||
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
|
isChecked = displayMode == LibraryDisplayMode.ComfortableGrid,
|
||||||
) {
|
) {
|
||||||
|
selectingDisplayMode = false
|
||||||
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
|
onDisplayModeChange(LibraryDisplayMode.ComfortableGrid)
|
||||||
}
|
}
|
||||||
RadioMenuItem(
|
RadioMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_display_grid)) },
|
text = { Text(text = stringResource(R.string.action_display_grid)) },
|
||||||
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
|
isChecked = displayMode == LibraryDisplayMode.CompactGrid,
|
||||||
) {
|
) {
|
||||||
|
selectingDisplayMode = false
|
||||||
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
|
onDisplayModeChange(LibraryDisplayMode.CompactGrid)
|
||||||
}
|
}
|
||||||
RadioMenuItem(
|
RadioMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_display_list)) },
|
text = { Text(text = stringResource(R.string.action_display_list)) },
|
||||||
isChecked = displayMode == LibraryDisplayMode.List,
|
isChecked = displayMode == LibraryDisplayMode.List,
|
||||||
) {
|
) {
|
||||||
|
selectingDisplayMode = false
|
||||||
onDisplayModeChange(LibraryDisplayMode.List)
|
onDisplayModeChange(LibraryDisplayMode.List)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,34 +102,3 @@ fun BrowseSourceRegularToolbar(
|
|||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BrowseSourceSearchToolbar(
|
|
||||||
searchQuery: String,
|
|
||||||
onSearchQueryChanged: (String) -> Unit,
|
|
||||||
placeholderText: String?,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
onResetClick: () -> Unit,
|
|
||||||
onSearchClick: (String) -> Unit,
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior?,
|
|
||||||
) {
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
|
|
||||||
SearchToolbar(
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
onChangeSearchQuery = onSearchQueryChanged,
|
|
||||||
placeholderText = placeholderText,
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
onSearchClick(searchQuery)
|
|
||||||
focusManager.clearFocus()
|
|
||||||
keyboardController?.hide()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onClickCloseSearch = navigateUp,
|
|
||||||
onClickResetSearch = onResetClick,
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -6,8 +6,10 @@ 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.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
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.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
@ -22,7 +24,7 @@ fun CategoryCreateDialog(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onCreate: (String) -> Unit,
|
onCreate: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val (name, onNameChange) = remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@ -48,7 +50,7 @@ fun CategoryCreateDialog(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = onNameChange,
|
onValueChange = { name = it },
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(R.string.name))
|
Text(text = stringResource(R.string.name))
|
||||||
},
|
},
|
||||||
@ -70,7 +72,7 @@ fun CategoryRenameDialog(
|
|||||||
onRename: (String) -> Unit,
|
onRename: (String) -> Unit,
|
||||||
category: Category,
|
category: Category,
|
||||||
) {
|
) {
|
||||||
val (name, onNameChange) = remember { mutableStateOf(category.name) }
|
var name by remember { mutableStateOf(category.name) }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@ -96,7 +98,7 @@ fun CategoryRenameDialog(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = onNameChange,
|
onValueChange = { name = it },
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(R.string.name))
|
Text(text = stringResource(R.string.name))
|
||||||
},
|
},
|
||||||
@ -130,7 +132,7 @@ fun CategoryDeleteDialog(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package eu.kanade.presentation.category.components
|
package eu.kanade.presentation.category.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
import eu.kanade.presentation.util.isScrolledToEnd
|
import eu.kanade.presentation.util.isScrolledToEnd
|
||||||
@ -23,8 +21,6 @@ fun CategoryFloatingActionButton(
|
|||||||
text = { Text(text = stringResource(R.string.action_add)) },
|
text = { Text(text = stringResource(R.string.action_add)) },
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") },
|
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") },
|
||||||
onClick = onCreate,
|
onClick = onCreate,
|
||||||
modifier = Modifier
|
|
||||||
.navigationBarsPadding(),
|
|
||||||
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,10 @@ import androidx.compose.material3.Text
|
|||||||
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.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryListItem(
|
fun CategoryListItem(
|
||||||
@ -64,10 +66,10 @@ fun CategoryListItem(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
IconButton(onClick = onRename) {
|
IconButton(onClick = onRename) {
|
||||||
Icon(imageVector = Icons.Outlined.Edit, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.Edit, contentDescription = stringResource(R.string.action_rename_category))
|
||||||
}
|
}
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(imageVector = Icons.Outlined.Delete, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.statusBars
|
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.TextFieldDefaults
|
import androidx.compose.material.TextFieldDefaults
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material.icons.outlined.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@ -30,6 +26,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@ -38,8 +35,11 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
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
|
||||||
@ -55,7 +55,7 @@ fun AppBar(
|
|||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
// Up button
|
// Up button
|
||||||
navigateUp: (() -> Unit)? = null,
|
navigateUp: (() -> Unit)? = null,
|
||||||
navigationIcon: ImageVector = Icons.Default.ArrowBack,
|
navigationIcon: ImageVector = Icons.Outlined.ArrowBack,
|
||||||
// Menu
|
// Menu
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
// Action mode
|
// Action mode
|
||||||
@ -105,7 +105,7 @@ fun AppBar(
|
|||||||
titleContent: @Composable () -> Unit,
|
titleContent: @Composable () -> Unit,
|
||||||
// Up button
|
// Up button
|
||||||
navigateUp: (() -> Unit)? = null,
|
navigateUp: (() -> Unit)? = null,
|
||||||
navigationIcon: ImageVector = Icons.Default.ArrowBack,
|
navigationIcon: ImageVector = Icons.Outlined.ArrowBack,
|
||||||
// Menu
|
// Menu
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
// Action mode
|
// Action mode
|
||||||
@ -125,7 +125,7 @@ fun AppBar(
|
|||||||
if (isActionMode) {
|
if (isActionMode) {
|
||||||
IconButton(onClick = onCancelActionMode) {
|
IconButton(onClick = onCancelActionMode) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_cancel),
|
contentDescription = stringResource(R.string.action_cancel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -142,7 +142,6 @@ fun AppBar(
|
|||||||
},
|
},
|
||||||
title = titleContent,
|
title = titleContent,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
windowInsets = WindowInsets.statusBars,
|
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
elevation = if (isActionMode) 3.dp else 0.dp,
|
elevation = if (isActionMode) 3.dp else 0.dp,
|
||||||
@ -200,7 +199,7 @@ fun AppBarActions(
|
|||||||
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
|
||||||
if (overflowActions.isNotEmpty()) {
|
if (overflowActions.isNotEmpty()) {
|
||||||
IconButton(onClick = { showMenu = !showMenu }) {
|
IconButton(onClick = { showMenu = !showMenu }) {
|
||||||
Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.label_more))
|
Icon(Icons.Outlined.MoreVert, contentDescription = stringResource(R.string.abc_action_menu_overflow_description))
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
@ -220,15 +219,22 @@ fun AppBarActions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param searchEnabled Set to false if you don't want to show search action.
|
||||||
|
* @param searchQuery If null, use normal toolbar.
|
||||||
|
* @param placeholderText If null, [R.string.action_search_hint] is used.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchToolbar(
|
fun SearchToolbar(
|
||||||
searchQuery: String,
|
titleContent: @Composable () -> Unit = {},
|
||||||
onChangeSearchQuery: (String) -> Unit,
|
navigateUp: (() -> Unit)? = null,
|
||||||
|
searchEnabled: Boolean = true,
|
||||||
|
searchQuery: String?,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
placeholderText: String? = null,
|
placeholderText: String? = null,
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
onSearch: (String) -> Unit = {},
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) },
|
||||||
onClickCloseSearch: () -> Unit,
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
onClickResetSearch: () -> Unit,
|
|
||||||
incognitoMode: Boolean = false,
|
incognitoMode: Boolean = false,
|
||||||
downloadedOnlyMode: Boolean = false,
|
downloadedOnlyMode: Boolean = false,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
@ -236,9 +242,15 @@ fun SearchToolbar(
|
|||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
var searchClickCount by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
AppBar(
|
AppBar(
|
||||||
titleContent = {
|
titleContent = {
|
||||||
|
if (searchQuery == null) return@AppBar titleContent()
|
||||||
|
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = searchQuery,
|
value = searchQuery,
|
||||||
onValueChange = onChangeSearchQuery,
|
onValueChange = onChangeSearchQuery,
|
||||||
@ -250,8 +262,14 @@ fun SearchToolbar(
|
|||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
),
|
),
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
onSearch(searchQuery)
|
||||||
|
focusManager.clearFocus()
|
||||||
|
keyboardController?.hide()
|
||||||
|
},
|
||||||
|
),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
|
||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
@ -265,7 +283,7 @@ fun SearchToolbar(
|
|||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
placeholder = {
|
placeholder = {
|
||||||
if (!placeholderText.isNullOrEmpty()) {
|
(placeholderText ?: stringResource(R.string.action_search_hint)).let { placeholderText ->
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
text = placeholderText,
|
text = placeholderText,
|
||||||
@ -282,22 +300,41 @@ fun SearchToolbar(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = Icons.Outlined.ArrowBack,
|
navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch,
|
||||||
navigateUp = onClickCloseSearch,
|
|
||||||
actions = {
|
actions = {
|
||||||
AnimatedVisibility(visible = searchQuery.isNotEmpty()) {
|
key("search") {
|
||||||
IconButton(onClick = onClickResetSearch) {
|
val onClick = {
|
||||||
|
searchClickCount++
|
||||||
|
onChangeSearchQuery("")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!searchEnabled) {
|
||||||
|
// Don't show search action
|
||||||
|
} else if (searchQuery == null) {
|
||||||
|
IconButton(onClick) {
|
||||||
|
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
|
||||||
|
}
|
||||||
|
} else if (searchQuery.isNotEmpty()) {
|
||||||
|
IconButton(onClick) {
|
||||||
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
|
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key("actions") { actions() }
|
||||||
},
|
},
|
||||||
isActionMode = false,
|
isActionMode = false,
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
downloadedOnlyMode = downloadedOnlyMode,
|
||||||
incognitoMode = incognitoMode,
|
incognitoMode = incognitoMode,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
LaunchedEffect(focusRequester) {
|
LaunchedEffect(searchClickCount) {
|
||||||
|
if (searchQuery == null) return@LaunchedEffect
|
||||||
|
try {
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
// TextField is gone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -19,7 +18,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
@Composable
|
@Composable
|
||||||
fun BadgeGroup(
|
fun BadgeGroup(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
shape: Shape = RoundedCornerShape(4.dp),
|
shape: Shape = MaterialTheme.shapes.extraSmall,
|
||||||
content: @Composable RowScope.() -> Unit,
|
content: @Composable RowScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(modifier = modifier.clip(shape)) {
|
Row(modifier = modifier.clip(shape)) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -13,6 +14,23 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WarningBanner(
|
||||||
|
@StringRes textRes: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(textRes),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.error)
|
||||||
|
.padding(16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onError,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppStateBanners(
|
fun AppStateBanners(
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
|
@ -68,7 +68,7 @@ fun ChangeCategoryDialog(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -7,9 +7,9 @@ import androidx.compose.foundation.layout.Box
|
|||||||
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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowDownward
|
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.filled.ErrorOutline
|
import androidx.compose.material.icons.outlined.ArrowDownward
|
||||||
|
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@ -26,6 +26,8 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
@ -43,26 +45,41 @@ enum class ChapterDownloadAction {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterDownloadIndicator(
|
fun ChapterDownloadIndicator(
|
||||||
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
downloadStateProvider: () -> Download.State,
|
downloadStateProvider: () -> Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
when (val downloadState = downloadStateProvider()) {
|
when (val downloadState = downloadStateProvider()) {
|
||||||
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(modifier = modifier, onClick = onClick)
|
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(
|
||||||
|
enabled = enabled,
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
Download.State.QUEUE, Download.State.DOWNLOADING -> DownloadingIndicator(
|
Download.State.QUEUE, Download.State.DOWNLOADING -> DownloadingIndicator(
|
||||||
|
enabled = enabled,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
downloadState = downloadState,
|
downloadState = downloadState,
|
||||||
downloadProgressProvider = downloadProgressProvider,
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
)
|
)
|
||||||
Download.State.DOWNLOADED -> DownloadedIndicator(modifier = modifier, onClick = onClick)
|
Download.State.DOWNLOADED -> DownloadedIndicator(
|
||||||
Download.State.ERROR -> ErrorIndicator(modifier = modifier, onClick = onClick)
|
enabled = enabled,
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
Download.State.ERROR -> ErrorIndicator(
|
||||||
|
enabled = enabled,
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NotDownloadedIndicator(
|
private fun NotDownloadedIndicator(
|
||||||
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -70,6 +87,7 @@ private fun NotDownloadedIndicator(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
|
enabled = enabled,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
||||||
onClick = { onClick(ChapterDownloadAction.START) },
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
)
|
)
|
||||||
@ -78,7 +96,7 @@ private fun NotDownloadedIndicator(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_download_chapter_24dp),
|
painter = painterResource(id = R.drawable.ic_download_chapter_24dp),
|
||||||
contentDescription = null,
|
contentDescription = stringResource(R.string.manga_download),
|
||||||
modifier = Modifier.size(IndicatorSize),
|
modifier = Modifier.size(IndicatorSize),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@ -87,6 +105,7 @@ private fun NotDownloadedIndicator(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DownloadingIndicator(
|
private fun DownloadingIndicator(
|
||||||
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
downloadState: Download.State,
|
downloadState: Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
@ -97,6 +116,7 @@ private fun DownloadingIndicator(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
|
enabled = enabled,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
||||||
onClick = { isMenuExpanded = true },
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
@ -148,7 +168,7 @@ private fun DownloadingIndicator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowDownward,
|
imageVector = Icons.Outlined.ArrowDownward,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = ArrowModifier,
|
modifier = ArrowModifier,
|
||||||
tint = arrowColor,
|
tint = arrowColor,
|
||||||
@ -158,6 +178,7 @@ private fun DownloadingIndicator(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DownloadedIndicator(
|
private fun DownloadedIndicator(
|
||||||
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -166,13 +187,14 @@ private fun DownloadedIndicator(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
|
enabled = enabled,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.DELETE) },
|
onLongClick = { onClick(ChapterDownloadAction.DELETE) },
|
||||||
onClick = { isMenuExpanded = true },
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(IndicatorSize),
|
modifier = Modifier.size(IndicatorSize),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@ -191,6 +213,7 @@ private fun DownloadedIndicator(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ErrorIndicator(
|
private fun ErrorIndicator(
|
||||||
|
enabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -198,14 +221,15 @@ private fun ErrorIndicator(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
|
enabled = enabled,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.START) },
|
onLongClick = { onClick(ChapterDownloadAction.START) },
|
||||||
onClick = { onClick(ChapterDownloadAction.START) },
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ErrorOutline,
|
imageVector = Icons.Outlined.ErrorOutline,
|
||||||
contentDescription = null,
|
contentDescription = stringResource(R.string.chapter_error),
|
||||||
modifier = Modifier.size(IndicatorSize),
|
modifier = Modifier.size(IndicatorSize),
|
||||||
tint = MaterialTheme.colorScheme.error,
|
tint = MaterialTheme.colorScheme.error,
|
||||||
)
|
)
|
||||||
@ -213,11 +237,18 @@ private fun ErrorIndicator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Modifier.commonClickable(
|
private fun Modifier.commonClickable(
|
||||||
|
enabled: Boolean,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) = composed {
|
) = composed {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
this.combinedClickable(
|
this.combinedClickable(
|
||||||
onLongClick = onLongClick,
|
enabled = enabled,
|
||||||
|
onLongClick = {
|
||||||
|
onLongClick()
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
@ -0,0 +1,317 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shadow
|
||||||
|
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||||
|
import androidx.compose.ui.node.DrawModifierNode
|
||||||
|
import androidx.compose.ui.node.modifierElementOf
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import eu.kanade.presentation.util.selectedBackground
|
||||||
|
|
||||||
|
object CommonMangaItemDefaults {
|
||||||
|
val GridHorizontalSpacer = 4.dp
|
||||||
|
val GridVerticalSpacer = 4.dp
|
||||||
|
|
||||||
|
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val GridSelectedCoverAlpha = 0.76f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout of grid list item with title overlaying the cover.
|
||||||
|
* Accepts null [title] for a cover-only view.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MangaCompactGridItem(
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
title: String? = null,
|
||||||
|
coverData: eu.kanade.domain.manga.model.MangaCover,
|
||||||
|
coverAlpha: Float = 1f,
|
||||||
|
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
|
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
GridItemSelectable(
|
||||||
|
isSelected = isSelected,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
) {
|
||||||
|
MangaGridCover(
|
||||||
|
cover = {
|
||||||
|
MangaCover.Book(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
||||||
|
data = coverData,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
badgesStart = coverBadgeStart,
|
||||||
|
badgesEnd = coverBadgeEnd,
|
||||||
|
content = {
|
||||||
|
if (title != null) {
|
||||||
|
CoverTextOverlay(title = title)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title overlay for [MangaCompactGridItem]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.CoverTextOverlay(title: String) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp))
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
0f to Color.Transparent,
|
||||||
|
1f to Color(0xAA000000),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.fillMaxHeight(0.33f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter),
|
||||||
|
)
|
||||||
|
GridItemTitle(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.align(Alignment.BottomStart),
|
||||||
|
title = title,
|
||||||
|
style = MaterialTheme.typography.titleSmall.copy(
|
||||||
|
color = Color.White,
|
||||||
|
shadow = Shadow(
|
||||||
|
color = Color.Black,
|
||||||
|
blurRadius = 4f,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout of grid list item with title below the cover.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MangaComfortableGridItem(
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
title: String,
|
||||||
|
coverData: eu.kanade.domain.manga.model.MangaCover,
|
||||||
|
coverAlpha: Float = 1f,
|
||||||
|
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
|
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
GridItemSelectable(
|
||||||
|
isSelected = isSelected,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
MangaGridCover(
|
||||||
|
cover = {
|
||||||
|
MangaCover.Book(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
||||||
|
data = coverData,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
badgesStart = coverBadgeStart,
|
||||||
|
badgesEnd = coverBadgeEnd,
|
||||||
|
)
|
||||||
|
GridItemTitle(
|
||||||
|
modifier = Modifier.padding(4.dp),
|
||||||
|
title = title,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common cover layout to add contents to be drawn on top of the cover.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun MangaGridCover(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
cover: @Composable BoxScope.() -> Unit = {},
|
||||||
|
badgesStart: (@Composable RowScope.() -> Unit)? = null,
|
||||||
|
badgesEnd: (@Composable RowScope.() -> Unit)? = null,
|
||||||
|
content: @Composable (BoxScope.() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(MangaCover.Book.ratio),
|
||||||
|
) {
|
||||||
|
cover()
|
||||||
|
content?.invoke(this)
|
||||||
|
if (badgesStart != null) {
|
||||||
|
BadgeGroup(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.align(Alignment.TopStart),
|
||||||
|
content = badgesStart,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (badgesEnd != null) {
|
||||||
|
BadgeGroup(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.align(Alignment.TopEnd),
|
||||||
|
content = badgesEnd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GridItemTitle(
|
||||||
|
modifier: Modifier,
|
||||||
|
title: String,
|
||||||
|
style: TextStyle,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = modifier,
|
||||||
|
text = title,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 18.sp,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = style,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for grid items to handle selection state, click and long click.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun GridItemSelectable(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
)
|
||||||
|
.selectedOutline(isSelected = isSelected, color = MaterialTheme.colorScheme.secondary)
|
||||||
|
.padding(4.dp),
|
||||||
|
) {
|
||||||
|
val contentColor = if (isSelected) {
|
||||||
|
MaterialTheme.colorScheme.onSecondary
|
||||||
|
} else {
|
||||||
|
LocalContentColor.current
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see GridItemSelectable
|
||||||
|
*/
|
||||||
|
private fun Modifier.selectedOutline(
|
||||||
|
isSelected: Boolean,
|
||||||
|
color: Color,
|
||||||
|
): Modifier {
|
||||||
|
class SelectedOutlineNode(var selected: Boolean, var color: Color) : DrawModifierNode, Modifier.Node() {
|
||||||
|
override fun ContentDrawScope.draw() {
|
||||||
|
if (selected) drawRect(color)
|
||||||
|
drawContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this then modifierElementOf(
|
||||||
|
params = isSelected.hashCode() + color.hashCode(),
|
||||||
|
create = { SelectedOutlineNode(isSelected, color) },
|
||||||
|
update = {
|
||||||
|
it.selected = isSelected
|
||||||
|
it.color = color
|
||||||
|
},
|
||||||
|
definitions = {
|
||||||
|
name = "selectionOutline"
|
||||||
|
properties["isSelected"] = isSelected
|
||||||
|
properties["color"] = color
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout of list item.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MangaListItem(
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
title: String,
|
||||||
|
coverData: eu.kanade.domain.manga.model.MangaCover,
|
||||||
|
coverAlpha: Float = 1f,
|
||||||
|
badge: @Composable RowScope.() -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.selectedBackground(isSelected)
|
||||||
|
.height(56.dp)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
MangaCover.Square(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.alpha(coverAlpha),
|
||||||
|
data = coverData,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.weight(1f),
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
BadgeGroup(content = badge)
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ fun DeleteLibraryMangaDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
const val DIVIDER_ALPHA = 0.2f
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Divider(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Divider(
|
||||||
|
modifier = modifier,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DownloadDropdownMenu(
|
||||||
|
expanded: Boolean,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onDownloadClicked: (DownloadAction) -> Unit,
|
||||||
|
includeDownloadAllOption: Boolean = true,
|
||||||
|
) {
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_1)) },
|
||||||
|
onClick = {
|
||||||
|
onDownloadClicked(DownloadAction.NEXT_1_CHAPTER)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_5)) },
|
||||||
|
onClick = {
|
||||||
|
onDownloadClicked(DownloadAction.NEXT_5_CHAPTERS)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_10)) },
|
||||||
|
onClick = {
|
||||||
|
onDownloadClicked(DownloadAction.NEXT_10_CHAPTERS)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_custom)) },
|
||||||
|
onClick = {
|
||||||
|
onDownloadClicked(DownloadAction.CUSTOM)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_unread)) },
|
||||||
|
onClick = {
|
||||||
|
onDownloadClicked(DownloadAction.UNREAD_CHAPTERS)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (includeDownloadAllOption) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.download_all)) },
|
||||||
|
onClick = {
|
||||||
|
onDownloadClicked(DownloadAction.ALL_CHAPTERS)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,29 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
||||||
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import me.saket.cascade.CascadeColumnScope
|
||||||
|
import me.saket.cascade.CascadeDropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
|
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -20,6 +31,7 @@ fun DropdownMenu(
|
|||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
offset: DpOffset = DpOffset(8.dp, (-56).dp),
|
||||||
properties: PopupProperties = PopupProperties(focusable = true),
|
properties: PopupProperties = PopupProperties(focusable = true),
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -27,7 +39,7 @@ fun DropdownMenu(
|
|||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
|
modifier = modifier.sizeIn(minWidth = 196.dp, maxWidth = 196.dp),
|
||||||
offset = DpOffset(8.dp, (-8).dp),
|
offset = offset,
|
||||||
properties = properties,
|
properties = properties,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
@ -46,15 +58,39 @@ fun RadioMenuItem(
|
|||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.RadioButtonChecked,
|
imageVector = Icons.Outlined.RadioButtonChecked,
|
||||||
contentDescription = "",
|
contentDescription = stringResource(R.string.selected),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.RadioButtonUnchecked,
|
imageVector = Icons.Outlined.RadioButtonUnchecked,
|
||||||
contentDescription = "",
|
contentDescription = stringResource(R.string.not_selected),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OverflowMenu(
|
||||||
|
content: @Composable CascadeColumnScope.(() -> Unit) -> Unit,
|
||||||
|
) {
|
||||||
|
var moreExpanded by remember { mutableStateOf(false) }
|
||||||
|
val closeMenu = { moreExpanded = false }
|
||||||
|
|
||||||
|
Box {
|
||||||
|
IconButton(onClick = { moreExpanded = !moreExpanded }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.MoreVert,
|
||||||
|
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CascadeDropdownMenu(
|
||||||
|
expanded = moreExpanded,
|
||||||
|
onDismissRequest = closeMenu,
|
||||||
|
offset = DpOffset(8.dp, (-56).dp),
|
||||||
|
) {
|
||||||
|
content(closeMenu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ fun DuplicateMangaDialog(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.paddingFromBaseline
|
import androidx.compose.foundation.layout.paddingFromBaseline
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
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
|
||||||
@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastFirstOrNull
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -100,9 +101,9 @@ fun EmptyScreen(
|
|||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
) { measurables, constraints ->
|
) { measurables, constraints ->
|
||||||
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
|
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
|
||||||
val facePlaceable = measurables.first { it.layoutId == "face" }
|
val facePlaceable = measurables.fastFirstOrNull { it.layoutId == "face" }!!
|
||||||
.measure(looseConstraints)
|
.measure(looseConstraints)
|
||||||
val actionsPlaceable = measurables.firstOrNull { it.layoutId == "actions" }
|
val actionsPlaceable = measurables.fastFirstOrNull { it.layoutId == "actions" }
|
||||||
?.measure(looseConstraints)
|
?.measure(looseConstraints)
|
||||||
|
|
||||||
layout(constraints.maxWidth, constraints.maxHeight) {
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
@ -187,12 +188,12 @@ private fun WithActionPreview() {
|
|||||||
actions = listOf(
|
actions = listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringResId = R.string.action_retry,
|
||||||
icon = Icons.Default.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.getting_started_guide,
|
stringResId = R.string.getting_started_guide,
|
||||||
icon = Icons.Default.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -22,13 +22,13 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.BookmarkAdd
|
import androidx.compose.material.icons.outlined.BookmarkAdd
|
||||||
import androidx.compose.material.icons.filled.BookmarkRemove
|
import androidx.compose.material.icons.outlined.BookmarkRemove
|
||||||
import androidx.compose.material.icons.filled.DoneAll
|
|
||||||
import androidx.compose.material.icons.filled.RemoveDone
|
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.Label
|
import androidx.compose.material.icons.outlined.Label
|
||||||
|
import androidx.compose.material.icons.outlined.RemoveDone
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -37,8 +37,10 @@ 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.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
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.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.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
@ -48,6 +50,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
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.DownloadAction
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -98,7 +101,7 @@ fun MangaBottomActionMenu(
|
|||||||
if (onBookmarkClicked != null) {
|
if (onBookmarkClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_bookmark),
|
title = stringResource(R.string.action_bookmark),
|
||||||
icon = Icons.Default.BookmarkAdd,
|
icon = Icons.Outlined.BookmarkAdd,
|
||||||
toConfirm = confirm[0],
|
toConfirm = confirm[0],
|
||||||
onLongClick = { onLongClickItem(0) },
|
onLongClick = { onLongClickItem(0) },
|
||||||
onClick = onBookmarkClicked,
|
onClick = onBookmarkClicked,
|
||||||
@ -107,7 +110,7 @@ fun MangaBottomActionMenu(
|
|||||||
if (onRemoveBookmarkClicked != null) {
|
if (onRemoveBookmarkClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_remove_bookmark),
|
title = stringResource(R.string.action_remove_bookmark),
|
||||||
icon = Icons.Default.BookmarkRemove,
|
icon = Icons.Outlined.BookmarkRemove,
|
||||||
toConfirm = confirm[1],
|
toConfirm = confirm[1],
|
||||||
onLongClick = { onLongClickItem(1) },
|
onLongClick = { onLongClickItem(1) },
|
||||||
onClick = onRemoveBookmarkClicked,
|
onClick = onRemoveBookmarkClicked,
|
||||||
@ -116,7 +119,7 @@ fun MangaBottomActionMenu(
|
|||||||
if (onMarkAsReadClicked != null) {
|
if (onMarkAsReadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_read),
|
title = stringResource(R.string.action_mark_as_read),
|
||||||
icon = Icons.Default.DoneAll,
|
icon = Icons.Outlined.DoneAll,
|
||||||
toConfirm = confirm[2],
|
toConfirm = confirm[2],
|
||||||
onLongClick = { onLongClickItem(2) },
|
onLongClick = { onLongClickItem(2) },
|
||||||
onClick = onMarkAsReadClicked,
|
onClick = onMarkAsReadClicked,
|
||||||
@ -125,7 +128,7 @@ fun MangaBottomActionMenu(
|
|||||||
if (onMarkAsUnreadClicked != null) {
|
if (onMarkAsUnreadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_unread),
|
title = stringResource(R.string.action_mark_as_unread),
|
||||||
icon = Icons.Default.RemoveDone,
|
icon = Icons.Outlined.RemoveDone,
|
||||||
toConfirm = confirm[3],
|
toConfirm = confirm[3],
|
||||||
onLongClick = { onLongClickItem(3) },
|
onLongClick = { onLongClickItem(3) },
|
||||||
onClick = onMarkAsUnreadClicked,
|
onClick = onMarkAsUnreadClicked,
|
||||||
@ -170,6 +173,7 @@ private fun RowScope.Button(
|
|||||||
toConfirm: Boolean,
|
toConfirm: Boolean,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
content: (@Composable () -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
|
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
|
||||||
Column(
|
Column(
|
||||||
@ -201,6 +205,7 @@ private fun RowScope.Button(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
content?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +216,7 @@ fun LibraryBottomActionMenu(
|
|||||||
onChangeCategoryClicked: (() -> Unit)?,
|
onChangeCategoryClicked: (() -> Unit)?,
|
||||||
onMarkAsReadClicked: (() -> Unit)?,
|
onMarkAsReadClicked: (() -> Unit)?,
|
||||||
onMarkAsUnreadClicked: (() -> Unit)?,
|
onMarkAsUnreadClicked: (() -> Unit)?,
|
||||||
onDownloadClicked: (() -> Unit)?,
|
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||||
onDeleteClicked: (() -> Unit)?,
|
onDeleteClicked: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
@ -254,7 +259,7 @@ fun LibraryBottomActionMenu(
|
|||||||
if (onMarkAsReadClicked != null) {
|
if (onMarkAsReadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_read),
|
title = stringResource(R.string.action_mark_as_read),
|
||||||
icon = Icons.Default.DoneAll,
|
icon = Icons.Outlined.DoneAll,
|
||||||
toConfirm = confirm[1],
|
toConfirm = confirm[1],
|
||||||
onLongClick = { onLongClickItem(1) },
|
onLongClick = { onLongClickItem(1) },
|
||||||
onClick = onMarkAsReadClicked,
|
onClick = onMarkAsReadClicked,
|
||||||
@ -263,21 +268,30 @@ fun LibraryBottomActionMenu(
|
|||||||
if (onMarkAsUnreadClicked != null) {
|
if (onMarkAsUnreadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_unread),
|
title = stringResource(R.string.action_mark_as_unread),
|
||||||
icon = Icons.Default.RemoveDone,
|
icon = Icons.Outlined.RemoveDone,
|
||||||
toConfirm = confirm[2],
|
toConfirm = confirm[2],
|
||||||
onLongClick = { onLongClickItem(2) },
|
onLongClick = { onLongClickItem(2) },
|
||||||
onClick = onMarkAsUnreadClicked,
|
onClick = onMarkAsUnreadClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (onDownloadClicked != null) {
|
if (onDownloadClicked != null) {
|
||||||
|
var downloadExpanded by remember { mutableStateOf(false) }
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_download),
|
title = stringResource(R.string.action_download),
|
||||||
icon = Icons.Outlined.Download,
|
icon = Icons.Outlined.Download,
|
||||||
toConfirm = confirm[3],
|
toConfirm = confirm[3],
|
||||||
onLongClick = { onLongClickItem(3) },
|
onLongClick = { onLongClickItem(3) },
|
||||||
onClick = onDownloadClicked,
|
onClick = { downloadExpanded = !downloadExpanded },
|
||||||
|
) {
|
||||||
|
val onDismissRequest = { downloadExpanded = false }
|
||||||
|
DownloadDropdownMenu(
|
||||||
|
expanded = downloadExpanded,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
onDownloadClicked = onDownloadClicked,
|
||||||
|
includeDownloadAllOption = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (onDeleteClicked != null) {
|
if (onDeleteClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_delete),
|
title = stringResource(R.string.action_delete),
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.presentation.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@ -11,7 +11,6 @@ import androidx.compose.ui.graphics.Shape
|
|||||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -26,7 +25,7 @@ enum class MangaCover(val ratio: Float) {
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
data: Any?,
|
data: Any?,
|
||||||
contentDescription: String = "",
|
contentDescription: String = "",
|
||||||
shape: Shape = RoundedCornerShape(4.dp),
|
shape: Shape = MaterialTheme.shapes.extraSmall,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
|
182
app/src/main/java/eu/kanade/presentation/components/Pager.kt
Normal file
182
app/src/main/java/eu/kanade/presentation/components/Pager.kt
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.saveable.listSaver
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.util.fastMaxBy
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HorizontalPager(
|
||||||
|
count: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
state: PagerState = rememberPagerState(),
|
||||||
|
key: ((page: Int) -> Any)? = null,
|
||||||
|
contentPadding: PaddingValues = PaddingValues(),
|
||||||
|
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
||||||
|
userScrollEnabled: Boolean = true,
|
||||||
|
content: @Composable BoxScope.(page: Int) -> Unit,
|
||||||
|
) {
|
||||||
|
Pager(
|
||||||
|
count = count,
|
||||||
|
modifier = modifier,
|
||||||
|
state = state,
|
||||||
|
isVertical = false,
|
||||||
|
key = key,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
verticalAlignment = verticalAlignment,
|
||||||
|
userScrollEnabled = userScrollEnabled,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Pager(
|
||||||
|
count: Int,
|
||||||
|
modifier: Modifier,
|
||||||
|
state: PagerState,
|
||||||
|
isVertical: Boolean,
|
||||||
|
key: ((page: Int) -> Any)?,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
userScrollEnabled: Boolean,
|
||||||
|
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
|
||||||
|
content: @Composable BoxScope.(page: Int) -> Unit,
|
||||||
|
) {
|
||||||
|
LaunchedEffect(count) {
|
||||||
|
state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { state.updateCurrentPageBasedOnLazyListState() }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isVertical) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier,
|
||||||
|
state = state.lazyListState,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
horizontalAlignment = horizontalAlignment,
|
||||||
|
verticalArrangement = Arrangement.aligned(verticalAlignment),
|
||||||
|
userScrollEnabled = userScrollEnabled,
|
||||||
|
flingBehavior = rememberSnapFlingBehavior(lazyListState = state.lazyListState),
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
count = count,
|
||||||
|
key = key,
|
||||||
|
) { page ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillParentMaxHeight()
|
||||||
|
.wrapContentSize(),
|
||||||
|
) {
|
||||||
|
content(this, page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyRow(
|
||||||
|
modifier = modifier,
|
||||||
|
state = state.lazyListState,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
verticalAlignment = verticalAlignment,
|
||||||
|
horizontalArrangement = Arrangement.aligned(horizontalAlignment),
|
||||||
|
userScrollEnabled = userScrollEnabled,
|
||||||
|
flingBehavior = rememberSnapFlingBehavior(lazyListState = state.lazyListState),
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
count = count,
|
||||||
|
key = key,
|
||||||
|
) { page ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillParentMaxWidth()
|
||||||
|
.wrapContentSize(),
|
||||||
|
) {
|
||||||
|
content(this, page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberPagerState(
|
||||||
|
initialPage: Int = 0,
|
||||||
|
) = rememberSaveable(saver = PagerState.Saver) {
|
||||||
|
PagerState(currentPage = initialPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class PagerState(
|
||||||
|
currentPage: Int = 0,
|
||||||
|
) {
|
||||||
|
init { check(currentPage >= 0) { "currentPage cannot be less than zero" } }
|
||||||
|
|
||||||
|
val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
|
||||||
|
|
||||||
|
private var _currentPage by mutableStateOf(currentPage)
|
||||||
|
|
||||||
|
var currentPage: Int
|
||||||
|
get() = _currentPage
|
||||||
|
set(value) {
|
||||||
|
if (value != _currentPage) {
|
||||||
|
_currentPage = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val mostVisiblePageLayoutInfo: LazyListItemInfo?
|
||||||
|
get() {
|
||||||
|
val layoutInfo = lazyListState.layoutInfo
|
||||||
|
return layoutInfo.visibleItemsInfo.fastMaxBy {
|
||||||
|
val start = maxOf(it.offset, 0)
|
||||||
|
val end = minOf(
|
||||||
|
it.offset + it.size,
|
||||||
|
layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding,
|
||||||
|
)
|
||||||
|
end - start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCurrentPageBasedOnLazyListState() {
|
||||||
|
mostVisiblePageLayoutInfo?.let {
|
||||||
|
currentPage = it.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun animateScrollToPage(page: Int) {
|
||||||
|
lazyListState.animateScrollToItem(index = page)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun scrollToPage(page: Int) {
|
||||||
|
lazyListState.scrollToItem(index = page)
|
||||||
|
updateCurrentPageBasedOnLazyListState()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val Saver: Saver<PagerState, *> = listSaver(
|
||||||
|
save = { listOf(it.currentPage) },
|
||||||
|
restore = { PagerState(it[0]) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredWidth
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -28,7 +27,7 @@ fun Pill(
|
|||||||
androidx.compose.material3.Surface(
|
androidx.compose.material3.Surface(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 4.dp),
|
.padding(start = 4.dp),
|
||||||
shape = RoundedCornerShape(100),
|
shape = MaterialTheme.shapes.extraLarge,
|
||||||
color = color,
|
color = color,
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
tonalElevation = elevation,
|
tonalElevation = elevation,
|
||||||
@ -43,7 +42,6 @@ fun Pill(
|
|||||||
text = text,
|
text = text,
|
||||||
fontSize = fontSize,
|
fontSize = fontSize,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
softWrap = false,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,167 +0,0 @@
|
|||||||
package eu.kanade.presentation.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
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.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
|
||||||
|
|
||||||
const val DIVIDER_ALPHA = 0.2f
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Divider(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
androidx.compose.material3.Divider(
|
|
||||||
modifier = modifier,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PreferenceRow(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
title: String,
|
|
||||||
painter: Painter? = null,
|
|
||||||
onClick: () -> Unit = {},
|
|
||||||
onLongClick: () -> Unit = {},
|
|
||||||
subtitle: String? = null,
|
|
||||||
action: @Composable (() -> Unit)? = null,
|
|
||||||
) {
|
|
||||||
val height = if (subtitle != null) 72.dp else 56.dp
|
|
||||||
|
|
||||||
val titleTextStyle = MaterialTheme.typography.bodyLarge
|
|
||||||
val subtitleTextStyle = MaterialTheme.typography.bodyMedium.copy(
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f),
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(min = height)
|
|
||||||
.combinedClickable(
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
onClick = onClick,
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
if (painter != null) {
|
|
||||||
Icon(
|
|
||||||
painter = painter,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = horizontalPadding, end = 16.dp)
|
|
||||||
.size(24.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.weight(1f),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = titleTextStyle,
|
|
||||||
)
|
|
||||||
if (subtitle != null) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(top = 4.dp),
|
|
||||||
text = subtitle,
|
|
||||||
style = subtitleTextStyle,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (action != null) {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.widthIn(min = 56.dp)
|
|
||||||
.padding(end = horizontalPadding),
|
|
||||||
) {
|
|
||||||
action()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SwitchPreference(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
checked: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
title: String,
|
|
||||||
subtitle: String? = null,
|
|
||||||
painter: Painter? = null,
|
|
||||||
) {
|
|
||||||
PreferenceRow(
|
|
||||||
modifier = modifier,
|
|
||||||
title = title,
|
|
||||||
subtitle = subtitle,
|
|
||||||
painter = painter,
|
|
||||||
action = { Switch(checked = checked, onCheckedChange = null) },
|
|
||||||
onClick = onClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SwitchPreference(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
preference: PreferenceMutableState<Boolean>,
|
|
||||||
title: String,
|
|
||||||
subtitle: String? = null,
|
|
||||||
painter: Painter? = null,
|
|
||||||
) {
|
|
||||||
SwitchPreference(
|
|
||||||
modifier = modifier,
|
|
||||||
title = title,
|
|
||||||
subtitle = subtitle,
|
|
||||||
painter = painter,
|
|
||||||
checked = preference.value,
|
|
||||||
onClick = { preference.value = !preference.value },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun PreferencesPreview() {
|
|
||||||
TachiyomiTheme {
|
|
||||||
Column {
|
|
||||||
PreferenceRow(
|
|
||||||
title = "Plain",
|
|
||||||
subtitle = "Subtitle",
|
|
||||||
)
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
SwitchPreference(
|
|
||||||
title = "Switch (on)",
|
|
||||||
subtitle = "Subtitle",
|
|
||||||
checked = true,
|
|
||||||
onClick = {},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
title = "Switch (off)",
|
|
||||||
subtitle = "Subtitle",
|
|
||||||
checked = false,
|
|
||||||
onClick = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,9 +19,11 @@ package eu.kanade.presentation.components
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ScaffoldDefaults
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.contentColorFor
|
import androidx.compose.material3.contentColorFor
|
||||||
@ -37,6 +39,11 @@ import androidx.compose.ui.layout.SubcomposeLayout
|
|||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.max
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import androidx.compose.ui.util.fastMap
|
||||||
|
import androidx.compose.ui.util.fastMaxBy
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>.
|
* <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>.
|
||||||
@ -59,6 +66,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
* * Pass scroll behavior to top bar by default
|
* * Pass scroll behavior to top bar by default
|
||||||
* * Remove height constraint for expanded app bar
|
* * Remove height constraint for expanded app bar
|
||||||
* * Also take account of fab height when providing inner padding
|
* * Also take account of fab height when providing inner padding
|
||||||
|
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
|
||||||
*
|
*
|
||||||
* @param modifier the [Modifier] to be applied to this scaffold
|
* @param modifier the [Modifier] to be applied to this scaffold
|
||||||
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
||||||
@ -72,6 +80,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
* @param contentColor the preferred color for content inside this scaffold. Defaults to either the
|
* @param contentColor the preferred color for content inside this scaffold. Defaults to either the
|
||||||
* matching content color for [containerColor], or to the current [LocalContentColor] if
|
* matching content color for [containerColor], or to the current [LocalContentColor] if
|
||||||
* [containerColor] is not a color from the theme.
|
* [containerColor] is not a color from the theme.
|
||||||
|
* @param contentWindowInsets window insets to be passed to content slot via PaddingValues params.
|
||||||
|
* Scaffold will take the insets into account from the top/bottom only if the topBar/ bottomBar
|
||||||
|
* are not present, as the scaffold expect topBar/bottomBar to handle insets instead
|
||||||
* @param content content of the screen. The lambda receives a [PaddingValues] that should be
|
* @param content content of the screen. The lambda receives a [PaddingValues] that should be
|
||||||
* applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to
|
* applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to
|
||||||
* properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to
|
* properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to
|
||||||
@ -89,6 +100,7 @@ fun Scaffold(
|
|||||||
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
floatingActionButtonPosition: FabPosition = FabPosition.End,
|
||||||
containerColor: Color = MaterialTheme.colorScheme.background,
|
containerColor: Color = MaterialTheme.colorScheme.background,
|
||||||
contentColor: Color = contentColorFor(containerColor),
|
contentColor: Color = contentColorFor(containerColor),
|
||||||
|
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.Surface(
|
androidx.compose.material3.Surface(
|
||||||
@ -104,6 +116,7 @@ fun Scaffold(
|
|||||||
bottomBar = bottomBar,
|
bottomBar = bottomBar,
|
||||||
content = content,
|
content = content,
|
||||||
snackbar = snackbarHost,
|
snackbar = snackbarHost,
|
||||||
|
contentWindowInsets = contentWindowInsets,
|
||||||
fab = floatingActionButton,
|
fab = floatingActionButton,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -129,6 +142,7 @@ private fun ScaffoldLayout(
|
|||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
snackbar: @Composable () -> Unit,
|
snackbar: @Composable () -> Unit,
|
||||||
fab: @Composable () -> Unit,
|
fab: @Composable () -> Unit,
|
||||||
|
contentWindowInsets: WindowInsets,
|
||||||
bottomBar: @Composable () -> Unit,
|
bottomBar: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
SubcomposeLayout { constraints ->
|
SubcomposeLayout { constraints ->
|
||||||
@ -143,37 +157,51 @@ private fun ScaffoldLayout(
|
|||||||
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
|
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
|
||||||
|
|
||||||
layout(layoutWidth, layoutHeight) {
|
layout(layoutWidth, layoutHeight) {
|
||||||
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).map {
|
val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
|
||||||
|
val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
|
||||||
|
val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
|
||||||
|
// Tachiyomi: layoutWidth after horizontal insets
|
||||||
|
val insetLayoutWidth = layoutWidth - leftInset - rightInset
|
||||||
|
|
||||||
|
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
|
||||||
it.measure(topBarConstraints)
|
it.measure(topBarConstraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
val topBarHeight = topBarPlaceables.maxByOrNull { it.height }?.height ?: 0
|
val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
|
||||||
|
|
||||||
val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).map {
|
val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
|
||||||
it.measure(looseConstraints)
|
it.measure(looseConstraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
val snackbarHeight = snackbarPlaceables.maxByOrNull { it.height }?.height ?: 0
|
val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
|
||||||
val snackbarWidth = snackbarPlaceables.maxByOrNull { it.width }?.width ?: 0
|
val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
|
||||||
|
|
||||||
|
// Tachiyomi: Calculate insets for snackbar placement offset
|
||||||
|
val snackbarLeft = if (snackbarPlaceables.isNotEmpty()) {
|
||||||
|
(insetLayoutWidth - snackbarWidth) / 2 + leftInset
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
val fabPlaceables =
|
val fabPlaceables =
|
||||||
subcompose(ScaffoldLayoutContent.Fab, fab).mapNotNull { measurable ->
|
subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
|
||||||
measurable.measure(looseConstraints).takeIf { it.height != 0 && it.width != 0 }
|
measurable.measure(looseConstraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fabHeight = fabPlaceables.maxByOrNull { it.height }?.height ?: 0
|
val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0
|
||||||
|
val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0
|
||||||
|
|
||||||
val fabPlacement = if (fabPlaceables.isNotEmpty()) {
|
val fabPlacement = if (fabPlaceables.isNotEmpty() && fabWidth != 0 && fabHeight != 0) {
|
||||||
val fabWidth = fabPlaceables.maxByOrNull { it.width }!!.width
|
|
||||||
// FAB distance from the left of the layout, taking into account LTR / RTL
|
// FAB distance from the left of the layout, taking into account LTR / RTL
|
||||||
|
// Tachiyomi: Calculate insets for fab placement offset
|
||||||
val fabLeftOffset = if (fabPosition == FabPosition.End) {
|
val fabLeftOffset = if (fabPosition == FabPosition.End) {
|
||||||
if (layoutDirection == LayoutDirection.Ltr) {
|
if (layoutDirection == LayoutDirection.Ltr) {
|
||||||
layoutWidth - FabSpacing.roundToPx() - fabWidth
|
layoutWidth - FabSpacing.roundToPx() - fabWidth - rightInset
|
||||||
} else {
|
} else {
|
||||||
FabSpacing.roundToPx()
|
FabSpacing.roundToPx() + leftInset
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(layoutWidth - fabWidth) / 2
|
leftInset + ((insetLayoutWidth - fabWidth) / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
FabPlacement(
|
FabPlacement(
|
||||||
@ -190,75 +218,63 @@ private fun ScaffoldLayout(
|
|||||||
LocalFabPlacement provides fabPlacement,
|
LocalFabPlacement provides fabPlacement,
|
||||||
content = bottomBar,
|
content = bottomBar,
|
||||||
)
|
)
|
||||||
}.map { it.measure(looseConstraints) }
|
}.fastMap { it.measure(looseConstraints) }
|
||||||
|
|
||||||
val bottomBarHeight = bottomBarPlaceables.maxByOrNull { it.height }?.height ?: 0
|
val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
|
||||||
val fabOffsetFromBottom = fabPlacement?.let {
|
val fabOffsetFromBottom = fabPlacement?.let {
|
||||||
if (bottomBarHeight == 0) {
|
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
|
||||||
it.height + FabSpacing.roundToPx()
|
|
||||||
} else {
|
|
||||||
// Total height is the bottom bar height + the FAB height + the padding
|
|
||||||
// between the FAB and bottom bar
|
|
||||||
bottomBarHeight + it.height + FabSpacing.roundToPx()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
|
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
|
||||||
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight)
|
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tachiyomi: Also take account of fab height when providing inner padding
|
|
||||||
*/
|
|
||||||
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
|
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
|
||||||
val insets = WindowInsets.Companion.safeDrawing
|
val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
|
||||||
.asPaddingValues(this@SubcomposeLayout)
|
val fabOffsetDp = fabOffsetFromBottom?.toDp() ?: 0.dp
|
||||||
|
val bottomBarHeightPx = bottomBarHeight ?: 0
|
||||||
val innerPadding = PaddingValues(
|
val innerPadding = PaddingValues(
|
||||||
top =
|
top =
|
||||||
if (topBarHeight == 0) {
|
if (topBarPlaceables.isEmpty()) {
|
||||||
insets.calculateTopPadding()
|
insets.calculateTopPadding()
|
||||||
} else {
|
} else {
|
||||||
topBarHeight.toDp()
|
topBarHeight.toDp()
|
||||||
},
|
},
|
||||||
bottom =
|
bottom = // Tachiyomi: Also take account of fab height when providing inner padding
|
||||||
(
|
if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) {
|
||||||
if (bottomBarHeight == 0) {
|
max(insets.calculateBottomPadding(), fabOffsetDp)
|
||||||
insets.calculateBottomPadding()
|
|
||||||
} else {
|
} else {
|
||||||
bottomBarHeight.toDp()
|
max(bottomBarHeightPx.toDp(), fabOffsetDp)
|
||||||
}
|
},
|
||||||
) + fabHeight.toDp(),
|
start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
|
||||||
start = insets.calculateLeftPadding((this@SubcomposeLayout).layoutDirection),
|
end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
|
||||||
end = insets.calculateRightPadding((this@SubcomposeLayout).layoutDirection),
|
|
||||||
)
|
)
|
||||||
content(innerPadding)
|
content(innerPadding)
|
||||||
}.map { it.measure(looseConstraints) }
|
}.fastMap { it.measure(looseConstraints) }
|
||||||
|
|
||||||
// Placing to control drawing order to match default elevation of each placeable
|
// Placing to control drawing order to match default elevation of each placeable
|
||||||
|
|
||||||
bodyContentPlaceables.forEach {
|
bodyContentPlaceables.fastForEach {
|
||||||
it.place(0, 0)
|
it.place(0, 0)
|
||||||
}
|
}
|
||||||
topBarPlaceables.forEach {
|
topBarPlaceables.fastForEach {
|
||||||
it.place(0, 0)
|
it.place(0, 0)
|
||||||
}
|
}
|
||||||
snackbarPlaceables.forEach {
|
snackbarPlaceables.fastForEach {
|
||||||
it.place(
|
it.place(
|
||||||
(layoutWidth - snackbarWidth) / 2,
|
snackbarLeft,
|
||||||
layoutHeight - snackbarOffsetFromBottom,
|
layoutHeight - snackbarOffsetFromBottom,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// The bottom bar is always at the bottom of the layout
|
// The bottom bar is always at the bottom of the layout
|
||||||
bottomBarPlaceables.forEach {
|
bottomBarPlaceables.fastForEach {
|
||||||
it.place(0, layoutHeight - bottomBarHeight)
|
it.place(0, layoutHeight - (bottomBarHeight ?: 0))
|
||||||
}
|
}
|
||||||
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
|
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
|
||||||
fabPlacement?.let { placement ->
|
fabPlaceables.fastForEach {
|
||||||
fabPlaceables.forEach {
|
it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
|
||||||
it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateEndPadding
|
|||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -16,8 +17,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ fun TabbedScreen(
|
|||||||
tabs: List<TabContent>,
|
tabs: List<TabContent>,
|
||||||
startIndex: Int? = null,
|
startIndex: Int? = null,
|
||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
@StringRes placeholderRes: Int? = null,
|
|
||||||
onChangeSearchQuery: (String?) -> Unit = {},
|
onChangeSearchQuery: (String?) -> Unit = {},
|
||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
@ -43,28 +41,16 @@ fun TabbedScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
if (searchQuery == null) {
|
val tab = tabs[state.currentPage]
|
||||||
AppBar(
|
val searchEnabled = tab.searchEnabled
|
||||||
title = stringResource(titleRes),
|
|
||||||
actions = {
|
|
||||||
AppBarActions(tabs[state.currentPage].actions)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
searchQuery = searchQuery,
|
titleContent = { AppBarTitle(stringResource(titleRes)) },
|
||||||
placeholderText = placeholderRes?.let { stringResource(it) },
|
searchEnabled = searchEnabled,
|
||||||
onChangeSearchQuery = {
|
searchQuery = if (searchEnabled) searchQuery else null,
|
||||||
onChangeSearchQuery(it)
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
},
|
actions = { AppBarActions(tab.actions) },
|
||||||
onClickCloseSearch = {
|
|
||||||
onChangeSearchQuery(null)
|
|
||||||
},
|
|
||||||
onClickResetSearch = {
|
|
||||||
onChangeSearchQuery("")
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
Column(
|
Column(
|
||||||
@ -82,9 +68,8 @@ fun TabbedScreen(
|
|||||||
Tab(
|
Tab(
|
||||||
selected = state.currentPage == index,
|
selected = state.currentPage == index,
|
||||||
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
||||||
text = {
|
text = { TabText(text = stringResource(tab.titleRes), badgeCount = tab.badgeNumber) },
|
||||||
TabText(stringResource(tab.titleRes), tab.badgeNumber, state.currentPage == index)
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,6 +95,7 @@ fun TabbedScreen(
|
|||||||
data class TabContent(
|
data class TabContent(
|
||||||
@StringRes val titleRes: Int,
|
@StringRes val titleRes: Int,
|
||||||
val badgeNumber: Int? = null,
|
val badgeNumber: Int? = null,
|
||||||
|
val searchEnabled: Boolean = false,
|
||||||
val actions: List<AppBar.Action> = emptyList(),
|
val actions: List<AppBar.Action> = emptyList(),
|
||||||
val content: @Composable (contentPadding: PaddingValues) -> Unit,
|
val content: @Composable (contentPadding: PaddingValues) -> Unit,
|
||||||
)
|
)
|
||||||
|
@ -30,17 +30,13 @@ fun TabIndicator(currentTabPosition: TabPosition) {
|
|||||||
fun TabText(
|
fun TabText(
|
||||||
text: String,
|
text: String,
|
||||||
badgeCount: Int? = null,
|
badgeCount: Int? = null,
|
||||||
isCurrentPage: Boolean,
|
|
||||||
) {
|
) {
|
||||||
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
|
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(text = text)
|
||||||
text = text,
|
|
||||||
color = if (isCurrentPage) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground,
|
|
||||||
)
|
|
||||||
if (badgeCount != null) {
|
if (badgeCount != null) {
|
||||||
Pill(
|
Pill(
|
||||||
text = "$badgeCount",
|
text = "$badgeCount",
|
||||||
|
@ -7,10 +7,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.RelativeDateHeader
|
import eu.kanade.presentation.components.RelativeDateHeader
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.presentation.history.HistoryUiModel
|
import eu.kanade.presentation.history.HistoryUiModel
|
||||||
import eu.kanade.presentation.util.plus
|
|
||||||
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.DateFormat
|
import java.text.DateFormat
|
||||||
@ -27,7 +26,7 @@ fun HistoryContent(
|
|||||||
val relativeTime: Int = remember { preferences.relativeTime().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()) }
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
|
@ -67,7 +67,7 @@ fun HistoryDeleteDialog(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -96,7 +96,7 @@ fun HistoryDeleteAllDialog(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
package eu.kanade.presentation.history.components
|
package eu.kanade.presentation.history.components
|
||||||
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||||
import androidx.compose.material.icons.outlined.Search
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import eu.kanade.presentation.components.AppBarTitle
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter
|
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter
|
||||||
@ -26,54 +20,12 @@ fun HistoryToolbar(
|
|||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
) {
|
) {
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
|
|
||||||
if (state.searchQuery == null) {
|
|
||||||
HistoryRegularToolbar(
|
|
||||||
onClickSearch = { state.searchQuery = "" },
|
|
||||||
onClickDelete = { state.dialog = HistoryPresenter.Dialog.DeleteAll },
|
|
||||||
incognitoMode = incognitoMode,
|
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
searchQuery = state.searchQuery!!,
|
titleContent = { AppBarTitle(stringResource(R.string.history)) },
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
onChangeSearchQuery = { state.searchQuery = it },
|
onChangeSearchQuery = { state.searchQuery = it },
|
||||||
placeholderText = stringResource(R.string.action_search_hint),
|
|
||||||
onClickCloseSearch = { state.searchQuery = null },
|
|
||||||
onClickResetSearch = { state.searchQuery = "" },
|
|
||||||
incognitoMode = incognitoMode,
|
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(
|
|
||||||
imeAction = ImeAction.Search,
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
keyboardController?.hide()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun HistoryRegularToolbar(
|
|
||||||
onClickSearch: () -> Unit,
|
|
||||||
onClickDelete: () -> Unit,
|
|
||||||
incognitoMode: Boolean,
|
|
||||||
downloadedOnlyMode: Boolean,
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior,
|
|
||||||
) {
|
|
||||||
AppBar(
|
|
||||||
title = stringResource(R.string.history),
|
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onClickSearch) {
|
IconButton(onClick = { state.dialog = HistoryPresenter.Dialog.DeleteAll }) {
|
||||||
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
|
|
||||||
}
|
|
||||||
IconButton(onClick = onClickDelete) {
|
|
||||||
Icon(Icons.Outlined.DeleteSweep, contentDescription = stringResource(R.string.pref_clear_history))
|
Icon(Icons.Outlined.DeleteSweep, contentDescription = stringResource(R.string.pref_clear_history))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,11 +2,14 @@ package eu.kanade.presentation.library
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.util.fastAll
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.library.model.display
|
import eu.kanade.domain.library.model.display
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
import eu.kanade.domain.manga.model.isLocal
|
||||||
@ -17,6 +20,7 @@ import eu.kanade.presentation.components.LoadingScreen
|
|||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.library.components.LibraryContent
|
import eu.kanade.presentation.library.components.LibraryContent
|
||||||
import eu.kanade.presentation.library.components.LibraryToolbar
|
import eu.kanade.presentation.library.components.LibraryToolbar
|
||||||
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||||
@ -29,14 +33,17 @@ fun LibraryScreen(
|
|||||||
onChangeCategoryClicked: () -> Unit,
|
onChangeCategoryClicked: () -> Unit,
|
||||||
onMarkAsReadClicked: () -> Unit,
|
onMarkAsReadClicked: () -> Unit,
|
||||||
onMarkAsUnreadClicked: () -> Unit,
|
onMarkAsUnreadClicked: () -> Unit,
|
||||||
onDownloadClicked: () -> Unit,
|
onDownloadClicked: (DownloadAction) -> Unit,
|
||||||
onDeleteClicked: () -> Unit,
|
onDeleteClicked: () -> Unit,
|
||||||
onClickUnselectAll: () -> Unit,
|
onClickUnselectAll: () -> Unit,
|
||||||
onClickSelectAll: () -> Unit,
|
onClickSelectAll: () -> Unit,
|
||||||
onClickInvertSelection: () -> Unit,
|
onClickInvertSelection: () -> Unit,
|
||||||
onClickFilter: () -> Unit,
|
onClickFilter: () -> Unit,
|
||||||
onClickRefresh: (Category?) -> Boolean,
|
onClickRefresh: (Category?) -> Boolean,
|
||||||
|
onClickOpenRandomManga: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
val title by presenter.getToolbarTitle()
|
val title by presenter.getToolbarTitle()
|
||||||
@ -51,6 +58,7 @@ fun LibraryScreen(
|
|||||||
onClickInvertSelection = onClickInvertSelection,
|
onClickInvertSelection = onClickInvertSelection,
|
||||||
onClickFilter = onClickFilter,
|
onClickFilter = onClickFilter,
|
||||||
onClickRefresh = { onClickRefresh(null) },
|
onClickRefresh = { onClickRefresh(null) },
|
||||||
|
onClickOpenRandomManga = onClickOpenRandomManga,
|
||||||
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
|
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -60,7 +68,7 @@ fun LibraryScreen(
|
|||||||
onChangeCategoryClicked = onChangeCategoryClicked,
|
onChangeCategoryClicked = onChangeCategoryClicked,
|
||||||
onMarkAsReadClicked = onMarkAsReadClicked,
|
onMarkAsReadClicked = onMarkAsReadClicked,
|
||||||
onMarkAsUnreadClicked = onMarkAsUnreadClicked,
|
onMarkAsUnreadClicked = onMarkAsUnreadClicked,
|
||||||
onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.none { it.manga.isLocal() } },
|
onDownloadClicked = onDownloadClicked.takeIf { presenter.selection.fastAll { !it.manga.isLocal() } },
|
||||||
onDeleteClicked = onDeleteClicked,
|
onDeleteClicked = onDeleteClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -79,7 +87,7 @@ fun LibraryScreen(
|
|||||||
actions = listOf(
|
actions = listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.getting_started_guide,
|
stringResId = R.string.getting_started_guide,
|
||||||
icon = Icons.Default.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -97,7 +105,10 @@ fun LibraryScreen(
|
|||||||
onChangeCurrentPage = { presenter.activeCategory = it },
|
onChangeCurrentPage = { presenter.activeCategory = it },
|
||||||
onMangaClicked = onMangaClicked,
|
onMangaClicked = onMangaClicked,
|
||||||
onToggleSelection = { presenter.toggleSelection(it) },
|
onToggleSelection = { presenter.toggleSelection(it) },
|
||||||
onToggleRangeSelection = { presenter.toggleRangeSelection(it) },
|
onToggleRangeSelection = {
|
||||||
|
presenter.toggleRangeSelection(it)
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
onRefresh = onClickRefresh,
|
onRefresh = onClickRefresh,
|
||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||||
getNumberOfMangaForCategory = { presenter.getMangaCountForCategory(it) },
|
getNumberOfMangaForCategory = { presenter.getMangaCountForCategory(it) },
|
||||||
|
@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
|
|||||||
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 androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
import eu.kanade.presentation.components.CommonMangaItemDefaults
|
||||||
import eu.kanade.presentation.components.FastScrollLazyVerticalGrid
|
import eu.kanade.presentation.components.FastScrollLazyVerticalGrid
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -26,9 +27,9 @@ fun LazyLibraryGrid(
|
|||||||
FastScrollLazyVerticalGrid(
|
FastScrollLazyVerticalGrid(
|
||||||
columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns),
|
columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentPadding = contentPadding + PaddingValues(12.dp),
|
contentPadding = contentPadding + PaddingValues(8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
package eu.kanade.presentation.library.components
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
import eu.kanade.domain.manga.model.MangaCover
|
import eu.kanade.domain.manga.model.MangaCover
|
||||||
|
import eu.kanade.presentation.components.MangaComfortableGridItem
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -44,76 +37,37 @@ fun LibraryComfortableGrid(
|
|||||||
items = items,
|
items = items,
|
||||||
contentType = { "library_comfortable_grid_item" },
|
contentType = { "library_comfortable_grid_item" },
|
||||||
) { libraryItem ->
|
) { libraryItem ->
|
||||||
LibraryComfortableGridItem(
|
val manga = libraryItem.libraryManga.manga
|
||||||
item = libraryItem,
|
MangaComfortableGridItem(
|
||||||
showDownloadBadge = showDownloadBadges,
|
|
||||||
showUnreadBadge = showUnreadBadges,
|
|
||||||
showLocalBadge = showLocalBadges,
|
|
||||||
showLanguageBadge = showLanguageBadges,
|
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
||||||
onClick = onClick,
|
title = manga.title,
|
||||||
onLongClick = onLongClick,
|
coverData = MangaCover(
|
||||||
)
|
mangaId = manga.id,
|
||||||
}
|
sourceId = manga.source,
|
||||||
}
|
isMangaFavorite = manga.favorite,
|
||||||
}
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
@Composable
|
|
||||||
fun LibraryComfortableGridItem(
|
|
||||||
item: LibraryItem,
|
|
||||||
showDownloadBadge: Boolean,
|
|
||||||
showUnreadBadge: Boolean,
|
|
||||||
showLocalBadge: Boolean,
|
|
||||||
showLanguageBadge: Boolean,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: (LibraryManga) -> Unit,
|
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
|
||||||
) {
|
|
||||||
val libraryManga = item.libraryManga
|
|
||||||
val manga = libraryManga.manga
|
|
||||||
LibraryGridItemSelectable(isSelected = isSelected) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = {
|
|
||||||
onClick(libraryManga)
|
|
||||||
},
|
|
||||||
onLongClick = {
|
|
||||||
onLongClick(libraryManga)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
) {
|
coverBadgeStart = {
|
||||||
LibraryGridCover(
|
DownloadsBadge(
|
||||||
mangaCover = MangaCover(
|
enabled = showDownloadBadges,
|
||||||
manga.id,
|
item = libraryItem,
|
||||||
manga.source,
|
|
||||||
manga.favorite,
|
|
||||||
manga.thumbnailUrl,
|
|
||||||
manga.coverLastModified,
|
|
||||||
),
|
|
||||||
item = item,
|
|
||||||
showDownloadBadge = showDownloadBadge,
|
|
||||||
showUnreadBadge = showUnreadBadge,
|
|
||||||
showLocalBadge = showLocalBadge,
|
|
||||||
showLanguageBadge = showLanguageBadge,
|
|
||||||
)
|
)
|
||||||
MangaGridComfortableText(
|
UnreadBadge(
|
||||||
text = manga.title,
|
enabled = showUnreadBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
coverBadgeEnd = {
|
||||||
|
LanguageBadge(
|
||||||
|
showLanguage = showLanguageBadges,
|
||||||
|
showLocal = showLocalBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaGridComfortableText(
|
|
||||||
text: String,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(4.dp),
|
|
||||||
text = text,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -1,35 +1,20 @@
|
|||||||
package eu.kanade.presentation.library.components
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.Shadow
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
|
import eu.kanade.domain.manga.model.MangaCover
|
||||||
|
import eu.kanade.presentation.components.MangaCompactGridItem
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryCompactGrid(
|
fun LibraryCompactGrid(
|
||||||
items: List<LibraryItem>,
|
items: List<LibraryItem>,
|
||||||
|
showTitle: Boolean,
|
||||||
showDownloadBadges: Boolean,
|
showDownloadBadges: Boolean,
|
||||||
showUnreadBadges: Boolean,
|
showUnreadBadges: Boolean,
|
||||||
showLocalBadges: Boolean,
|
showLocalBadges: Boolean,
|
||||||
@ -53,92 +38,37 @@ fun LibraryCompactGrid(
|
|||||||
items = items,
|
items = items,
|
||||||
contentType = { "library_compact_grid_item" },
|
contentType = { "library_compact_grid_item" },
|
||||||
) { libraryItem ->
|
) { libraryItem ->
|
||||||
LibraryCompactGridItem(
|
val manga = libraryItem.libraryManga.manga
|
||||||
item = libraryItem,
|
MangaCompactGridItem(
|
||||||
showDownloadBadge = showDownloadBadges,
|
|
||||||
showUnreadBadge = showUnreadBadges,
|
|
||||||
showLocalBadge = showLocalBadges,
|
|
||||||
showLanguageBadge = showLanguageBadges,
|
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
||||||
onClick = onClick,
|
title = manga.title.takeIf { showTitle },
|
||||||
onLongClick = onLongClick,
|
coverData = MangaCover(
|
||||||
|
mangaId = manga.id,
|
||||||
|
sourceId = manga.source,
|
||||||
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
|
),
|
||||||
|
coverBadgeStart = {
|
||||||
|
DownloadsBadge(
|
||||||
|
enabled = showDownloadBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
UnreadBadge(
|
||||||
|
enabled = showUnreadBadges,
|
||||||
|
item = libraryItem,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryCompactGridItem(
|
|
||||||
item: LibraryItem,
|
|
||||||
showDownloadBadge: Boolean,
|
|
||||||
showUnreadBadge: Boolean,
|
|
||||||
showLocalBadge: Boolean,
|
|
||||||
showLanguageBadge: Boolean,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: (LibraryManga) -> Unit,
|
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
|
||||||
) {
|
|
||||||
val libraryManga = item.libraryManga
|
|
||||||
val manga = libraryManga.manga
|
|
||||||
LibraryGridCover(
|
|
||||||
modifier = Modifier
|
|
||||||
.selectedOutline(isSelected)
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = {
|
|
||||||
onClick(libraryManga)
|
|
||||||
},
|
},
|
||||||
onLongClick = {
|
coverBadgeEnd = {
|
||||||
onLongClick(libraryManga)
|
LanguageBadge(
|
||||||
|
showLanguage = showLanguageBadges,
|
||||||
|
showLocal = showLocalBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
),
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
mangaCover = eu.kanade.domain.manga.model.MangaCover(
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
manga.id,
|
|
||||||
manga.source,
|
|
||||||
manga.favorite,
|
|
||||||
manga.thumbnailUrl,
|
|
||||||
manga.coverLastModified,
|
|
||||||
),
|
|
||||||
item = item,
|
|
||||||
showDownloadBadge = showDownloadBadge,
|
|
||||||
showUnreadBadge = showUnreadBadge,
|
|
||||||
showLocalBadge = showLocalBadge,
|
|
||||||
showLanguageBadge = showLanguageBadge,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp))
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
0f to Color.Transparent,
|
|
||||||
1f to Color(0xAA000000),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.fillMaxHeight(0.33f)
|
}
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.BottomCenter),
|
|
||||||
)
|
|
||||||
MangaGridCompactText(manga.title)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BoxScope.MangaGridCompactText(
|
|
||||||
text: String,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.align(Alignment.BottomStart),
|
|
||||||
color = Color.White,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.titleSmall.copy(
|
|
||||||
shadow = Shadow(
|
|
||||||
color = Color.Black,
|
|
||||||
blurRadius = 4f,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -15,12 +15,12 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
import eu.kanade.presentation.components.SwipeRefresh
|
import eu.kanade.presentation.components.SwipeRefresh
|
||||||
|
import eu.kanade.presentation.components.rememberPagerState
|
||||||
import eu.kanade.presentation.library.LibraryState
|
import eu.kanade.presentation.library.LibraryState
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -68,12 +68,13 @@ fun LibraryContent(
|
|||||||
|
|
||||||
if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) {
|
if (isLibraryEmpty.not() && showPageTabs && categories.size > 1) {
|
||||||
LibraryTabs(
|
LibraryTabs(
|
||||||
state = pagerState,
|
|
||||||
categories = categories,
|
categories = categories,
|
||||||
|
currentPageIndex = pagerState.currentPage,
|
||||||
showMangaCount = showMangaCount,
|
showMangaCount = showMangaCount,
|
||||||
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
|
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
|
||||||
isDownloadOnly = isDownloadOnly,
|
isDownloadOnly = isDownloadOnly,
|
||||||
isIncognitoMode = isIncognitoMode,
|
isIncognitoMode = isIncognitoMode,
|
||||||
|
onTabItemClick = { scope.launch { pagerState.animateScrollToPage(it) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
package eu.kanade.presentation.library.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.util.fastAny
|
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryCoverOnlyGrid(
|
|
||||||
items: List<LibraryItem>,
|
|
||||||
showDownloadBadges: Boolean,
|
|
||||||
showUnreadBadges: Boolean,
|
|
||||||
showLocalBadges: Boolean,
|
|
||||||
showLanguageBadges: Boolean,
|
|
||||||
columns: Int,
|
|
||||||
contentPadding: PaddingValues,
|
|
||||||
selection: List<LibraryManga>,
|
|
||||||
onClick: (LibraryManga) -> Unit,
|
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
|
||||||
searchQuery: String?,
|
|
||||||
onGlobalSearchClicked: () -> Unit,
|
|
||||||
) {
|
|
||||||
LazyLibraryGrid(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
columns = columns,
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
globalSearchItem(searchQuery, onGlobalSearchClicked)
|
|
||||||
|
|
||||||
items(
|
|
||||||
items = items,
|
|
||||||
contentType = { "library_only_cover_grid_item" },
|
|
||||||
) { libraryItem ->
|
|
||||||
LibraryCoverOnlyGridItem(
|
|
||||||
item = libraryItem,
|
|
||||||
showDownloadBadge = showDownloadBadges,
|
|
||||||
showUnreadBadge = showUnreadBadges,
|
|
||||||
showLocalBadge = showLocalBadges,
|
|
||||||
showLanguageBadge = showLanguageBadges,
|
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryCoverOnlyGridItem(
|
|
||||||
item: LibraryItem,
|
|
||||||
showDownloadBadge: Boolean,
|
|
||||||
showUnreadBadge: Boolean,
|
|
||||||
showLocalBadge: Boolean,
|
|
||||||
showLanguageBadge: Boolean,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: (LibraryManga) -> Unit,
|
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
|
||||||
) {
|
|
||||||
val libraryManga = item.libraryManga
|
|
||||||
val manga = libraryManga.manga
|
|
||||||
LibraryGridCover(
|
|
||||||
modifier = Modifier
|
|
||||||
.selectedOutline(isSelected)
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = {
|
|
||||||
onClick(libraryManga)
|
|
||||||
},
|
|
||||||
onLongClick = {
|
|
||||||
onLongClick(libraryManga)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
mangaCover = eu.kanade.domain.manga.model.MangaCover(
|
|
||||||
manga.id,
|
|
||||||
manga.source,
|
|
||||||
manga.favorite,
|
|
||||||
manga.thumbnailUrl,
|
|
||||||
manga.coverLastModified,
|
|
||||||
),
|
|
||||||
item = item,
|
|
||||||
showDownloadBadge = showDownloadBadge,
|
|
||||||
showUnreadBadge = showUnreadBadge,
|
|
||||||
showLocalBadge = showLocalBadge,
|
|
||||||
showLanguageBadge = showLanguageBadge,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package eu.kanade.presentation.library.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.presentation.components.BadgeGroup
|
|
||||||
import eu.kanade.presentation.components.MangaCover
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaGridCover(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
cover: @Composable BoxScope.() -> Unit = {},
|
|
||||||
badgesStart: (@Composable RowScope.() -> Unit)? = null,
|
|
||||||
badgesEnd: (@Composable RowScope.() -> Unit)? = null,
|
|
||||||
content: @Composable BoxScope.() -> Unit = {},
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(MangaCover.Book.ratio),
|
|
||||||
) {
|
|
||||||
cover()
|
|
||||||
content()
|
|
||||||
if (badgesStart != null) {
|
|
||||||
BadgeGroup(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.align(Alignment.TopStart),
|
|
||||||
content = badgesStart,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (badgesEnd != null) {
|
|
||||||
BadgeGroup(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.align(Alignment.TopEnd),
|
|
||||||
content = badgesEnd,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryGridCover(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
mangaCover: eu.kanade.domain.manga.model.MangaCover,
|
|
||||||
item: LibraryItem,
|
|
||||||
showDownloadBadge: Boolean,
|
|
||||||
showUnreadBadge: Boolean,
|
|
||||||
showLocalBadge: Boolean,
|
|
||||||
showLanguageBadge: Boolean,
|
|
||||||
content: @Composable BoxScope.() -> Unit = {},
|
|
||||||
) {
|
|
||||||
MangaGridCover(
|
|
||||||
modifier = modifier,
|
|
||||||
cover = {
|
|
||||||
MangaCover.Book(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
data = mangaCover,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badgesStart = {
|
|
||||||
DownloadsBadge(enabled = showDownloadBadge, item = item)
|
|
||||||
UnreadBadge(enabled = showUnreadBadge, item = item)
|
|
||||||
},
|
|
||||||
badgesEnd = {
|
|
||||||
LanguageBadge(showLanguage = showLanguageBadge, showLocal = showLocalBadge, item = item)
|
|
||||||
},
|
|
||||||
content = content,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package eu.kanade.presentation.library.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.composed
|
|
||||||
import androidx.compose.ui.draw.drawBehind
|
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
fun Modifier.selectedOutline(isSelected: Boolean) = composed {
|
|
||||||
val secondary = MaterialTheme.colorScheme.secondary
|
|
||||||
if (isSelected) {
|
|
||||||
drawBehind {
|
|
||||||
val additional = 24.dp.value
|
|
||||||
val offset = additional / 2
|
|
||||||
val height = size.height + additional
|
|
||||||
val width = size.width + additional
|
|
||||||
drawRoundRect(
|
|
||||||
color = secondary,
|
|
||||||
topLeft = Offset(-offset, -offset),
|
|
||||||
size = Size(width, height),
|
|
||||||
cornerRadius = CornerRadius(offset),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryGridItemSelectable(
|
|
||||||
isSelected: Boolean,
|
|
||||||
content: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
Box(Modifier.selectedOutline(isSelected)) {
|
|
||||||
CompositionLocalProvider(LocalContentColor provides if (isSelected) MaterialTheme.colorScheme.onSecondary else MaterialTheme.colorScheme.onBackground) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +1,22 @@
|
|||||||
package eu.kanade.presentation.library.components
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
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.ui.Alignment
|
|
||||||
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.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
import eu.kanade.domain.manga.model.MangaCover
|
import eu.kanade.domain.manga.model.MangaCover
|
||||||
import eu.kanade.presentation.components.BadgeGroup
|
|
||||||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.MangaCover.Square
|
import eu.kanade.presentation.components.MangaListItem
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.presentation.util.selectedBackground
|
|
||||||
import eu.kanade.presentation.util.verticalPadding
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
|
|
||||||
@ -47,11 +36,14 @@ fun LibraryList(
|
|||||||
) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding + PaddingValues(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
if (searchQuery.isNullOrEmpty().not()) {
|
if (searchQuery.isNullOrEmpty().not()) {
|
||||||
TextButton(onClick = onGlobalSearchClicked) {
|
TextButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onGlobalSearchClicked,
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.action_global_search_query, searchQuery!!),
|
text = stringResource(R.string.action_global_search_query, searchQuery!!),
|
||||||
modifier = Modifier.zIndex(99f),
|
modifier = Modifier.zIndex(99f),
|
||||||
@ -64,116 +56,25 @@ fun LibraryList(
|
|||||||
items = items,
|
items = items,
|
||||||
contentType = { "library_list_item" },
|
contentType = { "library_list_item" },
|
||||||
) { libraryItem ->
|
) { libraryItem ->
|
||||||
LibraryListItem(
|
val manga = libraryItem.libraryManga.manga
|
||||||
item = libraryItem,
|
MangaListItem(
|
||||||
showDownloadBadge = showDownloadBadges,
|
|
||||||
showUnreadBadge = showUnreadBadges,
|
|
||||||
showLocalBadge = showLocalBadges,
|
|
||||||
showLanguageBadge = showLanguageBadges,
|
|
||||||
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LibraryListItem(
|
|
||||||
item: LibraryItem,
|
|
||||||
showDownloadBadge: Boolean,
|
|
||||||
showUnreadBadge: Boolean,
|
|
||||||
showLocalBadge: Boolean,
|
|
||||||
showLanguageBadge: Boolean,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: (LibraryManga) -> Unit,
|
|
||||||
onLongClick: (LibraryManga) -> Unit,
|
|
||||||
) {
|
|
||||||
val libraryManga = item.libraryManga
|
|
||||||
val manga = libraryManga.manga
|
|
||||||
MangaListItem(
|
|
||||||
modifier = Modifier.selectedBackground(isSelected),
|
|
||||||
title = manga.title,
|
title = manga.title,
|
||||||
cover = MangaCover(
|
coverData = MangaCover(
|
||||||
manga.id,
|
mangaId = manga.id,
|
||||||
manga.source,
|
sourceId = manga.source,
|
||||||
manga.favorite,
|
isMangaFavorite = manga.favorite,
|
||||||
manga.thumbnailUrl,
|
url = manga.thumbnailUrl,
|
||||||
manga.coverLastModified,
|
lastModified = manga.coverLastModified,
|
||||||
),
|
),
|
||||||
onClick = { onClick(libraryManga) },
|
badge = {
|
||||||
onLongClick = { onLongClick(libraryManga) },
|
DownloadsBadge(enabled = showDownloadBadges, item = libraryItem)
|
||||||
) {
|
UnreadBadge(enabled = showUnreadBadges, item = libraryItem)
|
||||||
DownloadsBadge(enabled = showDownloadBadge, item = item)
|
LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem)
|
||||||
UnreadBadge(enabled = showUnreadBadge, item = item)
|
},
|
||||||
LanguageBadge(showLanguage = showLanguageBadge, showLocal = showLocalBadge, item = item)
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaListItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
title: String,
|
|
||||||
cover: MangaCover,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
onLongClick: () -> Unit = onClick,
|
|
||||||
badges: @Composable RowScope.() -> Unit,
|
|
||||||
) {
|
|
||||||
MangaListItem(
|
|
||||||
modifier = modifier,
|
|
||||||
coverContent = {
|
|
||||||
Square(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = verticalPadding)
|
|
||||||
.fillMaxHeight(),
|
|
||||||
data = cover,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badges = badges,
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
content = {
|
|
||||||
MangaListItemContent(title)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MangaListItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
coverContent: @Composable RowScope.() -> Unit,
|
|
||||||
badges: @Composable RowScope.() -> Unit,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
onLongClick: () -> Unit,
|
|
||||||
content: @Composable RowScope.() -> Unit,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.height(56.dp)
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
)
|
|
||||||
.padding(horizontal = horizontalPadding),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
coverContent()
|
|
||||||
content()
|
|
||||||
BadgeGroup(content = badges)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RowScope.MangaListItemContent(
|
|
||||||
text: String,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = horizontalPadding)
|
|
||||||
.weight(1f),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -10,11 +10,11 @@ import androidx.compose.runtime.remember
|
|||||||
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.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
|
||||||
import com.google.accompanist.pager.PagerState
|
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import eu.kanade.domain.library.model.LibraryDisplayMode
|
import eu.kanade.domain.library.model.LibraryDisplayMode
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
|
import eu.kanade.presentation.components.HorizontalPager
|
||||||
|
import eu.kanade.presentation.components.PagerState
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
import eu.kanade.tachiyomi.ui.library.LibraryItem
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -72,9 +72,10 @@ fun LibraryPager(
|
|||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LibraryDisplayMode.CompactGrid -> {
|
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
|
||||||
LibraryCompactGrid(
|
LibraryCompactGrid(
|
||||||
items = library,
|
items = library,
|
||||||
|
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
|
||||||
showDownloadBadges = showDownloadBadges,
|
showDownloadBadges = showDownloadBadges,
|
||||||
showUnreadBadges = showUnreadBadges,
|
showUnreadBadges = showUnreadBadges,
|
||||||
showLocalBadges = showLocalBadges,
|
showLocalBadges = showLocalBadges,
|
||||||
@ -104,22 +105,6 @@ fun LibraryPager(
|
|||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
onGlobalSearchClicked = onGlobalSearchClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LibraryDisplayMode.CoverOnlyGrid -> {
|
|
||||||
LibraryCoverOnlyGrid(
|
|
||||||
items = library,
|
|
||||||
showDownloadBadges = showDownloadBadges,
|
|
||||||
showUnreadBadges = showUnreadBadges,
|
|
||||||
showLocalBadges = showLocalBadges,
|
|
||||||
showLanguageBadges = showLanguageBadges,
|
|
||||||
columns = columns,
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
selection = selectedManga,
|
|
||||||
onClick = onClickManga,
|
|
||||||
onLongClick = onLongClickManga,
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
onGlobalSearchClicked = onGlobalSearchClicked,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,56 +1,54 @@
|
|||||||
package eu.kanade.presentation.library.components
|
package eu.kanade.presentation.library.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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
|
||||||
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.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.pager.PagerState
|
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import eu.kanade.presentation.components.AppStateBanners
|
import eu.kanade.presentation.components.AppStateBanners
|
||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.TabIndicator
|
import eu.kanade.presentation.components.TabIndicator
|
||||||
import eu.kanade.presentation.components.TabText
|
import eu.kanade.presentation.components.TabText
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryTabs(
|
fun LibraryTabs(
|
||||||
state: PagerState,
|
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
|
currentPageIndex: Int,
|
||||||
showMangaCount: Boolean,
|
showMangaCount: Boolean,
|
||||||
isDownloadOnly: Boolean,
|
isDownloadOnly: Boolean,
|
||||||
isIncognitoMode: Boolean,
|
isIncognitoMode: Boolean,
|
||||||
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
|
getNumberOfMangaForCategory: @Composable (Long) -> State<Int?>,
|
||||||
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
ScrollableTabRow(
|
ScrollableTabRow(
|
||||||
selectedTabIndex = state.currentPage,
|
selectedTabIndex = currentPageIndex,
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
indicator = { TabIndicator(it[state.currentPage]) },
|
indicator = { TabIndicator(it[currentPageIndex]) },
|
||||||
// TODO: use default when width is fixed upstream
|
// TODO: use default when width is fixed upstream
|
||||||
// https://issuetracker.google.com/issues/242879624
|
// https://issuetracker.google.com/issues/242879624
|
||||||
divider = {},
|
divider = {},
|
||||||
) {
|
) {
|
||||||
categories.forEachIndexed { index, category ->
|
categories.forEachIndexed { index, category ->
|
||||||
val count by if (showMangaCount) {
|
Tab(
|
||||||
|
selected = currentPageIndex == index,
|
||||||
|
onClick = { onTabItemClick(index) },
|
||||||
|
text = {
|
||||||
|
TabText(
|
||||||
|
text = category.visualName,
|
||||||
|
badgeCount = if (showMangaCount) {
|
||||||
getNumberOfMangaForCategory(category.id)
|
getNumberOfMangaForCategory(category.id)
|
||||||
} else {
|
} else {
|
||||||
remember { mutableStateOf<Int?>(null) }
|
null
|
||||||
}
|
}?.value,
|
||||||
Tab(
|
)
|
||||||
selected = state.currentPage == index,
|
|
||||||
onClick = { scope.launch { state.animateScrollToPage(index) } },
|
|
||||||
text = {
|
|
||||||
TabText(category.visualName, count, state.currentPage == index)
|
|
||||||
},
|
},
|
||||||
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,11 @@ package eu.kanade.presentation.library.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
import androidx.compose.material.icons.outlined.FlipToBack
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
|
||||||
import androidx.compose.material.icons.outlined.Search
|
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
@ -19,13 +16,11 @@ import androidx.compose.material3.TopAppBarScrollBehavior
|
|||||||
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.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.OverflowMenu
|
||||||
import eu.kanade.presentation.components.Pill
|
import eu.kanade.presentation.components.Pill
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.library.LibraryState
|
import eu.kanade.presentation.library.LibraryState
|
||||||
@ -43,6 +38,7 @@ fun LibraryToolbar(
|
|||||||
onClickInvertSelection: () -> Unit,
|
onClickInvertSelection: () -> Unit,
|
||||||
onClickFilter: () -> Unit,
|
onClickFilter: () -> Unit,
|
||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
|
onClickOpenRandomManga: () -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior?,
|
scrollBehavior: TopAppBarScrollBehavior?,
|
||||||
) = when {
|
) = when {
|
||||||
state.selectionMode -> LibrarySelectionToolbar(
|
state.selectionMode -> LibrarySelectionToolbar(
|
||||||
@ -53,38 +49,16 @@ fun LibraryToolbar(
|
|||||||
onClickSelectAll = onClickSelectAll,
|
onClickSelectAll = onClickSelectAll,
|
||||||
onClickInvertSelection = onClickInvertSelection,
|
onClickInvertSelection = onClickInvertSelection,
|
||||||
)
|
)
|
||||||
state.searchQuery != null -> {
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
|
|
||||||
SearchToolbar(
|
|
||||||
searchQuery = state.searchQuery!!,
|
|
||||||
onChangeSearchQuery = { state.searchQuery = it },
|
|
||||||
onClickCloseSearch = { state.searchQuery = null },
|
|
||||||
onClickResetSearch = { state.searchQuery = "" },
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
incognitoMode = incognitoMode,
|
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
|
||||||
placeholderText = stringResource(R.string.action_search_hint),
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(
|
|
||||||
imeAction = ImeAction.Search,
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
keyboardController?.hide()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> LibraryRegularToolbar(
|
else -> LibraryRegularToolbar(
|
||||||
title = title,
|
title = title,
|
||||||
hasFilters = state.hasActiveFilters,
|
hasFilters = state.hasActiveFilters,
|
||||||
incognitoMode = incognitoMode,
|
incognitoMode = incognitoMode,
|
||||||
downloadedOnlyMode = downloadedOnlyMode,
|
downloadedOnlyMode = downloadedOnlyMode,
|
||||||
onClickSearch = { state.searchQuery = "" },
|
searchQuery = state.searchQuery,
|
||||||
|
onChangeSearchQuery = { state.searchQuery = it },
|
||||||
onClickFilter = onClickFilter,
|
onClickFilter = onClickFilter,
|
||||||
onClickRefresh = onClickRefresh,
|
onClickRefresh = onClickRefresh,
|
||||||
|
onClickOpenRandomManga = onClickOpenRandomManga,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -95,14 +69,15 @@ fun LibraryRegularToolbar(
|
|||||||
hasFilters: Boolean,
|
hasFilters: Boolean,
|
||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
onClickSearch: () -> Unit,
|
searchQuery: String?,
|
||||||
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onClickFilter: () -> Unit,
|
onClickFilter: () -> Unit,
|
||||||
onClickRefresh: () -> Unit,
|
onClickRefresh: () -> Unit,
|
||||||
|
onClickOpenRandomManga: () -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior?,
|
scrollBehavior: TopAppBarScrollBehavior?,
|
||||||
) {
|
) {
|
||||||
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
|
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
|
||||||
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
SearchToolbar(
|
||||||
AppBar(
|
|
||||||
titleContent = {
|
titleContent = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(
|
||||||
@ -120,15 +95,29 @@ fun LibraryRegularToolbar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onClickSearch) {
|
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
|
||||||
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
|
|
||||||
}
|
|
||||||
IconButton(onClick = onClickFilter) {
|
IconButton(onClick = onClickFilter) {
|
||||||
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
|
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onClickRefresh) {
|
|
||||||
Icon(Icons.Outlined.Refresh, contentDescription = stringResource(R.string.pref_category_library_update))
|
OverflowMenu { closeMenu ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.pref_category_library_update)) },
|
||||||
|
onClick = {
|
||||||
|
onClickRefresh()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = stringResource(R.string.action_open_random_manga)) },
|
||||||
|
onClick = {
|
||||||
|
onClickOpenRandomManga()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
incognitoMode = incognitoMode,
|
incognitoMode = incognitoMode,
|
||||||
@ -150,10 +139,10 @@ fun LibrarySelectionToolbar(
|
|||||||
titleContent = { Text(text = "${state.selection.size}") },
|
titleContent = { Text(text = "${state.selection.size}") },
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onClickSelectAll) {
|
IconButton(onClick = onClickSelectAll) {
|
||||||
Icon(Icons.Outlined.SelectAll, contentDescription = "search")
|
Icon(Icons.Outlined.SelectAll, contentDescription = stringResource(R.string.action_select_all))
|
||||||
}
|
}
|
||||||
IconButton(onClick = onClickInvertSelection) {
|
IconButton(onClick = onClickInvertSelection) {
|
||||||
Icon(Icons.Outlined.FlipToBack, contentDescription = "invert")
|
Icon(Icons.Outlined.FlipToBack, contentDescription = stringResource(R.string.action_select_inverse))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActionMode = true,
|
isActionMode = true,
|
||||||
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.calculateEndPadding
|
|||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
@ -35,6 +34,7 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
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.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.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
@ -43,6 +43,9 @@ import androidx.compose.ui.platform.LocalDensity
|
|||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.util.fastAll
|
||||||
|
import androidx.compose.ui.util.fastAny
|
||||||
|
import androidx.compose.ui.util.fastMap
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
@ -204,7 +207,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
val chapters = remember(state) { state.processedChapters.toList() }
|
val chapters = remember(state) { state.processedChapters.toList() }
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
if (chapters.any { it.selected }) {
|
if (chapters.fastAny { it.selected }) {
|
||||||
onAllChapterSelected(false)
|
onAllChapterSelected(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
onBackClicked()
|
||||||
@ -213,8 +216,6 @@ private fun MangaScreenSmallImpl(
|
|||||||
BackHandler(onBack = internalOnBackPressed)
|
BackHandler(onBack = internalOnBackPressed)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
|
||||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal).asPaddingValues()),
|
|
||||||
topBar = {
|
topBar = {
|
||||||
val firstVisibleItemIndex by remember {
|
val firstVisibleItemIndex by remember {
|
||||||
derivedStateOf { chapterListState.firstVisibleItemIndex }
|
derivedStateOf { chapterListState.firstVisibleItemIndex }
|
||||||
@ -260,24 +261,22 @@ private fun MangaScreenSmallImpl(
|
|||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = chapters.any { !it.chapter.read } && chapters.none { it.selected },
|
visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected },
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
val id = if (chapters.any { it.chapter.read }) {
|
val id = if (chapters.fastAny { it.chapter.read }) {
|
||||||
R.string.action_resume
|
R.string.action_resume
|
||||||
} else {
|
} else {
|
||||||
R.string.action_start
|
R.string.action_start
|
||||||
}
|
}
|
||||||
Text(text = stringResource(id))
|
Text(text = stringResource(id))
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||||
modifier = Modifier
|
|
||||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -287,17 +286,21 @@ private fun MangaScreenSmallImpl(
|
|||||||
SwipeRefresh(
|
SwipeRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = chapters.none { it.selected },
|
enabled = chapters.fastAll { !it.selected },
|
||||||
indicatorPadding = contentPadding,
|
indicatorPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
VerticalFastScroller(
|
VerticalFastScroller(
|
||||||
listState = chapterListState,
|
listState = chapterListState,
|
||||||
topContentPadding = topPadding,
|
topContentPadding = topPadding,
|
||||||
|
endContentPadding = contentPadding.calculateEndPadding(layoutDirection),
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxHeight(),
|
modifier = Modifier.fillMaxHeight(),
|
||||||
state = chapterListState,
|
state = chapterListState,
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
|
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||||
|
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||||
bottom = contentPadding.calculateBottomPadding(),
|
bottom = contentPadding.calculateBottomPadding(),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
@ -351,6 +354,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||||
) {
|
) {
|
||||||
ChapterHeader(
|
ChapterHeader(
|
||||||
|
enabled = chapters.fastAll { !it.selected },
|
||||||
chapterCount = chapters.size,
|
chapterCount = chapters.size,
|
||||||
onClick = onFilterClicked,
|
onClick = onFilterClicked,
|
||||||
)
|
)
|
||||||
@ -410,11 +414,11 @@ fun MangaScreenLargeImpl(
|
|||||||
val chapters = remember(state) { state.processedChapters.toList() }
|
val chapters = remember(state) { state.processedChapters.toList() }
|
||||||
|
|
||||||
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||||
val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(0) }
|
var topBarHeight by remember { mutableStateOf(0) }
|
||||||
SwipeRefresh(
|
SwipeRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = chapters.none { it.selected },
|
enabled = chapters.fastAll { !it.selected },
|
||||||
indicatorPadding = PaddingValues(
|
indicatorPadding = PaddingValues(
|
||||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||||
top = with(density) { topBarHeight.toDp() },
|
top = with(density) { topBarHeight.toDp() },
|
||||||
@ -424,7 +428,7 @@ fun MangaScreenLargeImpl(
|
|||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
if (chapters.any { it.selected }) {
|
if (chapters.fastAny { it.selected }) {
|
||||||
onAllChapterSelected(false)
|
onAllChapterSelected(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
onBackClicked()
|
||||||
@ -433,12 +437,11 @@ fun MangaScreenLargeImpl(
|
|||||||
BackHandler(onBack = internalOnBackPressed)
|
BackHandler(onBack = internalOnBackPressed)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.padding(insetPadding),
|
|
||||||
topBar = {
|
topBar = {
|
||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
modifier = Modifier.onSizeChanged { onTopBarHeightChanged(it.height) },
|
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { if (chapters.any { it.selected }) 1f else 0f },
|
titleAlphaProvider = { if (chapters.fastAny { it.selected }) 1f else 0f },
|
||||||
backgroundAlphaProvider = { 1f },
|
backgroundAlphaProvider = { 1f },
|
||||||
hasFilters = state.manga.chaptersFiltered(),
|
hasFilters = state.manga.chaptersFiltered(),
|
||||||
incognitoMode = state.isIncognitoMode,
|
incognitoMode = state.isIncognitoMode,
|
||||||
@ -473,33 +476,36 @@ fun MangaScreenLargeImpl(
|
|||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = chapters.any { !it.chapter.read } && chapters.none { it.selected },
|
visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected },
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
val id = if (chapters.any { it.chapter.read }) {
|
val id = if (chapters.fastAny { it.chapter.read }) {
|
||||||
R.string.action_resume
|
R.string.action_resume
|
||||||
} else {
|
} else {
|
||||||
R.string.action_start
|
R.string.action_start
|
||||||
}
|
}
|
||||||
Text(text = stringResource(id))
|
Text(text = stringResource(id))
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||||
modifier = Modifier
|
|
||||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
TwoPanelBox(
|
TwoPanelBox(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||||
|
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||||
|
),
|
||||||
startContent = {
|
startContent = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = contentPadding.calculateBottomPadding()),
|
||||||
) {
|
) {
|
||||||
MangaInfoBox(
|
MangaInfoBox(
|
||||||
isTabletUi = true,
|
isTabletUi = true,
|
||||||
@ -548,6 +554,7 @@ fun MangaScreenLargeImpl(
|
|||||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||||
) {
|
) {
|
||||||
ChapterHeader(
|
ChapterHeader(
|
||||||
|
enabled = chapters.fastAll { !it.selected },
|
||||||
chapterCount = chapters.size,
|
chapterCount = chapters.size,
|
||||||
onClick = onFilterButtonClicked,
|
onClick = onFilterButtonClicked,
|
||||||
)
|
)
|
||||||
@ -582,29 +589,29 @@ private fun SharedMangaBottomActionMenu(
|
|||||||
visible = selected.isNotEmpty(),
|
visible = selected.isNotEmpty(),
|
||||||
modifier = modifier.fillMaxWidth(fillFraction),
|
modifier = modifier.fillMaxWidth(fillFraction),
|
||||||
onBookmarkClicked = {
|
onBookmarkClicked = {
|
||||||
onMultiBookmarkClicked.invoke(selected.map { it.chapter }, true)
|
onMultiBookmarkClicked.invoke(selected.fastMap { it.chapter }, true)
|
||||||
}.takeIf { selected.any { !it.chapter.bookmark } },
|
}.takeIf { selected.fastAny { !it.chapter.bookmark } },
|
||||||
onRemoveBookmarkClicked = {
|
onRemoveBookmarkClicked = {
|
||||||
onMultiBookmarkClicked.invoke(selected.map { it.chapter }, false)
|
onMultiBookmarkClicked.invoke(selected.fastMap { it.chapter }, false)
|
||||||
}.takeIf { selected.all { it.chapter.bookmark } },
|
}.takeIf { selected.fastAll { it.chapter.bookmark } },
|
||||||
onMarkAsReadClicked = {
|
onMarkAsReadClicked = {
|
||||||
onMultiMarkAsReadClicked(selected.map { it.chapter }, true)
|
onMultiMarkAsReadClicked(selected.fastMap { it.chapter }, true)
|
||||||
}.takeIf { selected.any { !it.chapter.read } },
|
}.takeIf { selected.fastAny { !it.chapter.read } },
|
||||||
onMarkAsUnreadClicked = {
|
onMarkAsUnreadClicked = {
|
||||||
onMultiMarkAsReadClicked(selected.map { it.chapter }, false)
|
onMultiMarkAsReadClicked(selected.fastMap { it.chapter }, false)
|
||||||
}.takeIf { selected.any { it.chapter.read || it.chapter.lastPageRead > 0L } },
|
}.takeIf { selected.fastAny { it.chapter.read || it.chapter.lastPageRead > 0L } },
|
||||||
onMarkPreviousAsReadClicked = {
|
onMarkPreviousAsReadClicked = {
|
||||||
onMarkPreviousAsReadClicked(selected[0].chapter)
|
onMarkPreviousAsReadClicked(selected[0].chapter)
|
||||||
}.takeIf { selected.size == 1 },
|
}.takeIf { selected.size == 1 },
|
||||||
onDownloadClicked = {
|
onDownloadClicked = {
|
||||||
onDownloadChapter!!(selected.toList(), ChapterDownloadAction.START)
|
onDownloadChapter!!(selected.toList(), ChapterDownloadAction.START)
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
onDownloadChapter != null && selected.any { it.downloadState != Download.State.DOWNLOADED }
|
onDownloadChapter != null && selected.fastAny { it.downloadState != Download.State.DOWNLOADED }
|
||||||
},
|
},
|
||||||
onDeleteClicked = {
|
onDeleteClicked = {
|
||||||
onMultiDeleteClicked(selected.map { it.chapter })
|
onMultiDeleteClicked(selected.fastMap { it.chapter })
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
onDownloadChapter != null && selected.any { it.downloadState == Download.State.DOWNLOADED }
|
onDownloadChapter != null && selected.fastAny { it.downloadState == Download.State.DOWNLOADED }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -629,6 +636,7 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
read = chapterItem.chapter.read,
|
read = chapterItem.chapter.read,
|
||||||
bookmark = chapterItem.chapter.bookmark,
|
bookmark = chapterItem.chapter.bookmark,
|
||||||
selected = chapterItem.selected,
|
selected = chapterItem.selected,
|
||||||
|
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
|
||||||
downloadStateProvider = { chapterItem.downloadState },
|
downloadStateProvider = { chapterItem.downloadState },
|
||||||
downloadProgressProvider = { chapterItem.downloadProgress },
|
downloadProgressProvider = { chapterItem.downloadProgress },
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
@ -660,7 +668,7 @@ private fun onChapterItemClick(
|
|||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
chapterItem.selected -> onToggleSelection(false)
|
chapterItem.selected -> onToggleSelection(false)
|
||||||
chapters.any { it.selected } -> onToggleSelection(true)
|
chapters.fastAny { it.selected } -> onToggleSelection(true)
|
||||||
else -> onChapterClicked(chapterItem.chapter)
|
else -> onChapterClicked(chapterItem.chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,17 @@ import eu.kanade.tachiyomi.R
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterHeader(
|
fun ChapterHeader(
|
||||||
|
enabled: Boolean,
|
||||||
chapterCount: Int?,
|
chapterCount: Int?,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.clickable(
|
||||||
|
enabled = enabled,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -37,7 +37,7 @@ fun DownloadCustomAmountDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -62,13 +62,13 @@ fun DownloadCustomAmountDialog(
|
|||||||
onClick = { setAmount(amount - 10) },
|
onClick = { setAmount(amount - 10) },
|
||||||
enabled = amount > 0,
|
enabled = amount > 0,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.KeyboardDoubleArrowLeft, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.KeyboardDoubleArrowLeft, contentDescription = "-10")
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { setAmount(amount - 1) },
|
onClick = { setAmount(amount - 1) },
|
||||||
enabled = amount > 0,
|
enabled = amount > 0,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.ChevronLeft, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.ChevronLeft, contentDescription = "-1")
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
@ -81,13 +81,13 @@ fun DownloadCustomAmountDialog(
|
|||||||
onClick = { setAmount(amount + 1) },
|
onClick = { setAmount(amount + 1) },
|
||||||
enabled = amount < maxAmount,
|
enabled = amount < maxAmount,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.ChevronRight, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.ChevronRight, contentDescription = "+1")
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { setAmount(amount + 10) },
|
onClick = { setAmount(amount + 10) },
|
||||||
enabled = amount < maxAmount,
|
enabled = amount < maxAmount,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.KeyboardDoubleArrowRight, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.KeyboardDoubleArrowRight, contentDescription = "+10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -23,7 +22,6 @@ 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.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@ -32,6 +30,8 @@ import androidx.compose.ui.unit.sp
|
|||||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.components.ChapterDownloadIndicator
|
import eu.kanade.presentation.components.ChapterDownloadIndicator
|
||||||
import eu.kanade.presentation.util.ReadItemAlpha
|
import eu.kanade.presentation.util.ReadItemAlpha
|
||||||
|
import eu.kanade.presentation.util.SecondaryItemAlpha
|
||||||
|
import eu.kanade.presentation.util.selectedBackground
|
||||||
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
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ fun MangaChapterListItem(
|
|||||||
read: Boolean,
|
read: Boolean,
|
||||||
bookmark: Boolean,
|
bookmark: Boolean,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
|
downloadIndicatorEnabled: Boolean,
|
||||||
downloadStateProvider: () -> Download.State,
|
downloadStateProvider: () -> Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
@ -53,7 +54,7 @@ fun MangaChapterListItem(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
|
.selectedBackground(selected)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
@ -67,12 +68,13 @@ fun MangaChapterListItem(
|
|||||||
MaterialTheme.colorScheme.onSurface
|
MaterialTheme.colorScheme.onSurface
|
||||||
}
|
}
|
||||||
val textAlpha = remember(read) { if (read) ReadItemAlpha else 1f }
|
val textAlpha = remember(read) { if (read) ReadItemAlpha else 1f }
|
||||||
|
val textSubtitleAlpha = remember(read) { if (read) ReadItemAlpha else SecondaryItemAlpha }
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
var textHeight by remember { mutableStateOf(0) }
|
var textHeight by remember { mutableStateOf(0) }
|
||||||
if (bookmark) {
|
if (bookmark) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bookmark,
|
imageVector = Icons.Filled.Bookmark,
|
||||||
contentDescription = stringResource(R.string.action_filter_bookmarked),
|
contentDescription = stringResource(R.string.action_filter_bookmarked),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
||||||
@ -91,7 +93,7 @@ fun MangaChapterListItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Row(modifier = Modifier.alpha(textAlpha)) {
|
Row(modifier = Modifier.alpha(textSubtitleAlpha)) {
|
||||||
ProvideTextStyle(
|
ProvideTextStyle(
|
||||||
value = MaterialTheme.typography.bodyMedium
|
value = MaterialTheme.typography.bodyMedium
|
||||||
.copy(color = textColor, fontSize = 12.sp),
|
.copy(color = textColor, fontSize = 12.sp),
|
||||||
@ -127,6 +129,7 @@ fun MangaChapterListItem(
|
|||||||
// Download view
|
// Download view
|
||||||
if (onDownloadClick != null) {
|
if (onDownloadClick != null) {
|
||||||
ChapterDownloadIndicator(
|
ChapterDownloadIndicator(
|
||||||
|
enabled = downloadIndicatorEnabled,
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
downloadStateProvider = downloadStateProvider,
|
downloadStateProvider = downloadStateProvider,
|
||||||
downloadProgressProvider = downloadProgressProvider,
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
|
@ -14,7 +14,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
import androidx.compose.material.icons.outlined.Save
|
import androidx.compose.material.icons.outlined.Save
|
||||||
import androidx.compose.material.icons.outlined.Share
|
import androidx.compose.material.icons.outlined.Share
|
||||||
@ -24,11 +24,14 @@ 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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
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.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
@ -63,7 +66,7 @@ fun MangaCoverDialog(
|
|||||||
) {
|
) {
|
||||||
IconButton(onClick = onDismissRequest) {
|
IconButton(onClick = onDismissRequest) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_close),
|
contentDescription = stringResource(R.string.action_close),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -82,9 +85,15 @@ fun MangaCoverDialog(
|
|||||||
}
|
}
|
||||||
if (onEditClick != null) {
|
if (onEditClick != null) {
|
||||||
Box {
|
Box {
|
||||||
val (expanded, onExpand) = remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { if (isCustomCover) onExpand(true) else onEditClick(EditCoverAction.EDIT) },
|
onClick = {
|
||||||
|
if (isCustomCover) {
|
||||||
|
expanded = true
|
||||||
|
} else {
|
||||||
|
onEditClick(EditCoverAction.EDIT)
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Edit,
|
imageVector = Icons.Outlined.Edit,
|
||||||
@ -93,20 +102,21 @@ fun MangaCoverDialog(
|
|||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onDismissRequest = { onExpand(false) },
|
onDismissRequest = { expanded = false },
|
||||||
|
offset = DpOffset(8.dp, 0.dp),
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_edit)) },
|
text = { Text(text = stringResource(R.string.action_edit)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onEditClick(EditCoverAction.EDIT)
|
onEditClick(EditCoverAction.EDIT)
|
||||||
onExpand(false)
|
expanded = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_delete)) },
|
text = { Text(text = stringResource(R.string.action_delete)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onEditClick(EditCoverAction.DELETE)
|
onEditClick(EditCoverAction.DELETE)
|
||||||
onExpand(false)
|
expanded = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ fun DeleteChaptersDialog(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -23,18 +23,18 @@ import androidx.compose.foundation.lazy.LazyRow
|
|||||||
import androidx.compose.foundation.lazy.items
|
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.AttachMoney
|
|
||||||
import androidx.compose.material.icons.filled.Block
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.Done
|
|
||||||
import androidx.compose.material.icons.filled.DoneAll
|
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.FavoriteBorder
|
|
||||||
import androidx.compose.material.icons.filled.Pause
|
|
||||||
import androidx.compose.material.icons.filled.Public
|
|
||||||
import androidx.compose.material.icons.filled.Schedule
|
|
||||||
import androidx.compose.material.icons.filled.Sync
|
|
||||||
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.Block
|
||||||
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.Done
|
||||||
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
|
import androidx.compose.material.icons.outlined.FavoriteBorder
|
||||||
|
import androidx.compose.material.icons.outlined.Pause
|
||||||
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.material.icons.outlined.Schedule
|
||||||
|
import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
|
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
|
||||||
@ -173,7 +173,7 @@ fun MangaActionRow(
|
|||||||
} else {
|
} else {
|
||||||
stringResource(R.string.add_to_library)
|
stringResource(R.string.add_to_library)
|
||||||
},
|
},
|
||||||
icon = if (favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
|
icon = if (favorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
|
||||||
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||||
onClick = onAddToLibraryClicked,
|
onClick = onAddToLibraryClicked,
|
||||||
onLongClick = onEditCategory,
|
onLongClick = onEditCategory,
|
||||||
@ -185,7 +185,7 @@ fun MangaActionRow(
|
|||||||
} else {
|
} else {
|
||||||
pluralStringResource(id = R.plurals.num_trackers, count = trackingCount, trackingCount)
|
pluralStringResource(id = R.plurals.num_trackers, count = trackingCount, trackingCount)
|
||||||
},
|
},
|
||||||
icon = if (trackingCount == 0) Icons.Default.Sync else Icons.Default.Done,
|
icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
|
||||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||||
onClick = onTrackingClicked,
|
onClick = onTrackingClicked,
|
||||||
)
|
)
|
||||||
@ -193,7 +193,7 @@ fun MangaActionRow(
|
|||||||
if (onWebViewClicked != null) {
|
if (onWebViewClicked != null) {
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = stringResource(R.string.action_web_view),
|
title = stringResource(R.string.action_web_view),
|
||||||
icon = Icons.Default.Public,
|
icon = Icons.Outlined.Public,
|
||||||
color = defaultActionButtonColor,
|
color = defaultActionButtonColor,
|
||||||
onClick = onWebViewClicked,
|
onClick = onWebViewClicked,
|
||||||
)
|
)
|
||||||
@ -345,13 +345,13 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = when (status) {
|
imageVector = when (status) {
|
||||||
SManga.ONGOING.toLong() -> Icons.Default.Schedule
|
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
||||||
SManga.COMPLETED.toLong() -> Icons.Default.DoneAll
|
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
||||||
SManga.LICENSED.toLong() -> Icons.Default.AttachMoney
|
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Default.Done
|
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
||||||
SManga.CANCELLED.toLong() -> Icons.Default.Close
|
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
||||||
SManga.ON_HIATUS.toLong() -> Icons.Default.Pause
|
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
||||||
else -> Icons.Default.Block
|
else -> Icons.Outlined.Block
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -375,7 +375,7 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
DotSeparatorText()
|
DotSeparatorText()
|
||||||
if (isStubSource) {
|
if (isStubSource) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Warning,
|
imageVector = Icons.Filled.Warning,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 4.dp)
|
.padding(end = 4.dp)
|
||||||
@ -478,13 +478,13 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = when (status) {
|
imageVector = when (status) {
|
||||||
SManga.ONGOING.toLong() -> Icons.Default.Schedule
|
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
||||||
SManga.COMPLETED.toLong() -> Icons.Default.DoneAll
|
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
||||||
SManga.LICENSED.toLong() -> Icons.Default.AttachMoney
|
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Default.Done
|
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
||||||
SManga.CANCELLED.toLong() -> Icons.Default.Close
|
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
||||||
SManga.ON_HIATUS.toLong() -> Icons.Default.Pause
|
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
||||||
else -> Icons.Default.Block
|
else -> Icons.Outlined.Block
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -508,7 +508,7 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
DotSeparatorText()
|
DotSeparatorText()
|
||||||
if (isStubSource) {
|
if (isStubSource) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Warning,
|
imageVector = Icons.Filled.Warning,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 4.dp)
|
.padding(end = 4.dp)
|
||||||
|
@ -2,16 +2,13 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.statusBars
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material.icons.filled.FlipToBack
|
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material.icons.filled.SelectAll
|
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.FilterList
|
import androidx.compose.material.icons.outlined.FilterList
|
||||||
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@ -30,7 +27,8 @@ import androidx.compose.ui.res.stringResource
|
|||||||
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.components.AppStateBanners
|
import eu.kanade.presentation.components.AppStateBanners
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||||
|
import eu.kanade.presentation.components.OverflowMenu
|
||||||
import eu.kanade.presentation.manga.DownloadAction
|
import eu.kanade.presentation.manga.DownloadAction
|
||||||
import eu.kanade.presentation.theme.active
|
import eu.kanade.presentation.theme.active
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -71,7 +69,7 @@ fun MangaToolbar(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBackClicked) {
|
IconButton(onClick = onBackClicked) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isActionMode) Icons.Default.Close else Icons.Default.ArrowBack,
|
imageVector = if (isActionMode) Icons.Outlined.Close else Icons.Outlined.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -80,13 +78,13 @@ fun MangaToolbar(
|
|||||||
if (isActionMode) {
|
if (isActionMode) {
|
||||||
IconButton(onClick = onSelectAll) {
|
IconButton(onClick = onSelectAll) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.SelectAll,
|
imageVector = Icons.Outlined.SelectAll,
|
||||||
contentDescription = stringResource(R.string.action_select_all),
|
contentDescription = stringResource(R.string.action_select_all),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onInvertSelection) {
|
IconButton(onClick = onInvertSelection) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.FlipToBack,
|
imageVector = Icons.Outlined.FlipToBack,
|
||||||
contentDescription = stringResource(R.string.action_select_inverse),
|
contentDescription = stringResource(R.string.action_select_inverse),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -101,53 +99,11 @@ fun MangaToolbar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val onDismissRequest = { onDownloadExpanded(false) }
|
val onDismissRequest = { onDownloadExpanded(false) }
|
||||||
DropdownMenu(
|
DownloadDropdownMenu(
|
||||||
expanded = downloadExpanded,
|
expanded = downloadExpanded,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
onDownloadClicked = onClickDownload,
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.download_1)) },
|
|
||||||
onClick = {
|
|
||||||
onClickDownload(DownloadAction.NEXT_1_CHAPTER)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.download_5)) },
|
|
||||||
onClick = {
|
|
||||||
onClickDownload(DownloadAction.NEXT_5_CHAPTERS)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.download_10)) },
|
|
||||||
onClick = {
|
|
||||||
onClickDownload(DownloadAction.NEXT_10_CHAPTERS)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.download_custom)) },
|
|
||||||
onClick = {
|
|
||||||
onClickDownload(DownloadAction.CUSTOM)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.download_unread)) },
|
|
||||||
onClick = {
|
|
||||||
onClickDownload(DownloadAction.UNREAD_CHAPTERS)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.download_all)) },
|
|
||||||
onClick = {
|
|
||||||
onClickDownload(DownloadAction.ALL_CHAPTERS)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,49 +112,39 @@ fun MangaToolbar(
|
|||||||
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
|
Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClickEditCategory != null && onClickMigrate != null) {
|
if (onClickEditCategory != null || onClickMigrate != null || onClickShare != null) {
|
||||||
val (moreExpanded, onMoreExpanded) = remember { mutableStateOf(false) }
|
OverflowMenu { closeMenu ->
|
||||||
Box {
|
if (onClickEditCategory != null) {
|
||||||
IconButton(onClick = { onMoreExpanded(!moreExpanded) }) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.MoreVert,
|
|
||||||
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val onDismissRequest = { onMoreExpanded(false) }
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = moreExpanded,
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_edit_categories)) },
|
text = { Text(text = stringResource(R.string.action_edit_categories)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onClickEditCategory()
|
onClickEditCategory()
|
||||||
onDismissRequest()
|
closeMenu()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
if (onClickMigrate != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_migrate)) },
|
text = { Text(text = stringResource(R.string.action_migrate)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onClickMigrate()
|
onClickMigrate()
|
||||||
onDismissRequest()
|
closeMenu()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
if (onClickShare != null) {
|
if (onClickShare != null) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(R.string.action_share)) },
|
text = { Text(text = stringResource(R.string.action_share)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onClickShare()
|
onClickShare()
|
||||||
onDismissRequest()
|
closeMenu()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets.statusBars,
|
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme
|
containerColor = MaterialTheme.colorScheme
|
||||||
.surfaceColorAtElevation(3.dp)
|
.surfaceColorAtElevation(3.dp)
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package eu.kanade.presentation.more
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.CloudOff
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
@ -21,6 +25,7 @@ import androidx.compose.ui.res.vectorResource
|
|||||||
import eu.kanade.presentation.components.AppStateBanners
|
import eu.kanade.presentation.components.AppStateBanners
|
||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
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.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -32,6 +37,7 @@ import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|||||||
@Composable
|
@Composable
|
||||||
fun MoreScreen(
|
fun MoreScreen(
|
||||||
presenter: MorePresenter,
|
presenter: MorePresenter,
|
||||||
|
isFDroid: Boolean,
|
||||||
onClickDownloadQueue: () -> Unit,
|
onClickDownloadQueue: () -> Unit,
|
||||||
onClickCategories: () -> Unit,
|
onClickCategories: () -> Unit,
|
||||||
onClickBackupAndRestore: () -> Unit,
|
onClickBackupAndRestore: () -> Unit,
|
||||||
@ -43,8 +49,21 @@ fun MoreScreen(
|
|||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier.statusBarsPadding(),
|
||||||
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(),
|
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(
|
||||||
|
WindowInsets.navigationBars.asPaddingValues(),
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
|
if (isFDroid) {
|
||||||
|
item {
|
||||||
|
WarningBanner(
|
||||||
|
textRes = R.string.fdroid_warning,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
uriHandler.openUri("https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
LogoHeader()
|
LogoHeader()
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ internal fun PreferenceItem(
|
|||||||
ListPreferenceWidget(
|
ListPreferenceWidget(
|
||||||
value = value,
|
value = value,
|
||||||
title = item.title,
|
title = item.title,
|
||||||
subtitle = item.subtitle,
|
subtitle = item.internalSubtitleProvider(value, item.entries),
|
||||||
icon = item.icon,
|
icon = item.icon,
|
||||||
entries = item.entries,
|
entries = item.entries,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
@ -98,7 +98,7 @@ internal fun PreferenceItem(
|
|||||||
ListPreferenceWidget(
|
ListPreferenceWidget(
|
||||||
value = item.value,
|
value = item.value,
|
||||||
title = item.title,
|
title = item.title,
|
||||||
subtitle = item.subtitle,
|
subtitle = item.subtitleProvider(item.value, item.entries),
|
||||||
icon = item.icon,
|
icon = item.icon,
|
||||||
entries = item.entries,
|
entries = item.entries,
|
||||||
onValueChange = { scope.launch { item.onValueChanged(it) } },
|
onValueChange = { scope.launch { item.onValueChanged(it) } },
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package eu.kanade.presentation.more.settings
|
package eu.kanade.presentation.more.settings
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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 eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.core.preference.Preference as PreferenceData
|
import eu.kanade.tachiyomi.core.preference.Preference as PreferenceData
|
||||||
|
|
||||||
@ -47,6 +51,8 @@ sealed class Preference {
|
|||||||
val pref: PreferenceData<T>,
|
val pref: PreferenceData<T>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
|
val subtitleProvider: @Composable (value: T, entries: Map<T, String>) -> String? =
|
||||||
|
{ v, e -> subtitle?.format(e[v]) },
|
||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: T) -> Boolean = { true },
|
override val onValueChanged: suspend (newValue: T) -> Boolean = { true },
|
||||||
@ -55,6 +61,10 @@ sealed class Preference {
|
|||||||
) : PreferenceItem<T>() {
|
) : PreferenceItem<T>() {
|
||||||
internal fun internalSet(newValue: Any) = pref.set(newValue as T)
|
internal fun internalSet(newValue: Any) = pref.set(newValue as T)
|
||||||
internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T)
|
internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun internalSubtitleProvider(value: Any?, entries: Map<out Any?, String>) =
|
||||||
|
subtitleProvider(value as T, entries as Map<T, String>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,6 +74,8 @@ sealed class Preference {
|
|||||||
val value: String,
|
val value: String,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = "%s",
|
override val subtitle: String? = "%s",
|
||||||
|
val subtitleProvider: @Composable (value: String, entries: Map<String, String>) -> String? =
|
||||||
|
{ v, e -> subtitle?.format(e[v]) },
|
||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
|
||||||
@ -78,7 +90,15 @@ sealed class Preference {
|
|||||||
data class MultiSelectListPreference(
|
data class MultiSelectListPreference(
|
||||||
val pref: PreferenceData<Set<String>>,
|
val pref: PreferenceData<Set<String>>,
|
||||||
override val title: String,
|
override val title: String,
|
||||||
override val subtitle: String? = null,
|
override val subtitle: String? = "%s",
|
||||||
|
val subtitleProvider: @Composable (value: Set<String>, entries: Map<String, String>) -> String? = { v, e ->
|
||||||
|
val combined = remember(v) {
|
||||||
|
v.map { e[it] }
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.joinToString()
|
||||||
|
} ?: stringResource(id = R.string.none)
|
||||||
|
subtitle?.format(combined)
|
||||||
|
},
|
||||||
override val icon: ImageVector? = null,
|
override val icon: ImageVector? = null,
|
||||||
override val enabled: Boolean = true,
|
override val enabled: Boolean = true,
|
||||||
override val onValueChanged: suspend (newValue: Set<String>) -> Boolean = { true },
|
override val onValueChanged: suspend (newValue: Set<String>) -> Boolean = { true },
|
||||||
|
@ -3,7 +3,7 @@ package eu.kanade.presentation.more.settings
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -28,7 +28,7 @@ fun PreferenceScaffold(
|
|||||||
if (onBackPressed != null) {
|
if (onBackPressed != null) {
|
||||||
IconButton(onClick = onBackPressed) {
|
IconButton(onClick = onBackPressed) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowBack,
|
imageVector = Icons.Outlined.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
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 androidx.compose.ui.util.fastMap
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
@ -81,7 +82,7 @@ class ClearDatabaseScreen : Screen {
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = model::hideConfirmation) {
|
TextButton(onClick = model::hideConfirmation) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
@ -240,14 +241,14 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
|
|||||||
|
|
||||||
fun selectAll() = mutableState.update { state ->
|
fun selectAll() = mutableState.update { state ->
|
||||||
if (state !is State.Ready) return@update state
|
if (state !is State.Ready) return@update state
|
||||||
state.copy(selection = state.items.map { it.id })
|
state.copy(selection = state.items.fastMap { it.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invertSelection() = mutableState.update { state ->
|
fun invertSelection() = mutableState.update { state ->
|
||||||
if (state !is State.Ready) return@update state
|
if (state !is State.Ready) return@update state
|
||||||
state.copy(
|
state.copy(
|
||||||
selection = state.items
|
selection = state.items
|
||||||
.map { it.id }
|
.fastMap { it.id }
|
||||||
.filterNot { it in state.selection },
|
.filterNot { it in state.selection },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,7 @@ class SettingsAdvancedScreen : SearchableSettings {
|
|||||||
text = { Text(text = stringResource(R.string.ext_installer_shizuku_unavailable_dialog)) },
|
text = { Text(text = stringResource(R.string.ext_installer_shizuku_unavailable_dialog)) },
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = dismiss) {
|
TextButton(onClick = dismiss) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -72,7 +72,6 @@ class SettingsAppearanceScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = themeModePref,
|
pref = themeModePref,
|
||||||
title = stringResource(R.string.pref_theme_mode),
|
title = stringResource(R.string.pref_theme_mode),
|
||||||
subtitle = "%s",
|
|
||||||
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
mapOf(
|
mapOf(
|
||||||
ThemeMode.SYSTEM to stringResource(R.string.theme_system),
|
ThemeMode.SYSTEM to stringResource(R.string.theme_system),
|
||||||
@ -129,7 +128,6 @@ class SettingsAppearanceScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = uiPreferences.relativeTime(),
|
pref = uiPreferences.relativeTime(),
|
||||||
title = stringResource(R.string.pref_relative_format),
|
title = stringResource(R.string.pref_relative_format),
|
||||||
subtitle = "%s",
|
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
0 to stringResource(R.string.off),
|
0 to stringResource(R.string.off),
|
||||||
2 to stringResource(R.string.pref_relative_time_short),
|
2 to stringResource(R.string.pref_relative_time_short),
|
||||||
@ -139,7 +137,6 @@ class SettingsAppearanceScreen : SearchableSettings {
|
|||||||
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),
|
||||||
subtitle = "%s",
|
|
||||||
entries = DateFormats
|
entries = DateFormats
|
||||||
.associateWith {
|
.associateWith {
|
||||||
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
val formattedDate = UiPreferences.dateFormat(it).format(now)
|
||||||
|
@ -192,7 +192,7 @@ class SettingsBackupScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -252,7 +252,7 @@ class SettingsBackupScreen : SearchableSettings {
|
|||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.copy))
|
Text(text = stringResource(android.R.string.copy))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -10,13 +10,13 @@ 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.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
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.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.util.fastMap
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
@ -27,7 +27,6 @@ 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.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -101,9 +100,11 @@ class SettingsDownloadScreen : SearchableSettings {
|
|||||||
return Preference.PreferenceItem.ListPreference(
|
return Preference.PreferenceItem.ListPreference(
|
||||||
pref = currentDirPref,
|
pref = currentDirPref,
|
||||||
title = stringResource(R.string.pref_download_directory),
|
title = stringResource(R.string.pref_download_directory),
|
||||||
subtitle = remember(currentDir) {
|
subtitleProvider = { value, _ ->
|
||||||
UniFile.fromUri(context, currentDir.toUri())?.filePath
|
remember(value) {
|
||||||
} ?: stringResource(R.string.invalid_location, currentDir),
|
UniFile.fromUri(context, value.toUri())?.filePath
|
||||||
|
} ?: stringResource(R.string.invalid_location, value)
|
||||||
|
},
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
defaultDirPair,
|
defaultDirPair,
|
||||||
customDirEntryKey to stringResource(R.string.custom_dir),
|
customDirEntryKey to stringResource(R.string.custom_dir),
|
||||||
@ -173,25 +174,10 @@ class SettingsDownloadScreen : SearchableSettings {
|
|||||||
downloadPreferences: DownloadPreferences,
|
downloadPreferences: DownloadPreferences,
|
||||||
categories: () -> List<Category>,
|
categories: () -> List<Category>,
|
||||||
): Preference.PreferenceItem.MultiSelectListPreference {
|
): Preference.PreferenceItem.MultiSelectListPreference {
|
||||||
val none = stringResource(R.string.none)
|
|
||||||
val pref = downloadPreferences.removeExcludeCategories()
|
|
||||||
val entries = categories().associate { it.id.toString() to it.visualName }
|
|
||||||
val subtitle by produceState(initialValue = "") {
|
|
||||||
pref.changes()
|
|
||||||
.stateIn(this)
|
|
||||||
.collect { mutable ->
|
|
||||||
value = mutable
|
|
||||||
.mapNotNull { id -> entries[id] }
|
|
||||||
.sortedBy { entries.values.indexOf(it) }
|
|
||||||
.joinToString()
|
|
||||||
.ifEmpty { none }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Preference.PreferenceItem.MultiSelectListPreference(
|
return Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = pref,
|
pref = downloadPreferences.removeExcludeCategories(),
|
||||||
title = stringResource(R.string.pref_remove_exclude_categories),
|
title = stringResource(R.string.pref_remove_exclude_categories),
|
||||||
subtitle = subtitle,
|
entries = categories().associate { it.id.toString() to it.visualName },
|
||||||
entries = entries,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,8 +205,8 @@ class SettingsDownloadScreen : SearchableSettings {
|
|||||||
itemLabel = { it.visualName },
|
itemLabel = { it.visualName },
|
||||||
onDismissRequest = { showDialog = false },
|
onDismissRequest = { showDialog = false },
|
||||||
onValueChanged = { newIncluded, newExcluded ->
|
onValueChanged = { newIncluded, newExcluded ->
|
||||||
downloadNewChapterCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
|
downloadNewChapterCategoriesPref.set(newIncluded.fastMap { it.id.toString() }.toSet())
|
||||||
downloadNewChapterCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
|
downloadNewChapterCategoriesExcludePref.set(newExcluded.fastMap { it.id.toString() }.toSet())
|
||||||
showDialog = false
|
showDialog = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -72,7 +72,6 @@ class SettingsGeneralScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.BasicListPreference(
|
Preference.PreferenceItem.BasicListPreference(
|
||||||
value = currentLanguage,
|
value = currentLanguage,
|
||||||
title = stringResource(R.string.pref_app_language),
|
title = stringResource(R.string.pref_app_language),
|
||||||
subtitle = "%s",
|
|
||||||
entries = langs,
|
entries = langs,
|
||||||
onValueChanged = { newValue ->
|
onValueChanged = { newValue ->
|
||||||
currentLanguage = newValue
|
currentLanguage = newValue
|
||||||
|
@ -25,6 +25,7 @@ import androidx.compose.ui.draw.clipToBounds
|
|||||||
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.util.fastMap
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.Router
|
import com.bluelinelabs.conductor.Router
|
||||||
@ -124,9 +125,9 @@ class SettingsLibraryScreen : SearchableSettings {
|
|||||||
|
|
||||||
// For default category
|
// For default category
|
||||||
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
|
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
|
||||||
allCategories.map { it.id.toInt() }
|
allCategories.fastMap { it.id.toInt() }
|
||||||
val labels = listOf(stringResource(R.string.default_category_summary)) +
|
val labels = listOf(stringResource(R.string.default_category_summary)) +
|
||||||
allCategories.map { it.visualName(context) }
|
allCategories.fastMap { it.visualName(context) }
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(R.string.categories),
|
||||||
@ -177,28 +178,6 @@ class SettingsLibraryScreen : SearchableSettings {
|
|||||||
|
|
||||||
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
|
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
|
||||||
|
|
||||||
val deviceRestrictionEntries = mapOf(
|
|
||||||
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
|
|
||||||
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
|
|
||||||
DEVICE_CHARGING to stringResource(R.string.charging),
|
|
||||||
DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low),
|
|
||||||
)
|
|
||||||
val deviceRestrictions = libraryUpdateDeviceRestrictionPref.collectAsState()
|
|
||||||
.value
|
|
||||||
.sorted()
|
|
||||||
.map { deviceRestrictionEntries.getOrElse(it) { it } }
|
|
||||||
.let { if (it.isEmpty()) stringResource(R.string.none) else it.joinToString() }
|
|
||||||
|
|
||||||
val mangaRestrictionEntries = mapOf(
|
|
||||||
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
|
|
||||||
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
|
|
||||||
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
|
|
||||||
)
|
|
||||||
val mangaRestrictions = libraryUpdateMangaRestrictionPref.collectAsState()
|
|
||||||
.value
|
|
||||||
.map { mangaRestrictionEntries.getOrElse(it) { it } }
|
|
||||||
.let { if (it.isEmpty()) stringResource(R.string.none) else it.joinToString() }
|
|
||||||
|
|
||||||
val included by libraryUpdateCategoriesPref.collectAsState()
|
val included by libraryUpdateCategoriesPref.collectAsState()
|
||||||
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
|
||||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
var showDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
@ -224,7 +203,6 @@ class SettingsLibraryScreen : SearchableSettings {
|
|||||||
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),
|
||||||
subtitle = "%s",
|
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
0 to stringResource(R.string.update_never),
|
0 to stringResource(R.string.update_never),
|
||||||
12 to stringResource(R.string.update_12hour),
|
12 to stringResource(R.string.update_12hour),
|
||||||
@ -242,8 +220,13 @@ class SettingsLibraryScreen : SearchableSettings {
|
|||||||
pref = libraryUpdateDeviceRestrictionPref,
|
pref = libraryUpdateDeviceRestrictionPref,
|
||||||
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, deviceRestrictions),
|
subtitle = stringResource(R.string.restrictions),
|
||||||
entries = deviceRestrictionEntries,
|
entries = mapOf(
|
||||||
|
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
|
||||||
|
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
|
||||||
|
DEVICE_CHARGING to stringResource(R.string.charging),
|
||||||
|
DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low),
|
||||||
|
),
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
// Post to event looper to allow the preference to be updated.
|
// Post to event looper to allow the preference to be updated.
|
||||||
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
|
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
|
||||||
@ -253,8 +236,11 @@ class SettingsLibraryScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryUpdateMangaRestrictionPref,
|
pref = libraryUpdateMangaRestrictionPref,
|
||||||
title = stringResource(R.string.pref_library_update_manga_restriction),
|
title = stringResource(R.string.pref_library_update_manga_restriction),
|
||||||
subtitle = mangaRestrictions,
|
entries = mapOf(
|
||||||
entries = mangaRestrictionEntries,
|
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),
|
||||||
|
MANGA_NON_READ to stringResource(R.string.pref_update_only_started),
|
||||||
|
MANGA_NON_COMPLETED to stringResource(R.string.pref_update_only_non_completed),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(R.string.categories),
|
||||||
@ -341,7 +327,7 @@ class SettingsLibraryScreen : SearchableSettings {
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -8,7 +8,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
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.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.ChromeReaderMode
|
import androidx.compose.material.icons.outlined.ChromeReaderMode
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
||||||
@ -98,7 +98,7 @@ object SettingsMainScreen : Screen {
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = backPress::invoke) {
|
IconButton(onClick = backPress::invoke) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowBack,
|
imageVector = Icons.Outlined.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ import androidx.compose.foundation.text.BasicTextField
|
|||||||
import androidx.compose.foundation.text.KeyboardActions
|
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.filled.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
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
|
||||||
@ -95,8 +95,8 @@ class SettingsSearchScreen : Screen {
|
|||||||
if (canPop) {
|
if (canPop) {
|
||||||
IconButton(onClick = navigator::pop) {
|
IconButton(onClick = navigator::pop) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowBack,
|
imageVector = Icons.Outlined.ArrowBack,
|
||||||
contentDescription = null,
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ class SettingsSearchScreen : Screen {
|
|||||||
if (textFieldValue.text.isNotEmpty()) {
|
if (textFieldValue.text.isNotEmpty()) {
|
||||||
IconButton(onClick = { textFieldValue = TextFieldValue() }) {
|
IconButton(onClick = { textFieldValue = TextFieldValue() }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,6 @@ class SettingsSecurityScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = securityPreferences.lockAppAfter(),
|
pref = securityPreferences.lockAppAfter(),
|
||||||
title = stringResource(R.string.lock_when_idle),
|
title = stringResource(R.string.lock_when_idle),
|
||||||
subtitle = "%s",
|
|
||||||
enabled = authSupported && useAuth,
|
enabled = authSupported && useAuth,
|
||||||
entries = LockAfterValues
|
entries = LockAfterValues
|
||||||
.associateWith {
|
.associateWith {
|
||||||
@ -72,7 +71,6 @@ class 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),
|
||||||
subtitle = "%s",
|
|
||||||
entries = SecurityPreferences.SecureScreenMode.values()
|
entries = SecurityPreferences.SecureScreenMode.values()
|
||||||
.associateWith { stringResource(it.titleResId) },
|
.associateWith { stringResource(it.titleResId) },
|
||||||
),
|
),
|
||||||
|
@ -10,10 +10,10 @@ import androidx.compose.foundation.layout.RowScope
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
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.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.HelpOutline
|
|
||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
import androidx.compose.material.icons.filled.VisibilityOff
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
import androidx.compose.material.icons.outlined.Close
|
||||||
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
@ -71,7 +71,7 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/help/guides/tracking/") }) {
|
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/help/guides/tracking/") }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.HelpOutline,
|
imageVector = Icons.Outlined.HelpOutline,
|
||||||
contentDescription = stringResource(R.string.tracking_guide),
|
contentDescription = stringResource(R.string.tracking_guide),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -199,7 +199,7 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
IconButton(onClick = onDismissRequest) {
|
IconButton(onClick = onDismissRequest) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = stringResource(R.string.action_close),
|
contentDescription = stringResource(R.string.action_close),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -227,9 +227,9 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||||||
IconButton(onClick = { hidePassword = !hidePassword }) {
|
IconButton(onClick = { hidePassword = !hidePassword }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (hidePassword) {
|
imageVector = if (hidePassword) {
|
||||||
Icons.Default.Visibility
|
Icons.Filled.Visibility
|
||||||
} else {
|
} else {
|
||||||
Icons.Default.VisibilityOff
|
Icons.Filled.VisibilityOff
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
@ -317,7 +317,7 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
onClick = onDismissRequest,
|
onClick = onDismissRequest,
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
@ -45,6 +45,7 @@ import eu.kanade.presentation.components.DIVIDER_ALPHA
|
|||||||
import eu.kanade.presentation.components.MangaCover
|
import eu.kanade.presentation.components.MangaCover
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
|
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
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ fun AppThemePreviewItem(
|
|||||||
.padding(end = 4.dp)
|
.padding(end = 4.dp)
|
||||||
.background(
|
.background(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
shape = RoundedCornerShape(9.dp),
|
shape = MaterialTheme.shapes.small,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -168,8 +169,8 @@ fun AppThemePreviewItem(
|
|||||||
) {
|
) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = stringResource(R.string.selected),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -182,7 +183,7 @@ fun AppThemePreviewItem(
|
|||||||
.padding(start = 8.dp, top = 2.dp)
|
.padding(start = 8.dp, top = 2.dp)
|
||||||
.background(
|
.background(
|
||||||
color = dividerColor,
|
color = dividerColor,
|
||||||
shape = RoundedCornerShape(9.dp),
|
shape = MaterialTheme.shapes.small,
|
||||||
)
|
)
|
||||||
.fillMaxWidth(0.5f)
|
.fillMaxWidth(0.5f)
|
||||||
.aspectRatio(MangaCover.Book.ratio),
|
.aspectRatio(MangaCover.Book.ratio),
|
||||||
@ -242,7 +243,7 @@ fun AppThemePreviewItem(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.background(
|
.background(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
shape = RoundedCornerShape(9.dp),
|
shape = MaterialTheme.shapes.small,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,9 @@ internal fun BasePreferenceWidget(
|
|||||||
widget: @Composable (() -> Unit)? = null,
|
widget: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val highlighted = LocalPreferenceHighlighted.current
|
val highlighted = LocalPreferenceHighlighted.current
|
||||||
Box(modifier = Modifier.highlightBackground(highlighted)) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.highlightBackground(highlighted)
|
||||||
.sizeIn(minHeight = 56.dp)
|
.sizeIn(minHeight = 56.dp)
|
||||||
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
@ -82,7 +82,6 @@ internal fun BasePreferenceWidget(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = composed {
|
internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = composed {
|
||||||
|
@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -27,18 +28,18 @@ fun EditTextPreferenceWidget(
|
|||||||
value: String,
|
value: String,
|
||||||
onConfirm: suspend (String) -> Boolean,
|
onConfirm: suspend (String) -> Boolean,
|
||||||
) {
|
) {
|
||||||
val (isDialogShown, showDialog) = remember { mutableStateOf(false) }
|
var isDialogShown by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle?.format(value),
|
subtitle = subtitle?.format(value),
|
||||||
icon = icon,
|
icon = icon,
|
||||||
onPreferenceClick = { showDialog(true) },
|
onPreferenceClick = { isDialogShown = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isDialogShown) {
|
if (isDialogShown) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val onDismissRequest = { showDialog(false) }
|
val onDismissRequest = { isDialogShown = false }
|
||||||
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||||
mutableStateOf(TextFieldValue(value))
|
mutableStateOf(TextFieldValue(value))
|
||||||
}
|
}
|
||||||
@ -71,7 +72,7 @@ fun EditTextPreferenceWidget(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -6,15 +6,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
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.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
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
|
||||||
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.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
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.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@ -26,6 +27,7 @@ import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|||||||
import eu.kanade.presentation.util.isScrolledToEnd
|
import eu.kanade.presentation.util.isScrolledToEnd
|
||||||
import eu.kanade.presentation.util.isScrolledToStart
|
import eu.kanade.presentation.util.isScrolledToStart
|
||||||
import eu.kanade.presentation.util.minimumTouchTargetSize
|
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> ListPreferenceWidget(
|
fun <T> ListPreferenceWidget(
|
||||||
@ -36,18 +38,18 @@ fun <T> ListPreferenceWidget(
|
|||||||
entries: Map<out T, String>,
|
entries: Map<out T, String>,
|
||||||
onValueChange: (T) -> Unit,
|
onValueChange: (T) -> Unit,
|
||||||
) {
|
) {
|
||||||
val (isDialogShown, showDialog) = remember { mutableStateOf(false) }
|
var isDialogShown by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle?.format(entries[value]),
|
subtitle = subtitle,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
onPreferenceClick = { showDialog(true) },
|
onPreferenceClick = { isDialogShown = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isDialogShown) {
|
if (isDialogShown) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showDialog(false) },
|
onDismissRequest = { isDialogShown = false },
|
||||||
title = { Text(text = title) },
|
title = { Text(text = title) },
|
||||||
text = {
|
text = {
|
||||||
Box {
|
Box {
|
||||||
@ -61,7 +63,7 @@ fun <T> ListPreferenceWidget(
|
|||||||
isSelected = isSelected,
|
isSelected = isSelected,
|
||||||
onSelected = {
|
onSelected = {
|
||||||
onValueChange(current.key!!)
|
onValueChange(current.key!!)
|
||||||
showDialog(false)
|
isDialogShown = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -72,8 +74,8 @@ fun <T> ListPreferenceWidget(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { showDialog(false) }) {
|
TextButton(onClick = { isDialogShown = false }) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -89,7 +91,7 @@ private fun DialogRow(
|
|||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(MaterialTheme.shapes.small)
|
||||||
.selectable(
|
.selectable(
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = { if (!isSelected) onSelected() },
|
onClick = { if (!isSelected) onSelected() },
|
||||||
|
@ -5,15 +5,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
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.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.setValue
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -23,6 +24,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.minimumTouchTargetSize
|
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MultiSelectListPreferenceWidget(
|
fun MultiSelectListPreferenceWidget(
|
||||||
@ -30,13 +32,13 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
values: Set<String>,
|
values: Set<String>,
|
||||||
onValuesChange: (Set<String>) -> Unit,
|
onValuesChange: (Set<String>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val (isDialogShown, showDialog) = remember { mutableStateOf(false) }
|
var isDialogShown by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = preference.title,
|
title = preference.title,
|
||||||
subtitle = preference.subtitle,
|
subtitle = preference.subtitleProvider(values, preference.entries),
|
||||||
icon = preference.icon,
|
icon = preference.icon,
|
||||||
onPreferenceClick = { showDialog(true) },
|
onPreferenceClick = { isDialogShown = true },
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isDialogShown) {
|
if (isDialogShown) {
|
||||||
@ -46,7 +48,7 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
.toMutableStateList()
|
.toMutableStateList()
|
||||||
}
|
}
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showDialog(false) },
|
onDismissRequest = { isDialogShown = false },
|
||||||
title = { Text(text = preference.title) },
|
title = { Text(text = preference.title) },
|
||||||
text = {
|
text = {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
@ -62,7 +64,7 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(MaterialTheme.shapes.small)
|
||||||
.selectable(
|
.selectable(
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = { onSelectionChanged() },
|
onClick = { onSelectionChanged() },
|
||||||
@ -91,15 +93,15 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onValuesChange(selected.toMutableSet())
|
onValuesChange(selected.toMutableSet())
|
||||||
showDialog(false)
|
isDialogShown = false
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(android.R.string.ok))
|
Text(text = stringResource(android.R.string.ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showDialog(false) }) {
|
TextButton(onClick = { isDialogShown = false }) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchPreferenceWidget(
|
fun SwitchPreferenceWidget(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
@ -21,6 +22,7 @@ fun SwitchPreferenceWidget(
|
|||||||
onCheckedChanged: (Boolean) -> Unit,
|
onCheckedChanged: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
|
modifier = modifier,
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
@ -44,7 +46,7 @@ private fun SwitchPreferenceWidgetPreview() {
|
|||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
title = "Text preference with icon",
|
title = "Text preference with icon",
|
||||||
subtitle = "Text preference summary",
|
subtitle = "Text preference summary",
|
||||||
icon = Icons.Default.Preview,
|
icon = Icons.Filled.Preview,
|
||||||
checked = true,
|
checked = true,
|
||||||
onCheckedChanged = {},
|
onCheckedChanged = {},
|
||||||
)
|
)
|
||||||
|
@ -67,7 +67,7 @@ private fun TextPreferenceWidgetPreview() {
|
|||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = "Text preference with icon",
|
title = "Text preference with icon",
|
||||||
subtitle = "Text preference summary",
|
subtitle = "Text preference summary",
|
||||||
icon = Icons.Default.Preview,
|
icon = Icons.Filled.Preview,
|
||||||
onPreferenceClick = {},
|
onPreferenceClick = {},
|
||||||
)
|
)
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
|
@ -10,9 +10,8 @@ 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.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.outlined.Done
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -21,8 +20,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TrackingPreferenceWidget(
|
fun TrackingPreferenceWidget(
|
||||||
@ -45,7 +46,7 @@ fun TrackingPreferenceWidget(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.background(color = Color(logoColor), shape = RoundedCornerShape(8.dp))
|
.background(color = Color(logoColor), shape = MaterialTheme.shapes.small)
|
||||||
.padding(4.dp),
|
.padding(4.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
@ -65,12 +66,12 @@ fun TrackingPreferenceWidget(
|
|||||||
)
|
)
|
||||||
if (checked) {
|
if (checked) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Check,
|
imageVector = Icons.Outlined.Done,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.size(32.dp),
|
.size(32.dp),
|
||||||
tint = Color(0xFF4CAF50),
|
tint = Color(0xFF4CAF50),
|
||||||
contentDescription = null,
|
contentDescription = stringResource(R.string.login_success),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.CheckBox
|
import androidx.compose.material.icons.rounded.CheckBox
|
||||||
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||||
@ -79,7 +78,7 @@ fun <T> TriStateListDialog(
|
|||||||
val state = selected[index]
|
val state = selected[index]
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(MaterialTheme.shapes.small)
|
||||||
.clickable {
|
.clickable {
|
||||||
selected[index] = when (state) {
|
selected[index] = when (state) {
|
||||||
State.UNCHECKED -> State.CHECKED
|
State.UNCHECKED -> State.CHECKED
|
||||||
@ -103,7 +102,13 @@ fun <T> TriStateListDialog(
|
|||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = stringResource(
|
||||||
|
when (state) {
|
||||||
|
State.UNCHECKED -> R.string.not_selected
|
||||||
|
State.CHECKED -> R.string.selected
|
||||||
|
State.INVERSED -> R.string.disabled
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
Text(text = itemLabel(item))
|
Text(text = itemLabel(item))
|
||||||
}
|
}
|
||||||
@ -117,7 +122,7 @@ fun <T> TriStateListDialog(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -27,7 +27,7 @@ fun UpdatesDeleteConfirmationDialog(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -2,15 +2,12 @@ package eu.kanade.presentation.updates
|
|||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
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.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.FlipToBack
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material.icons.filled.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@ -23,17 +20,17 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.util.fastAll
|
||||||
|
import androidx.compose.ui.util.fastAny
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LazyColumn
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.MangaBottomActionMenu
|
import eu.kanade.presentation.components.MangaBottomActionMenu
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.SwipeRefresh
|
import eu.kanade.presentation.components.SwipeRefresh
|
||||||
import eu.kanade.presentation.components.VerticalFastScroller
|
|
||||||
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.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
@ -124,7 +121,6 @@ private fun UpdateScreenContent(
|
|||||||
onClickCover: (UpdatesItem) -> Unit,
|
onClickCover: (UpdatesItem) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val updatesListState = rememberLazyListState()
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@ -143,14 +139,7 @@ private fun UpdateScreenContent(
|
|||||||
enabled = presenter.selectionMode.not(),
|
enabled = presenter.selectionMode.not(),
|
||||||
indicatorPadding = contentPadding,
|
indicatorPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
VerticalFastScroller(
|
FastScrollLazyColumn(
|
||||||
listState = updatesListState,
|
|
||||||
topContentPadding = contentPadding.calculateTopPadding(),
|
|
||||||
endContentPadding = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
state = updatesListState,
|
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
if (presenter.lastUpdated > 0L) {
|
if (presenter.lastUpdated > 0L) {
|
||||||
@ -172,7 +161,6 @@ private fun UpdateScreenContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val onDismissDialog = { presenter.dialog = null }
|
val onDismissDialog = { presenter.dialog = null }
|
||||||
when (val dialog = presenter.dialog) {
|
when (val dialog = presenter.dialog) {
|
||||||
@ -215,7 +203,7 @@ private fun UpdatesAppBar(
|
|||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onUpdateLibrary) {
|
IconButton(onClick = onUpdateLibrary) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Refresh,
|
imageVector = Icons.Outlined.Refresh,
|
||||||
contentDescription = stringResource(R.string.action_update_library),
|
contentDescription = stringResource(R.string.action_update_library),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -225,13 +213,13 @@ private fun UpdatesAppBar(
|
|||||||
actionModeActions = {
|
actionModeActions = {
|
||||||
IconButton(onClick = onSelectAll) {
|
IconButton(onClick = onSelectAll) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.SelectAll,
|
imageVector = Icons.Outlined.SelectAll,
|
||||||
contentDescription = stringResource(R.string.action_select_all),
|
contentDescription = stringResource(R.string.action_select_all),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onInvertSelection) {
|
IconButton(onClick = onInvertSelection) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.FlipToBack,
|
imageVector = Icons.Outlined.FlipToBack,
|
||||||
contentDescription = stringResource(R.string.action_select_inverse),
|
contentDescription = stringResource(R.string.action_select_inverse),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -255,24 +243,24 @@ private fun UpdatesBottomBar(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onBookmarkClicked = {
|
onBookmarkClicked = {
|
||||||
onMultiBookmarkClicked.invoke(selected, true)
|
onMultiBookmarkClicked.invoke(selected, true)
|
||||||
}.takeIf { selected.any { !it.update.bookmark } },
|
}.takeIf { selected.fastAny { !it.update.bookmark } },
|
||||||
onRemoveBookmarkClicked = {
|
onRemoveBookmarkClicked = {
|
||||||
onMultiBookmarkClicked.invoke(selected, false)
|
onMultiBookmarkClicked.invoke(selected, false)
|
||||||
}.takeIf { selected.all { it.update.bookmark } },
|
}.takeIf { selected.fastAll { it.update.bookmark } },
|
||||||
onMarkAsReadClicked = {
|
onMarkAsReadClicked = {
|
||||||
onMultiMarkAsReadClicked(selected, true)
|
onMultiMarkAsReadClicked(selected, true)
|
||||||
}.takeIf { selected.any { !it.update.read } },
|
}.takeIf { selected.fastAny { !it.update.read } },
|
||||||
onMarkAsUnreadClicked = {
|
onMarkAsUnreadClicked = {
|
||||||
onMultiMarkAsReadClicked(selected, false)
|
onMultiMarkAsReadClicked(selected, false)
|
||||||
}.takeIf { selected.any { it.update.read } },
|
}.takeIf { selected.fastAny { it.update.read } },
|
||||||
onDownloadClicked = {
|
onDownloadClicked = {
|
||||||
onDownloadChapter(selected, ChapterDownloadAction.START)
|
onDownloadChapter(selected, ChapterDownloadAction.START)
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
selected.any { it.downloadStateProvider() != Download.State.DOWNLOADED }
|
selected.fastAny { it.downloadStateProvider() != Download.State.DOWNLOADED }
|
||||||
},
|
},
|
||||||
onDeleteClicked = {
|
onDeleteClicked = {
|
||||||
onMultiDeleteClicked(selected)
|
onMultiDeleteClicked(selected)
|
||||||
}.takeIf { selected.any { it.downloadStateProvider() == Download.State.DOWNLOADED } },
|
}.takeIf { selected.fastAny { it.downloadStateProvider() == Download.State.DOWNLOADED } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.presentation.updates
|
package eu.kanade.presentation.updates
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -28,7 +27,6 @@ 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.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
@ -43,6 +41,7 @@ import eu.kanade.presentation.components.MangaCover
|
|||||||
import eu.kanade.presentation.components.RelativeDateHeader
|
import eu.kanade.presentation.components.RelativeDateHeader
|
||||||
import eu.kanade.presentation.util.ReadItemAlpha
|
import eu.kanade.presentation.util.ReadItemAlpha
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
|
import eu.kanade.presentation.util.selectedBackground
|
||||||
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.recent.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem
|
||||||
@ -135,6 +134,7 @@ fun LazyListScope.updatesUiItems(
|
|||||||
onDownloadChapter = {
|
onDownloadChapter = {
|
||||||
if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
|
if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
|
||||||
},
|
},
|
||||||
|
downloadIndicatorEnabled = selectionMode.not(),
|
||||||
downloadStateProvider = updatesItem.downloadStateProvider,
|
downloadStateProvider = updatesItem.downloadStateProvider,
|
||||||
downloadProgressProvider = updatesItem.downloadProgressProvider,
|
downloadProgressProvider = updatesItem.downloadProgressProvider,
|
||||||
)
|
)
|
||||||
@ -153,13 +153,14 @@ fun UpdatesUiItem(
|
|||||||
onClickCover: () -> Unit,
|
onClickCover: () -> Unit,
|
||||||
onDownloadChapter: (ChapterDownloadAction) -> Unit,
|
onDownloadChapter: (ChapterDownloadAction) -> Unit,
|
||||||
// Download Indicator
|
// Download Indicator
|
||||||
|
downloadIndicatorEnabled: Boolean,
|
||||||
downloadStateProvider: () -> Download.State,
|
downloadStateProvider: () -> Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
|
.selectedBackground(selected)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
@ -205,7 +206,7 @@ fun UpdatesUiItem(
|
|||||||
var textHeight by remember { mutableStateOf(0) }
|
var textHeight by remember { mutableStateOf(0) }
|
||||||
if (bookmark) {
|
if (bookmark) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bookmark,
|
imageVector = Icons.Filled.Bookmark,
|
||||||
contentDescription = stringResource(R.string.action_filter_bookmarked),
|
contentDescription = stringResource(R.string.action_filter_bookmarked),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
||||||
@ -225,6 +226,7 @@ fun UpdatesUiItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChapterDownloadIndicator(
|
ChapterDownloadIndicator(
|
||||||
|
enabled = downloadIndicatorEnabled,
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
downloadStateProvider = downloadStateProvider,
|
downloadStateProvider = downloadStateProvider,
|
||||||
downloadProgressProvider = downloadProgressProvider,
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
|
@ -12,3 +12,4 @@ val verticalPadding = vertical
|
|||||||
val topPaddingValues = PaddingValues(top = vertical)
|
val topPaddingValues = PaddingValues(top = vertical)
|
||||||
|
|
||||||
const val ReadItemAlpha = .38f
|
const val ReadItemAlpha = .38f
|
||||||
|
const val SecondaryItemAlpha = .78f
|
||||||
|
@ -29,7 +29,7 @@ fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(.78f)
|
fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha)
|
||||||
|
|
||||||
fun Modifier.clickableNoIndication(
|
fun Modifier.clickableNoIndication(
|
||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user