mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-30 21:17:50 +02:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
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.1)
|
||||||
- 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.1"
|
||||||
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.1](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.1](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 = 90
|
||||||
versionName = "0.14.0"
|
versionName = "0.14.1"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -329,6 +329,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 {
|
||||||
|
@ -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
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ class GetNextChapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
|
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
|
||||||
val chapter = getChapter.await(chapterId)!!
|
val chapter = getChapter.await(chapterId) ?: return null
|
||||||
val manga = getManga.await(mangaId)!!
|
val manga = getManga.await(mangaId) ?: return null
|
||||||
|
|
||||||
if (!chapter.read) return chapter
|
if (!chapter.read) return chapter
|
||||||
|
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
MangaGridCover(
|
),
|
||||||
cover = {
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
MangaCover.Book(
|
coverBadgeStart = {
|
||||||
modifier = Modifier
|
if (manga.favorite) {
|
||||||
.fillMaxWidth()
|
Badge(text = stringResource(R.string.in_library))
|
||||||
.drawWithContent {
|
}
|
||||||
drawContent()
|
},
|
||||||
if (manga.favorite) {
|
onLongClick = onLongClick,
|
||||||
drawRect(overlayColor)
|
onClick = onClick,
|
||||||
}
|
)
|
||||||
},
|
|
||||||
data = manga.thumbnailUrl,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badgesStart = {
|
|
||||||
if (manga.favorite) {
|
|
||||||
Badge(text = stringResource(R.string.in_library))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
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,
|
||||||
cover = {
|
lastModified = manga.coverLastModified,
|
||||||
MangaCover.Book(
|
),
|
||||||
modifier = Modifier
|
coverAlpha = if (manga.favorite) CommonMangaItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||||
.fillMaxHeight()
|
coverBadgeStart = {
|
||||||
.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,
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,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,
|
||||||
@ -44,7 +44,7 @@ fun BrowseSourceToolbar(
|
|||||||
) {
|
) {
|
||||||
if (state.searchQuery == null) {
|
if (state.searchQuery == null) {
|
||||||
BrowseSourceRegularToolbar(
|
BrowseSourceRegularToolbar(
|
||||||
title = if (state.isUserQuery) state.currentFilter.query else source.name,
|
title = if (state.isUserQuery) state.currentFilter.query else source?.name.orEmpty(),
|
||||||
isLocalSource = source is LocalSource,
|
isLocalSource = source is LocalSource,
|
||||||
displayMode = displayMode,
|
displayMode = displayMode,
|
||||||
onDisplayModeChange = onDisplayModeChange,
|
onDisplayModeChange = onDisplayModeChange,
|
||||||
|
@ -130,7 +130,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 = {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,9 @@ 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.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
|
||||||
@ -55,7 +53,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 +103,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 +123,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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -200,7 +198,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(
|
||||||
|
@ -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)) {
|
||||||
|
@ -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
|
||||||
@ -78,7 +78,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,
|
||||||
)
|
)
|
||||||
@ -148,7 +148,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,
|
||||||
@ -172,7 +172,7 @@ private fun DownloadedIndicator(
|
|||||||
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,
|
||||||
@ -204,8 +204,8 @@ private fun ErrorIndicator(
|
|||||||
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,
|
||||||
)
|
)
|
||||||
|
@ -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 = {
|
||||||
|
@ -10,9 +10,11 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
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 androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
|
import androidx.compose.material3.DropdownMenu as ComposeDropdownMenu
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -27,7 +29,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 = DpOffset(8.dp, (-56).dp),
|
||||||
properties = properties,
|
properties = properties,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
@ -46,13 +48,13 @@ 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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
@ -187,12 +187,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
|
||||||
@ -98,7 +98,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 +107,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 +116,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 +125,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,
|
||||||
@ -254,7 +254,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,7 +263,7 @@ 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,
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ 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
|
||||||
@ -79,7 +79,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") },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
coverBadgeStart = {
|
||||||
|
DownloadsBadge(
|
||||||
|
enabled = showDownloadBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
UnreadBadge(
|
||||||
|
enabled = showUnreadBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
coverBadgeEnd = {
|
||||||
|
LanguageBadge(
|
||||||
|
showLanguage = showLanguageBadges,
|
||||||
|
showLocal = showLocalBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
LibraryGridCover(
|
|
||||||
mangaCover = MangaCover(
|
|
||||||
manga.id,
|
|
||||||
manga.source,
|
|
||||||
manga.favorite,
|
|
||||||
manga.thumbnailUrl,
|
|
||||||
manga.coverLastModified,
|
|
||||||
),
|
|
||||||
item = item,
|
|
||||||
showDownloadBadge = showDownloadBadge,
|
|
||||||
showUnreadBadge = showUnreadBadge,
|
|
||||||
showLocalBadge = showLocalBadge,
|
|
||||||
showLanguageBadge = showLanguageBadge,
|
|
||||||
)
|
|
||||||
MangaGridComfortableText(
|
|
||||||
text = manga.title,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
coverBadgeEnd = {
|
||||||
|
LanguageBadge(
|
||||||
|
showLanguage = showLanguageBadges,
|
||||||
|
showLocal = showLocalBadges,
|
||||||
|
item = libraryItem,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 = {
|
|
||||||
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,
|
|
||||||
) {
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -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,21 @@
|
|||||||
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.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,7 +35,7 @@ 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()) {
|
||||||
@ -64,116 +52,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,
|
title = manga.title,
|
||||||
onLongClick = onLongClick,
|
coverData = MangaCover(
|
||||||
|
mangaId = manga.id,
|
||||||
|
sourceId = manga.source,
|
||||||
|
isMangaFavorite = manga.favorite,
|
||||||
|
url = manga.thumbnailUrl,
|
||||||
|
lastModified = manga.coverLastModified,
|
||||||
|
),
|
||||||
|
badge = {
|
||||||
|
DownloadsBadge(enabled = showDownloadBadges, item = libraryItem)
|
||||||
|
UnreadBadge(enabled = showUnreadBadges, item = libraryItem)
|
||||||
|
LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem)
|
||||||
|
},
|
||||||
|
onLongClick = { onLongClick(libraryItem.libraryManga) },
|
||||||
|
onClick = { onClick(libraryItem.libraryManga) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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,
|
|
||||||
cover = MangaCover(
|
|
||||||
manga.id,
|
|
||||||
manga.source,
|
|
||||||
manga.favorite,
|
|
||||||
manga.thumbnailUrl,
|
|
||||||
manga.coverLastModified,
|
|
||||||
),
|
|
||||||
onClick = { onClick(libraryManga) },
|
|
||||||
onLongClick = { onLongClick(libraryManga) },
|
|
||||||
) {
|
|
||||||
DownloadsBadge(enabled = showDownloadBadge, item = item)
|
|
||||||
UnreadBadge(enabled = showUnreadBadge, item = item)
|
|
||||||
LanguageBadge(showLanguage = showLanguageBadge, showLocal = showLocalBadge, item = item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,10 +150,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,
|
||||||
|
@ -273,7 +273,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
}
|
}
|
||||||
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
|
modifier = Modifier
|
||||||
@ -486,7 +486,7 @@ fun MangaScreenLargeImpl(
|
|||||||
}
|
}
|
||||||
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
|
modifier = Modifier
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -72,7 +72,7 @@ fun MangaChapterListItem(
|
|||||||
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 }),
|
||||||
|
@ -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
|
||||||
@ -63,7 +63,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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -5,13 +5,13 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.statusBars
|
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.MoreVert
|
||||||
|
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
|
||||||
@ -71,7 +71,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 +80,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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -161,7 +161,7 @@ fun MangaToolbar(
|
|||||||
Box {
|
Box {
|
||||||
IconButton(onClick = { onMoreExpanded(!moreExpanded) }) {
|
IconButton(onClick = { onMoreExpanded(!moreExpanded) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.MoreVert,
|
imageVector = Icons.Outlined.MoreVert,
|
||||||
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,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 = {
|
||||||
|
@ -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,7 +10,6 @@ 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
|
||||||
@ -27,7 +26,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 +99,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 +173,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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -177,28 +177,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 +202,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 +219,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 +235,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 +326,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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -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,7 +6,6 @@ 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
|
||||||
@ -26,6 +25,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(
|
||||||
@ -40,7 +40,7 @@ fun <T> ListPreferenceWidget(
|
|||||||
|
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle?.format(entries[value]),
|
subtitle = subtitle,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
onPreferenceClick = { showDialog(true) },
|
onPreferenceClick = { showDialog(true) },
|
||||||
)
|
)
|
||||||
@ -73,7 +73,7 @@ fun <T> ListPreferenceWidget(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { showDialog(false) }) {
|
TextButton(onClick = { showDialog(false) }) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -89,7 +89,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,7 +5,6 @@ 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
|
||||||
@ -23,6 +22,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(
|
||||||
@ -34,7 +34,7 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
|
|
||||||
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 = { showDialog(true) },
|
||||||
)
|
)
|
||||||
@ -62,7 +62,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() },
|
||||||
@ -99,7 +99,7 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showDialog(false) }) {
|
TextButton(onClick = { showDialog(false) }) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -44,7 +44,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))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -8,9 +8,9 @@ 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.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
|
||||||
@ -215,7 +215,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 +225,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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,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 }),
|
||||||
|
@ -7,9 +7,9 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.ArrowForward
|
import androidx.compose.material.icons.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -48,13 +48,13 @@ fun WebViewScreen(
|
|||||||
title = state.pageTitle ?: initialTitle,
|
title = state.pageTitle ?: initialTitle,
|
||||||
subtitle = state.content.getCurrentUrl(),
|
subtitle = state.content.getCurrentUrl(),
|
||||||
navigateUp = onNavigateUp,
|
navigateUp = onNavigateUp,
|
||||||
navigationIcon = Icons.Default.Close,
|
navigationIcon = Icons.Outlined.Close,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
listOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_webview_back),
|
title = stringResource(R.string.action_webview_back),
|
||||||
icon = Icons.Default.ArrowBack,
|
icon = Icons.Outlined.ArrowBack,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (navigator.canGoBack) {
|
if (navigator.canGoBack) {
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
@ -64,7 +64,7 @@ fun WebViewScreen(
|
|||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_webview_forward),
|
title = stringResource(R.string.action_webview_forward),
|
||||||
icon = Icons.Default.ArrowForward,
|
icon = Icons.Outlined.ArrowForward,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (navigator.canGoForward) {
|
if (navigator.canGoForward) {
|
||||||
navigator.navigateForward()
|
navigator.navigateForward()
|
||||||
|
@ -48,6 +48,8 @@ class DownloadCache(
|
|||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
private val notifier by lazy { DownloadNotifier(context) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The interval after which this cache should be invalidated. 1 hour shouldn't cause major
|
* The interval after which this cache should be invalidated. 1 hour shouldn't cause major
|
||||||
* issues, as the cache is only used for UI feedback.
|
* issues, as the cache is only used for UI feedback.
|
||||||
@ -241,56 +243,62 @@ class DownloadCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
renewalJob = scope.launchIO {
|
renewalJob = scope.launchIO {
|
||||||
var sources = getSources()
|
try {
|
||||||
|
notifier.onCacheProgress()
|
||||||
|
|
||||||
// Try to wait until extensions and sources have loaded
|
var sources = getSources()
|
||||||
withTimeout(30.seconds) {
|
|
||||||
while (!extensionManager.isInitialized) {
|
|
||||||
delay(2.seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
while (sources.isEmpty()) {
|
// Try to wait until extensions and sources have loaded
|
||||||
delay(2.seconds)
|
withTimeout(30.seconds) {
|
||||||
sources = getSources()
|
while (!extensionManager.isInitialized) {
|
||||||
}
|
delay(2.seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty()
|
while (sources.isEmpty()) {
|
||||||
.associate { it.name to SourceDirectory(it) }
|
delay(2.seconds)
|
||||||
.mapNotNullKeys { entry ->
|
sources = getSources()
|
||||||
sources.find {
|
|
||||||
provider.getSourceDirName(it).equals(entry.key, ignoreCase = true)
|
|
||||||
}?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
rootDownloadsDir.sourceDirs = sourceDirs
|
|
||||||
|
|
||||||
sourceDirs.values
|
|
||||||
.map { sourceDir ->
|
|
||||||
async {
|
|
||||||
val mangaDirs = sourceDir.dir.listFiles().orEmpty()
|
|
||||||
.filterNot { it.name.isNullOrBlank() }
|
|
||||||
.associate { it.name!! to MangaDirectory(it) }
|
|
||||||
|
|
||||||
sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs)
|
|
||||||
|
|
||||||
mangaDirs.values.forEach { mangaDir ->
|
|
||||||
val chapterDirs = mangaDir.dir.listFiles().orEmpty()
|
|
||||||
.mapNotNull { chapterDir ->
|
|
||||||
chapterDir.name
|
|
||||||
?.replace(".cbz", "")
|
|
||||||
?.takeUnless { it.endsWith(Downloader.TMP_DIR_SUFFIX) }
|
|
||||||
}
|
|
||||||
.toMutableSet()
|
|
||||||
|
|
||||||
mangaDir.chapterDirs = chapterDirs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.awaitAll()
|
|
||||||
|
|
||||||
lastRenew = System.currentTimeMillis()
|
val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty()
|
||||||
notifyChanges()
|
.associate { it.name to SourceDirectory(it) }
|
||||||
|
.mapNotNullKeys { entry ->
|
||||||
|
sources.find {
|
||||||
|
provider.getSourceDirName(it).equals(entry.key, ignoreCase = true)
|
||||||
|
}?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDownloadsDir.sourceDirs = sourceDirs
|
||||||
|
|
||||||
|
sourceDirs.values
|
||||||
|
.map { sourceDir ->
|
||||||
|
async {
|
||||||
|
val mangaDirs = sourceDir.dir.listFiles().orEmpty()
|
||||||
|
.filterNot { it.name.isNullOrBlank() }
|
||||||
|
.associate { it.name!! to MangaDirectory(it) }
|
||||||
|
|
||||||
|
sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs)
|
||||||
|
|
||||||
|
mangaDirs.values.forEach { mangaDir ->
|
||||||
|
val chapterDirs = mangaDir.dir.listFiles().orEmpty()
|
||||||
|
.mapNotNull { chapterDir ->
|
||||||
|
chapterDir.name
|
||||||
|
?.replace(".cbz", "")
|
||||||
|
?.takeUnless { it.endsWith(Downloader.TMP_DIR_SUFFIX) }
|
||||||
|
}
|
||||||
|
.toMutableSet()
|
||||||
|
|
||||||
|
mangaDir.chapterDirs = chapterDirs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.awaitAll()
|
||||||
|
|
||||||
|
lastRenew = System.currentTimeMillis()
|
||||||
|
notifyChanges()
|
||||||
|
} finally {
|
||||||
|
notifier.dismissCacheProgress()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,17 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val cacheNotificationBuilder by lazy {
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_CACHE) {
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
setContentTitle(context.getString(R.string.download_notifier_cache_renewal))
|
||||||
|
setProgress(100, 100, true)
|
||||||
|
setOngoing(true)
|
||||||
|
setAutoCancel(false)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of download. Used for correct notification icon.
|
* Status of download. Used for correct notification icon.
|
||||||
*/
|
*/
|
||||||
@ -233,4 +244,14 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
errorThrown = true
|
errorThrown = true
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onCacheProgress() {
|
||||||
|
with(cacheNotificationBuilder) {
|
||||||
|
show(Notifications.ID_DOWNLOAD_CACHE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissCacheProgress() {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CACHE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,17 +485,15 @@ class Downloader(
|
|||||||
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile): Boolean {
|
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile): Boolean {
|
||||||
if (!downloadPreferences.splitTallImages().get()) return true
|
if (!downloadPreferences.splitTallImages().get()) return true
|
||||||
|
|
||||||
val filename = String.format("%03d", page.number)
|
val filenamePrefix = String.format("%03d", page.number)
|
||||||
val imageFile = tmpDir.listFiles()?.find { it.name!!.startsWith(filename) }
|
val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) }
|
||||||
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
|
?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
|
||||||
val imageFilePath = imageFile.filePath
|
|
||||||
?: throw Error(context.getString(R.string.download_notifier_split_page_path_not_found, page.number))
|
|
||||||
|
|
||||||
// check if the original page was previously splitted before then skip.
|
// check if the original page was previously splitted before then skip.
|
||||||
if (imageFile.name!!.contains("__")) return true
|
if (imageFile.name!!.contains("__")) return true
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
ImageUtil.splitTallImage(imageFile, imageFilePath)
|
ImageUtil.splitTallImage(tmpDir, imageFile, filenamePrefix)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
false
|
false
|
||||||
|
@ -59,7 +59,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
|||||||
setLargeIcon(notificationBitmap)
|
setLargeIcon(notificationBitmap)
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
addAction(R.drawable.ic_close_24dp, context.getString(android.R.string.cancel), cancelIntent)
|
addAction(R.drawable.ic_close_24dp, context.getString(R.string.action_cancel), cancelIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ object Notifications {
|
|||||||
const val ID_DOWNLOAD_CHAPTER_COMPLETE = -203
|
const val ID_DOWNLOAD_CHAPTER_COMPLETE = -203
|
||||||
const val CHANNEL_DOWNLOADER_ERROR = "downloader_error_channel"
|
const val CHANNEL_DOWNLOADER_ERROR = "downloader_error_channel"
|
||||||
const val ID_DOWNLOAD_CHAPTER_ERROR = -202
|
const val ID_DOWNLOAD_CHAPTER_ERROR = -202
|
||||||
|
const val CHANNEL_DOWNLOADER_CACHE = "downloader_cache_renewal"
|
||||||
|
const val ID_DOWNLOAD_CACHE = -204
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification channel and ids used by the library updater.
|
* Notification channel and ids used by the library updater.
|
||||||
@ -159,6 +161,11 @@ object Notifications {
|
|||||||
setGroup(GROUP_DOWNLOADER)
|
setGroup(GROUP_DOWNLOADER)
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
},
|
},
|
||||||
|
buildNotificationChannel(CHANNEL_DOWNLOADER_CACHE, IMPORTANCE_LOW) {
|
||||||
|
setName(context.getString(R.string.channel_downloader_cache))
|
||||||
|
setGroup(GROUP_DOWNLOADER)
|
||||||
|
setShowBadge(false)
|
||||||
|
},
|
||||||
buildNotificationChannel(CHANNEL_BACKUP_RESTORE_PROGRESS, IMPORTANCE_LOW) {
|
buildNotificationChannel(CHANNEL_BACKUP_RESTORE_PROGRESS, IMPORTANCE_LOW) {
|
||||||
setName(context.getString(R.string.channel_progress))
|
setName(context.getString(R.string.channel_progress))
|
||||||
setGroup(GROUP_BACKUP_RESTORE)
|
setGroup(GROUP_BACKUP_RESTORE)
|
||||||
|
@ -180,8 +180,7 @@ class ExtensionManager(
|
|||||||
mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
|
mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
|
||||||
changed = true
|
changed = true
|
||||||
} else if (availableExt != null) {
|
} else if (availableExt != null) {
|
||||||
val hasUpdate = !installedExt.isUnofficial &&
|
val hasUpdate = installedExt.updateExists(availableExt)
|
||||||
availableExt.versionCode > installedExt.versionCode
|
|
||||||
|
|
||||||
if (installedExt.hasUpdate != hasUpdate) {
|
if (installedExt.hasUpdate != hasUpdate) {
|
||||||
mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate)
|
mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate)
|
||||||
@ -347,11 +346,18 @@ class ExtensionManager(
|
|||||||
* Extension method to set the update field of an installed extension.
|
* Extension method to set the update field of an installed extension.
|
||||||
*/
|
*/
|
||||||
private fun Extension.Installed.withUpdateCheck(): Extension.Installed {
|
private fun Extension.Installed.withUpdateCheck(): Extension.Installed {
|
||||||
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
return if (updateExists()) {
|
||||||
if (!isUnofficial && availableExt != null && availableExt.versionCode > versionCode) {
|
copy(hasUpdate = true)
|
||||||
return copy(hasUpdate = true)
|
} else {
|
||||||
|
this
|
||||||
}
|
}
|
||||||
return this
|
}
|
||||||
|
|
||||||
|
private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
|
||||||
|
val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
|
||||||
|
if (isUnofficial || availableExt == null) return false
|
||||||
|
|
||||||
|
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePendingUpdatesCount() {
|
private fun updatePendingUpdatesCount() {
|
||||||
|
@ -100,7 +100,7 @@ internal class ExtensionGithubApi {
|
|||||||
private fun List<ExtensionJsonObject>.toExtensions(): List<Extension.Available> {
|
private fun List<ExtensionJsonObject>.toExtensions(): List<Extension.Available> {
|
||||||
return this
|
return this
|
||||||
.filter {
|
.filter {
|
||||||
val libVersion = it.version.substringBeforeLast('.').toDouble()
|
val libVersion = it.extractLibVersion()
|
||||||
libVersion >= ExtensionLoader.LIB_VERSION_MIN && libVersion <= ExtensionLoader.LIB_VERSION_MAX
|
libVersion >= ExtensionLoader.LIB_VERSION_MIN && libVersion <= ExtensionLoader.LIB_VERSION_MAX
|
||||||
}
|
}
|
||||||
.map {
|
.map {
|
||||||
@ -109,6 +109,7 @@ internal class ExtensionGithubApi {
|
|||||||
pkgName = it.pkg,
|
pkgName = it.pkg,
|
||||||
versionName = it.version,
|
versionName = it.version,
|
||||||
versionCode = it.code,
|
versionCode = it.code,
|
||||||
|
libVersion = it.extractLibVersion(),
|
||||||
lang = it.lang,
|
lang = it.lang,
|
||||||
isNsfw = it.nsfw == 1,
|
isNsfw = it.nsfw == 1,
|
||||||
hasReadme = it.hasReadme == 1,
|
hasReadme = it.hasReadme == 1,
|
||||||
@ -142,6 +143,10 @@ internal class ExtensionGithubApi {
|
|||||||
REPO_URL_PREFIX
|
REPO_URL_PREFIX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ExtensionJsonObject.extractLibVersion(): Double {
|
||||||
|
return version.substringBeforeLast('.').toDouble()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/"
|
private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/"
|
||||||
|
@ -10,6 +10,7 @@ sealed class Extension {
|
|||||||
abstract val pkgName: String
|
abstract val pkgName: String
|
||||||
abstract val versionName: String
|
abstract val versionName: String
|
||||||
abstract val versionCode: Long
|
abstract val versionCode: Long
|
||||||
|
abstract val libVersion: Double
|
||||||
abstract val lang: String?
|
abstract val lang: String?
|
||||||
abstract val isNsfw: Boolean
|
abstract val isNsfw: Boolean
|
||||||
abstract val hasReadme: Boolean
|
abstract val hasReadme: Boolean
|
||||||
@ -20,6 +21,7 @@ sealed class Extension {
|
|||||||
override val pkgName: String,
|
override val pkgName: String,
|
||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Long,
|
override val versionCode: Long,
|
||||||
|
override val libVersion: Double,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
override val isNsfw: Boolean,
|
override val isNsfw: Boolean,
|
||||||
override val hasReadme: Boolean,
|
override val hasReadme: Boolean,
|
||||||
@ -37,6 +39,7 @@ sealed class Extension {
|
|||||||
override val pkgName: String,
|
override val pkgName: String,
|
||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Long,
|
override val versionCode: Long,
|
||||||
|
override val libVersion: Double,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
override val isNsfw: Boolean,
|
override val isNsfw: Boolean,
|
||||||
override val hasReadme: Boolean,
|
override val hasReadme: Boolean,
|
||||||
@ -51,6 +54,7 @@ sealed class Extension {
|
|||||||
override val pkgName: String,
|
override val pkgName: String,
|
||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Long,
|
override val versionCode: Long,
|
||||||
|
override val libVersion: Double,
|
||||||
val signatureHash: String,
|
val signatureHash: String,
|
||||||
override val lang: String? = null,
|
override val lang: String? = null,
|
||||||
override val isNsfw: Boolean = false,
|
override val isNsfw: Boolean = false,
|
||||||
|
@ -141,7 +141,7 @@ internal object ExtensionLoader {
|
|||||||
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
|
||||||
return LoadResult.Error
|
return LoadResult.Error
|
||||||
} else if (signatureHash !in trustedSignatures) {
|
} else if (signatureHash !in trustedSignatures) {
|
||||||
val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, signatureHash)
|
val extension = Extension.Untrusted(extName, pkgName, versionName, versionCode, libVersion, signatureHash)
|
||||||
logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" }
|
logcat(LogPriority.WARN) { "Extension $pkgName isn't trusted" }
|
||||||
return LoadResult.Untrusted(extension)
|
return LoadResult.Untrusted(extension)
|
||||||
}
|
}
|
||||||
@ -190,14 +190,15 @@ internal object ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val extension = Extension.Installed(
|
val extension = Extension.Installed(
|
||||||
extName,
|
name = extName,
|
||||||
pkgName,
|
pkgName = pkgName,
|
||||||
versionName,
|
versionName = versionName,
|
||||||
versionCode,
|
versionCode = versionCode,
|
||||||
lang,
|
libVersion = libVersion,
|
||||||
isNsfw,
|
lang = lang,
|
||||||
hasReadme,
|
isNsfw = isNsfw,
|
||||||
hasChangelog,
|
hasReadme = hasReadme,
|
||||||
|
hasChangelog = hasChangelog,
|
||||||
sources = sources,
|
sources = sources,
|
||||||
pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY),
|
pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY),
|
||||||
isUnofficial = signatureHash != officialSignature,
|
isUnofficial = signatureHash != officialSignature,
|
||||||
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class SourceManager(
|
class SourceManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@ -31,7 +32,7 @@ class SourceManager(
|
|||||||
|
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.IO)
|
private val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
|
||||||
private var sourcesMap = emptyMap<Long, Source>()
|
private var sourcesMap = ConcurrentHashMap<Long, Source>()
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
sourcesMapFlow.value = field
|
sourcesMapFlow.value = field
|
||||||
@ -39,7 +40,7 @@ class SourceManager(
|
|||||||
|
|
||||||
private val sourcesMapFlow = MutableStateFlow(sourcesMap)
|
private val sourcesMapFlow = MutableStateFlow(sourcesMap)
|
||||||
|
|
||||||
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
private val stubSourcesMap = ConcurrentHashMap<Long, StubSource>()
|
||||||
|
|
||||||
val catalogueSources: Flow<List<CatalogueSource>> = sourcesMapFlow.map { it.values.filterIsInstance<CatalogueSource>() }
|
val catalogueSources: Flow<List<CatalogueSource>> = sourcesMapFlow.map { it.values.filterIsInstance<CatalogueSource>() }
|
||||||
val onlineSources: Flow<List<HttpSource>> = catalogueSources.map { sources -> sources.filterIsInstance<HttpSource>() }
|
val onlineSources: Flow<List<HttpSource>> = catalogueSources.map { sources -> sources.filterIsInstance<HttpSource>() }
|
||||||
@ -48,7 +49,7 @@ class SourceManager(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
extensionManager.installedExtensionsFlow
|
extensionManager.installedExtensionsFlow
|
||||||
.collectLatest { extensions ->
|
.collectLatest { extensions ->
|
||||||
val mutableMap = mutableMapOf<Long, Source>(LocalSource.ID to LocalSource(context))
|
val mutableMap = ConcurrentHashMap<Long, Source>(mapOf(LocalSource.ID to LocalSource(context)))
|
||||||
extensions.forEach { extension ->
|
extensions.forEach { extension ->
|
||||||
extension.sources.forEach {
|
extension.sources.forEach {
|
||||||
mutableMap[it.id] = it
|
mutableMap[it.id] = it
|
||||||
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -35,6 +36,7 @@ class ExtensionFilterPresenter(
|
|||||||
logcat(LogPriority.ERROR, exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
_events.send(Event.FailedFetchingLanguages)
|
_events.send(Event.FailedFetchingLanguages)
|
||||||
}
|
}
|
||||||
|
.stateIn(presenterScope)
|
||||||
.collectLatest(::collectLatestSourceLangMap)
|
.collectLatest(::collectLatestSourceLangMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,6 @@ class SourceSearchController(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
// LocalContext is not a first available to us when we try access it
|
|
||||||
// Decoupling from BrowseSourceController is needed
|
|
||||||
val context = applicationContext!!
|
|
||||||
|
|
||||||
SourceSearchScreen(
|
SourceSearchScreen(
|
||||||
presenter = presenter,
|
presenter = presenter,
|
||||||
navigateUp = { router.popCurrentController() },
|
navigateUp = { router.popCurrentController() },
|
||||||
@ -46,8 +42,10 @@ class SourceSearchController(
|
|||||||
},
|
},
|
||||||
onWebViewClick = f@{
|
onWebViewClick = f@{
|
||||||
val source = presenter.source as? HttpSource ?: return@f
|
val source = presenter.source as? HttpSource ?: return@f
|
||||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
activity?.let { context ->
|
||||||
context.startActivity(intent)
|
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -39,6 +40,7 @@ class SourcesFilterPresenter(
|
|||||||
logcat(LogPriority.ERROR, exception)
|
logcat(LogPriority.ERROR, exception)
|
||||||
_events.send(Event.FailedFetchingLanguages)
|
_events.send(Event.FailedFetchingLanguages)
|
||||||
}
|
}
|
||||||
|
.stateIn(presenterScope)
|
||||||
.collectLatest(::collectLatestSourceLangMap)
|
.collectLatest(::collectLatestSourceLangMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
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.Pause
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
|
import androidx.compose.material.icons.outlined.Pause
|
||||||
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
|
||||||
@ -152,7 +152,7 @@ class DownloadController :
|
|||||||
IconButton(onClick = { onExpanded(!expanded) }) {
|
IconButton(onClick = { onExpanded(!expanded) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.MoreVert,
|
imageVector = Icons.Outlined.MoreVert,
|
||||||
contentDescription = stringResource(R.string.label_more),
|
contentDescription = stringResource(R.string.abc_action_menu_overflow_description),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CascadeDropdownMenu(
|
CascadeDropdownMenu(
|
||||||
@ -234,9 +234,9 @@ class DownloadController :
|
|||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
val icon = if (isRunning) {
|
val icon = if (isRunning) {
|
||||||
Icons.Default.Pause
|
Icons.Outlined.Pause
|
||||||
} else {
|
} else {
|
||||||
Icons.Default.PlayArrow
|
Icons.Filled.PlayArrow
|
||||||
}
|
}
|
||||||
Icon(imageVector = icon, contentDescription = null)
|
Icon(imageVector = icon, contentDescription = null)
|
||||||
},
|
},
|
||||||
|
@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.ui.category.CategoryController
|
|||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
|
||||||
@ -126,7 +127,6 @@ class LibraryController(
|
|||||||
settingsSheet = LibrarySettingsSheet(router) { group ->
|
settingsSheet = LibrarySettingsSheet(router) { group ->
|
||||||
when (group) {
|
when (group) {
|
||||||
is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
|
is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
|
||||||
is LibrarySettingsSheet.Sort.SortGroup -> onSortChanged()
|
|
||||||
else -> {} // Handled via different mechanisms
|
else -> {} // Handled via different mechanisms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,12 +152,10 @@ class LibraryController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onFilterChanged() {
|
private fun onFilterChanged() {
|
||||||
presenter.requestFilterUpdate()
|
viewScope.launchUI {
|
||||||
activity?.invalidateOptionsMenu()
|
presenter.requestFilterUpdate()
|
||||||
}
|
activity?.invalidateOptionsMenu()
|
||||||
|
}
|
||||||
private fun onSortChanged() {
|
|
||||||
presenter.requestSortUpdate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String) {
|
fun search(query: String) {
|
||||||
@ -180,7 +178,7 @@ class LibraryController(
|
|||||||
* Clear all of the manga currently selected, and
|
* Clear all of the manga currently selected, and
|
||||||
* invalidate the action mode to revert the top toolbar
|
* invalidate the action mode to revert the top toolbar
|
||||||
*/
|
*/
|
||||||
fun clearSelection() {
|
private fun clearSelection() {
|
||||||
presenter.clearSelection()
|
presenter.clearSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,8 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
|
||||||
import eu.kanade.core.prefs.CheckboxState
|
import eu.kanade.core.prefs.CheckboxState
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import eu.kanade.core.util.asFlow
|
|
||||||
import eu.kanade.core.util.asObservable
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||||
@ -32,7 +29,7 @@ import eu.kanade.domain.manga.interactor.UpdateManga
|
|||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.manga.model.MangaUpdate
|
import eu.kanade.domain.manga.model.MangaUpdate
|
||||||
import eu.kanade.domain.manga.model.isLocal
|
import eu.kanade.domain.manga.model.isLocal
|
||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.GetTracksPerManga
|
||||||
import eu.kanade.presentation.category.visualName
|
import eu.kanade.presentation.category.visualName
|
||||||
import eu.kanade.presentation.library.LibraryState
|
import eu.kanade.presentation.library.LibraryState
|
||||||
import eu.kanade.presentation.library.LibraryStateImpl
|
import eu.kanade.presentation.library.LibraryStateImpl
|
||||||
@ -49,16 +46,16 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.onStart
|
||||||
import rx.Observable
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
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.Collator
|
import java.text.Collator
|
||||||
@ -79,7 +76,7 @@ typealias LibraryMap = Map<Long, List<LibraryItem>>
|
|||||||
class LibraryPresenter(
|
class LibraryPresenter(
|
||||||
private val state: LibraryStateImpl = LibraryState() as LibraryStateImpl,
|
private val state: LibraryStateImpl = LibraryState() as LibraryStateImpl,
|
||||||
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
||||||
private val getTracks: GetTracks = Injekt.get(),
|
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
||||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||||
@ -111,15 +108,8 @@ class LibraryPresenter(
|
|||||||
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
||||||
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
||||||
|
|
||||||
/**
|
private val _filterChanges: Channel<Unit> = Channel(Int.MAX_VALUE)
|
||||||
* Relay used to apply the UI filters to the last emission of the library.
|
private val filterChanges = _filterChanges.receiveAsFlow().onStart { emit(Unit) }
|
||||||
*/
|
|
||||||
private val filterTriggerRelay = BehaviorRelay.create(Unit)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relay used to apply the selected sorting method to the last emission of the library.
|
|
||||||
*/
|
|
||||||
private val sortTriggerRelay = BehaviorRelay.create(Unit)
|
|
||||||
|
|
||||||
private var librarySubscription: Job? = null
|
private var librarySubscription: Job? = null
|
||||||
|
|
||||||
@ -129,30 +119,23 @@ class LibraryPresenter(
|
|||||||
subscribeLibrary()
|
subscribeLibrary()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to library if needed.
|
|
||||||
*/
|
|
||||||
fun subscribeLibrary() {
|
fun subscribeLibrary() {
|
||||||
/**
|
/**
|
||||||
* TODO: Move this to a coroutine world
|
* TODO:
|
||||||
* - Move filter and sort to getMangaForCategory and only filter and sort the current display category instead of whole library as some has 5000+ items in the library
|
* - Move filter and sort to getMangaForCategory and only filter and sort the current display category instead of whole library as some has 5000+ items in the library
|
||||||
* - Create new db view and new query to just fetch the current category save as needed to instance variable
|
* - Create new db view and new query to just fetch the current category save as needed to instance variable
|
||||||
* - Fetch badges to maps and retrieve as needed instead of fetching all of them at once
|
* - Fetch badges to maps and retrieve as needed instead of fetching all of them at once
|
||||||
*/
|
*/
|
||||||
if (librarySubscription == null || librarySubscription!!.isCancelled) {
|
if (librarySubscription == null || librarySubscription!!.isCancelled) {
|
||||||
librarySubscription = presenterScope.launchIO {
|
librarySubscription = presenterScope.launchIO {
|
||||||
getLibraryFlow().asObservable()
|
combine(getLibraryFlow(), getTracksPerManga.subscribe(), filterChanges) { library, tracks, _ ->
|
||||||
.combineLatest(getFilterObservable()) { lib, tracks ->
|
library.mangaMap
|
||||||
lib.copy(mangaMap = applyFilters(lib.mangaMap, tracks))
|
.applyFilters(tracks)
|
||||||
}
|
.applySort(library.categories)
|
||||||
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
|
}
|
||||||
lib.copy(mangaMap = applySort(lib.categories, lib.mangaMap))
|
|
||||||
}
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.asFlow()
|
|
||||||
.collectLatest {
|
.collectLatest {
|
||||||
state.isLoading = false
|
state.isLoading = false
|
||||||
loadedManga = it.mangaMap
|
loadedManga = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,21 +143,24 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies library filters to the given map of manga.
|
* Applies library filters to the given map of manga.
|
||||||
*
|
|
||||||
* @param map the map to filter.
|
|
||||||
*/
|
*/
|
||||||
private fun applyFilters(map: LibraryMap, trackMap: Map<Long, Map<Long, Boolean>>): LibraryMap {
|
private fun LibraryMap.applyFilters(trackMap: Map<Long, List<Long>>): LibraryMap {
|
||||||
val downloadedOnly = preferences.downloadedOnly().get()
|
val downloadedOnly = preferences.downloadedOnly().get()
|
||||||
val filterDownloaded = libraryPreferences.filterDownloaded().get()
|
val filterDownloaded = libraryPreferences.filterDownloaded().get()
|
||||||
val filterUnread = libraryPreferences.filterUnread().get()
|
val filterUnread = libraryPreferences.filterUnread().get()
|
||||||
val filterStarted = libraryPreferences.filterStarted().get()
|
val filterStarted = libraryPreferences.filterStarted().get()
|
||||||
val filterBookmarked = libraryPreferences.filterBookmarked().get()
|
val filterBookmarked = libraryPreferences.filterBookmarked().get()
|
||||||
val filterCompleted = libraryPreferences.filterCompleted().get()
|
val filterCompleted = libraryPreferences.filterCompleted().get()
|
||||||
val loggedInServices = trackManager.services.filter { trackService -> trackService.isLogged }
|
|
||||||
|
val loggedInTrackServices = trackManager.services.filter { trackService -> trackService.isLogged }
|
||||||
.associate { trackService ->
|
.associate { trackService ->
|
||||||
Pair(trackService.id, libraryPreferences.filterTracking(trackService.id.toInt()).get())
|
trackService.id to libraryPreferences.filterTracking(trackService.id.toInt()).get()
|
||||||
}
|
}
|
||||||
val isNotAnyLoggedIn = !loggedInServices.values.any()
|
val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty()
|
||||||
|
|
||||||
|
val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == State.EXCLUDE.value) it.key else null }
|
||||||
|
val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == State.INCLUDE.value) it.key else null }
|
||||||
|
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
|
||||||
|
|
||||||
val filterFnDownloaded: (LibraryItem) -> Boolean = downloaded@{ item ->
|
val filterFnDownloaded: (LibraryItem) -> Boolean = downloaded@{ item ->
|
||||||
if (!downloadedOnly && filterDownloaded == State.IGNORE.value) return@downloaded true
|
if (!downloadedOnly && filterDownloaded == State.IGNORE.value) return@downloaded true
|
||||||
@ -237,25 +223,21 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val filterFnTracking: (LibraryItem) -> Boolean = tracking@{ item ->
|
val filterFnTracking: (LibraryItem) -> Boolean = tracking@{ item ->
|
||||||
if (isNotAnyLoggedIn) return@tracking true
|
if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true
|
||||||
|
|
||||||
val trackedManga = trackMap[item.libraryManga.manga.id]
|
val mangaTracks = trackMap[item.libraryManga.id].orEmpty()
|
||||||
|
|
||||||
val containsExclude = loggedInServices.filterValues { it == State.EXCLUDE.value }
|
val exclude = mangaTracks.filter { it in excludedTracks }
|
||||||
val containsInclude = loggedInServices.filterValues { it == State.INCLUDE.value }
|
val include = mangaTracks.filter { it in includedTracks }
|
||||||
|
|
||||||
if (!containsExclude.any() && !containsInclude.any()) return@tracking true
|
// TODO: Simplify the filter logic
|
||||||
|
if (includedTracks.isNotEmpty() && excludedTracks.isNotEmpty()) {
|
||||||
val exclude = trackedManga?.filterKeys { containsExclude.containsKey(it) }?.values ?: emptyList()
|
return@tracking if (exclude.isNotEmpty()) false else include.isNotEmpty()
|
||||||
val include = trackedManga?.filterKeys { containsInclude.containsKey(it) }?.values ?: emptyList()
|
|
||||||
|
|
||||||
if (containsInclude.any() && containsExclude.any()) {
|
|
||||||
return@tracking if (exclude.isNotEmpty()) !exclude.any() else include.any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (containsExclude.any()) return@tracking !exclude.any()
|
if (excludedTracks.isNotEmpty()) return@tracking exclude.isEmpty()
|
||||||
|
|
||||||
if (containsInclude.any()) return@tracking include.any()
|
if (includedTracks.isNotEmpty()) return@tracking include.isNotEmpty()
|
||||||
|
|
||||||
return@tracking false
|
return@tracking false
|
||||||
}
|
}
|
||||||
@ -271,26 +253,28 @@ class LibraryPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return map.mapValues { entry -> entry.value.filter(filterFn) }
|
return this.mapValues { entry -> entry.value.filter(filterFn) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies library sorting to the given map of manga.
|
* Applies library sorting to the given map of manga.
|
||||||
*
|
|
||||||
* @param map the map to sort.
|
|
||||||
*/
|
*/
|
||||||
private fun applySort(categories: List<Category>, map: LibraryMap): LibraryMap {
|
private fun LibraryMap.applySort(categories: List<Category>): LibraryMap {
|
||||||
val sortModes = categories.associate { it.id to it.sort }
|
val sortModes = categories.associate { it.id to it.sort }
|
||||||
|
|
||||||
val locale = Locale.getDefault()
|
val locale = Locale.getDefault()
|
||||||
val collator = Collator.getInstance(locale).apply {
|
val collator = Collator.getInstance(locale).apply {
|
||||||
strength = Collator.PRIMARY
|
strength = Collator.PRIMARY
|
||||||
}
|
}
|
||||||
|
val sortAlphabetically: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
||||||
|
collator.compare(i1.libraryManga.manga.title.lowercase(locale), i2.libraryManga.manga.title.lowercase(locale))
|
||||||
|
}
|
||||||
|
|
||||||
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
||||||
val sort = sortModes[i1.libraryManga.category]!!
|
val sort = sortModes[i1.libraryManga.category]!!
|
||||||
when (sort.type) {
|
when (sort.type) {
|
||||||
LibrarySort.Type.Alphabetical -> {
|
LibrarySort.Type.Alphabetical -> {
|
||||||
collator.compare(i1.libraryManga.manga.title.lowercase(locale), i2.libraryManga.manga.title.lowercase(locale))
|
sortAlphabetically(i1, i2)
|
||||||
}
|
}
|
||||||
LibrarySort.Type.LastRead -> {
|
LibrarySort.Type.LastRead -> {
|
||||||
i1.libraryManga.lastRead.compareTo(i2.libraryManga.lastRead)
|
i1.libraryManga.lastRead.compareTo(i2.libraryManga.lastRead)
|
||||||
@ -321,14 +305,14 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return map.mapValues { entry ->
|
return this.mapValues { entry ->
|
||||||
val comparator = if (sortModes[entry.key]!!.isAscending) {
|
val comparator = if (sortModes[entry.key]!!.isAscending) {
|
||||||
Comparator(sortFn)
|
Comparator(sortFn)
|
||||||
} else {
|
} else {
|
||||||
Collections.reverseOrder(sortFn)
|
Collections.reverseOrder(sortFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.value.sortedWith(comparator)
|
entry.value.sortedWith(comparator.thenComparator(sortAlphabetically))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,13 +326,18 @@ class LibraryPresenter(
|
|||||||
getLibraryManga.subscribe(),
|
getLibraryManga.subscribe(),
|
||||||
libraryPreferences.downloadBadge().changes(),
|
libraryPreferences.downloadBadge().changes(),
|
||||||
libraryPreferences.filterDownloaded().changes(),
|
libraryPreferences.filterDownloaded().changes(),
|
||||||
|
preferences.downloadedOnly().changes(),
|
||||||
downloadCache.changes,
|
downloadCache.changes,
|
||||||
) { libraryMangaList, downloadBadgePref, filterDownloadedPref, _ ->
|
) { libraryMangaList, downloadBadgePref, filterDownloadedPref, downloadedOnly, _ ->
|
||||||
libraryMangaList
|
libraryMangaList
|
||||||
.map { libraryManga ->
|
.map { libraryManga ->
|
||||||
|
val needsDownloadCounts = downloadBadgePref ||
|
||||||
|
filterDownloadedPref != State.IGNORE.value ||
|
||||||
|
downloadedOnly
|
||||||
|
|
||||||
// Display mode based on user preference: take it from global library setting or category
|
// Display mode based on user preference: take it from global library setting or category
|
||||||
LibraryItem(libraryManga).apply {
|
LibraryItem(libraryManga).apply {
|
||||||
downloadCount = if (downloadBadgePref || filterDownloadedPref == State.INCLUDE.value) {
|
downloadCount = if (needsDownloadCounts) {
|
||||||
downloadManager.getDownloadCount(libraryManga.manga).toLong()
|
downloadManager.getDownloadCount(libraryManga.manga).toLong()
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
@ -373,48 +362,11 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the tracked manga from the database and checks if the filter gets changed
|
|
||||||
*
|
|
||||||
* @return an observable of tracked manga.
|
|
||||||
*/
|
|
||||||
private fun getFilterObservable(): Observable<Map<Long, Map<Long, Boolean>>> {
|
|
||||||
return filterTriggerRelay.observeOn(Schedulers.io())
|
|
||||||
.combineLatest(getTracksFlow().asObservable().observeOn(Schedulers.io())) { _, tracks -> tracks }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the tracked manga from the database
|
|
||||||
*
|
|
||||||
* @return an observable of tracked manga.
|
|
||||||
*/
|
|
||||||
private fun getTracksFlow(): Flow<Map<Long, Map<Long, Boolean>>> {
|
|
||||||
// TODO: Move this to domain/data layer
|
|
||||||
return getTracks.subscribe()
|
|
||||||
.map { tracks ->
|
|
||||||
tracks
|
|
||||||
.groupBy { it.mangaId }
|
|
||||||
.mapValues { tracksForMangaId ->
|
|
||||||
// Check if any of the trackers is logged in for the current manga id
|
|
||||||
tracksForMangaId.value.associate {
|
|
||||||
Pair(it.syncId, trackManager.getService(it.syncId)?.isLogged ?: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the library to be filtered.
|
* Requests the library to be filtered.
|
||||||
*/
|
*/
|
||||||
fun requestFilterUpdate() {
|
suspend fun requestFilterUpdate() = withIOContext {
|
||||||
filterTriggerRelay.call(Unit)
|
_filterChanges.send(Unit)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the library to be sorted.
|
|
||||||
*/
|
|
||||||
fun requestSortUpdate() {
|
|
||||||
sortTriggerRelay.call(Unit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -432,9 +384,9 @@ class LibraryPresenter(
|
|||||||
*/
|
*/
|
||||||
suspend fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
suspend fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
|
||||||
if (mangas.isEmpty()) return emptyList()
|
if (mangas.isEmpty()) return emptyList()
|
||||||
return mangas.toSet()
|
return mangas
|
||||||
.map { getCategories.await(it.id) }
|
.map { getCategories.await(it.id).toSet() }
|
||||||
.reduce { set1, set2 -> set1.intersect(set2).toMutableList() }
|
.reduce { set1, set2 -> set1.intersect(set2) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -444,9 +396,9 @@ class LibraryPresenter(
|
|||||||
*/
|
*/
|
||||||
suspend fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
suspend fun getMixCategories(mangas: List<Manga>): Collection<Category> {
|
||||||
if (mangas.isEmpty()) return emptyList()
|
if (mangas.isEmpty()) return emptyList()
|
||||||
val mangaCategories = mangas.toSet().map { getCategories.await(it.id) }
|
val mangaCategories = mangas.map { getCategories.await(it.id).toSet() }
|
||||||
val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2).toMutableList() }
|
val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2) }
|
||||||
return mangaCategories.flatten().distinct().subtract(common).toMutableList()
|
return mangaCategories.flatten().distinct().subtract(common)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -524,10 +476,10 @@ class LibraryPresenter(
|
|||||||
*/
|
*/
|
||||||
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
||||||
presenterScope.launchNonCancellable {
|
presenterScope.launchNonCancellable {
|
||||||
mangaList.map { manga ->
|
mangaList.forEach { manga ->
|
||||||
val categoryIds = getCategories.await(manga.id)
|
val categoryIds = getCategories.await(manga.id)
|
||||||
.map { it.id }
|
.map { it.id }
|
||||||
.subtract(removeCategories)
|
.subtract(removeCategories.toSet())
|
||||||
.plus(addCategories)
|
.plus(addCategories)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
@ -649,10 +601,6 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T, U, R> Observable<T>.combineLatest(o2: Observable<U>, combineFn: (T, U) -> R): Observable<R> {
|
|
||||||
return Observable.combineLatest(this, o2, combineFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Dialog {
|
sealed class Dialog {
|
||||||
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
|
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
|
||||||
data class DeleteManga(val manga: List<Manga>) : Dialog()
|
data class DeleteManga(val manga: List<Manga>) : Dialog()
|
||||||
|
@ -48,7 +48,7 @@ class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
|
|||||||
|
|
||||||
activity?.toast(R.string.chapter_settings_updated)
|
activity?.toast(R.string.chapter_settings_updated)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class SetTrackChaptersDialog<T> : DialogController
|
|||||||
np.clearFocus()
|
np.clearFocus()
|
||||||
listener.setChaptersRead(item, np.value)
|
listener.setChaptersRead(item, np.value)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class SetTrackScoreDialog<T> : DialogController
|
|||||||
np.clearFocus()
|
np.clearFocus()
|
||||||
listener.setScore(item, np.value)
|
listener.setScore(item, np.value)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class SetTrackStatusDialog<T> : DialogController
|
|||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
listener.setStatus(item, selectedIndex)
|
listener.setStatus(item, selectedIndex)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|||||||
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
import eu.kanade.tachiyomi.util.preference.toggle
|
import eu.kanade.tachiyomi.util.preference.toggle
|
||||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||||
@ -275,6 +276,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
*/
|
*/
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
|
R.id.action_open_in_web_view -> {
|
||||||
|
openChapterInWebview()
|
||||||
|
}
|
||||||
R.id.action_bookmark -> {
|
R.id.action_bookmark -> {
|
||||||
presenter.bookmarkCurrentChapter(true)
|
presenter.bookmarkCurrentChapter(true)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
@ -665,6 +669,15 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
startPostponedEnterTransition()
|
startPostponedEnterTransition()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openChapterInWebview() {
|
||||||
|
val manga = presenter.manga ?: return
|
||||||
|
val source = presenter.getSource() ?: return
|
||||||
|
val url = presenter.getChapterUrl() ?: return
|
||||||
|
|
||||||
|
val intent = WebViewActivity.newIntent(this, url, source.id, manga.title)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showReadingModeToast(mode: Int) {
|
private fun showReadingModeToast(mode: Int) {
|
||||||
try {
|
try {
|
||||||
val strings = resources.getStringArray(R.array.viewers_selector)
|
val strings = resources.getStringArray(R.array.viewers_selector)
|
||||||
|
@ -40,7 +40,7 @@ class ReaderPageSheet(
|
|||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
activity.setAsCover(page)
|
activity.setAsCover(page)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
|
|||||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
|
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
|
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
|
||||||
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
|
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
|
||||||
@ -606,6 +607,15 @@ class ReaderPresenter(
|
|||||||
return viewerChaptersRelay.value?.currChapter
|
return viewerChaptersRelay.value?.currChapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSource() = manga?.source?.let { sourceManager.getOrStub(it) } as? HttpSource
|
||||||
|
|
||||||
|
fun getChapterUrl(): String? {
|
||||||
|
val sChapter = getCurrentChapter()?.chapter ?: return null
|
||||||
|
val source = getSource() ?: return null
|
||||||
|
|
||||||
|
return source.getChapterUrl(sChapter)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bookmarks the currently active chapter.
|
* Bookmarks the currently active chapter.
|
||||||
*/
|
*/
|
||||||
|
@ -27,8 +27,6 @@ import tachiyomi.decoder.ImageDecoder
|
|||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@ -202,7 +200,7 @@ object ImageUtil {
|
|||||||
/**
|
/**
|
||||||
* Splits tall images to improve performance of reader
|
* Splits tall images to improve performance of reader
|
||||||
*/
|
*/
|
||||||
fun splitTallImage(imageFile: UniFile, imageFilePath: String): Boolean {
|
fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String): Boolean {
|
||||||
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
|
if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -221,11 +219,15 @@ object ImageUtil {
|
|||||||
|
|
||||||
return try {
|
return try {
|
||||||
splitDataList.forEach { splitData ->
|
splitDataList.forEach { splitData ->
|
||||||
val splitPath = splitImagePath(imageFilePath, splitData.index)
|
val splitImageName = splitImageName(filenamePrefix, splitData.index)
|
||||||
|
// Remove pre-existing split if exists (this split shouldn't exist under normal circumstances)
|
||||||
|
tmpDir.findFile(splitImageName)?.delete()
|
||||||
|
|
||||||
|
val splitFile = tmpDir.createFile(splitImageName)
|
||||||
|
|
||||||
val region = Rect(0, splitData.topOffset, splitData.splitWidth, splitData.bottomOffset)
|
val region = Rect(0, splitData.topOffset, splitData.splitWidth, splitData.bottomOffset)
|
||||||
|
|
||||||
FileOutputStream(splitPath).use { outputStream ->
|
splitFile.openOutputStream().use { outputStream ->
|
||||||
val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
|
val splitBitmap = bitmapRegionDecoder.decodeRegion(region, options)
|
||||||
splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
splitBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||||
splitBitmap.recycle()
|
splitBitmap.recycle()
|
||||||
@ -240,8 +242,8 @@ object ImageUtil {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Image splits were not successfully saved so delete them and keep the original image
|
// Image splits were not successfully saved so delete them and keep the original image
|
||||||
splitDataList
|
splitDataList
|
||||||
.map { splitImagePath(imageFilePath, it.index) }
|
.map { splitImageName(filenamePrefix, it.index) }
|
||||||
.forEach { File(it).delete() }
|
.forEach { tmpDir.findFile(it)?.delete() }
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
false
|
false
|
||||||
} finally {
|
} finally {
|
||||||
@ -249,8 +251,7 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun splitImagePath(imageFilePath: String, index: Int) =
|
private fun splitImageName(filenamePrefix: String, index: Int) = "${filenamePrefix}__${"%03d".format(index + 1)}.jpg"
|
||||||
imageFilePath.substringBeforeLast(".") + "__${"%03d".format(index + 1)}.jpg"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the image is a long Strip that needs splitting
|
* Check whether the image is a long Strip that needs splitting
|
||||||
|
7
app/src/main/res/drawable/ic_webview_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_webview_24dp.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z"/>
|
||||||
|
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user