mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
chore: merge upstream.
This commit is contained in:
commit
d0eaf5e3cb
@ -1,4 +1,5 @@
|
|||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
|
max_line_length = 120
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
ij_kotlin_allow_trailing_comma = true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
|
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.6)
|
- To the latest version of the app (stable is v0.14.7)
|
||||||
- All extensions
|
- All extensions
|
||||||
- I have gone through the FAQ (https://tachiyomi.org/docs/faq/general) and troubleshooting guide (https://tachiyomi.org/docs/guides/troubleshooting/)
|
- I have gone through the FAQ (https://tachiyomi.org/docs/faq/general) and troubleshooting guide (https://tachiyomi.org/docs/guides/troubleshooting/)
|
||||||
- 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.6"
|
Example: "0.14.7"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://tachiyomi.org/docs/faq/general) and [troubleshooting guide](https://tachiyomi.org/docs/guides/troubleshooting/).
|
- label: I have gone through the [FAQ](https://tachiyomi.org/docs/faq/general) and [troubleshooting guide](https://tachiyomi.org/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.14.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.14.7](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.6](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.14.7](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
|
||||||
|
@ -22,8 +22,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.kanade.tachiyomi"
|
applicationId = "eu.kanade.tachiyomi"
|
||||||
|
|
||||||
versionCode = 107
|
versionCode = 109
|
||||||
versionName = "0.14.6"
|
versionName = "0.14.7"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -304,12 +304,12 @@ tasks {
|
|||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics",
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics",
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,10 +159,6 @@
|
|||||||
android:name=".data.download.DownloadService"
|
android:name=".data.download.DownloadService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".data.updater.AppUpdateService"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".extension.util.ExtensionInstallService"
|
android:name=".extension.util.ExtensionInstallService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package eu.kanade.domain
|
package eu.kanade.domain
|
||||||
|
|
||||||
|
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
|
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
||||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
@ -40,8 +43,8 @@ import tachiyomi.domain.category.interactor.SetSortModeForCategory
|
|||||||
import tachiyomi.domain.category.interactor.UpdateCategory
|
import tachiyomi.domain.category.interactor.UpdateCategory
|
||||||
import tachiyomi.domain.category.repository.CategoryRepository
|
import tachiyomi.domain.category.repository.CategoryRepository
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapter
|
import tachiyomi.domain.chapter.interactor.GetChapter
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
@ -112,13 +115,15 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get(), get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
|
||||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
addFactory { GetApplicationRelease(get(), get()) }
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
|
||||||
addFactory { TrackChapter(get(), get(), get(), get()) }
|
addFactory { TrackChapter(get(), get(), get(), get()) }
|
||||||
addFactory { AddTracks(get(), get(), get()) }
|
addFactory { AddTracks(get(), get(), get(), get()) }
|
||||||
addFactory { RefreshTracks(get(), get(), get(), get()) }
|
addFactory { RefreshTracks(get(), get(), get(), get()) }
|
||||||
addFactory { DeleteTrack(get()) }
|
addFactory { DeleteTrack(get()) }
|
||||||
addFactory { GetTracksPerManga(get()) }
|
addFactory { GetTracksPerManga(get()) }
|
||||||
@ -128,12 +133,13 @@ class DomainModule : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
addFactory { GetChapter(get()) }
|
addFactory { GetChapter(get()) }
|
||||||
addFactory { GetChapterByMangaId(get()) }
|
addFactory { GetChaptersByMangaId(get()) }
|
||||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
|
@ -5,6 +5,7 @@ import androidx.annotation.StringRes
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
@ -12,9 +13,12 @@ class BasePreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
|
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||||
|
Preference.appStateKey("pref_downloaded_only"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package eu.kanade.domain.chapter.interactor
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
|
|
||||||
|
class GetAvailableScanlators(
|
||||||
|
private val repository: ChapterRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private fun List<String>.cleanupAvailableScanlators(): Set<String> {
|
||||||
|
return mapNotNull { it.ifBlank { null } }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long): Set<String> {
|
||||||
|
return repository.getScanlatorsByMangaId(mangaId)
|
||||||
|
.cleanupAvailableScanlators()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||||
|
return repository.getScanlatorsByMangaIdAsFlow(mangaId)
|
||||||
|
.map { it.cleanupAvailableScanlators() }
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package eu.kanade.domain.chapter.interactor
|
|||||||
|
|
||||||
import eu.kanade.domain.chapter.model.copyFromSChapter
|
import eu.kanade.domain.chapter.model.copyFromSChapter
|
||||||
import eu.kanade.domain.chapter.model.toSChapter
|
import eu.kanade.domain.chapter.model.toSChapter
|
||||||
|
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
@ -10,7 +11,7 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import tachiyomi.data.chapter.ChapterSanitizer
|
import tachiyomi.data.chapter.ChapterSanitizer
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
|
||||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
@ -32,7 +33,8 @@ class SyncChaptersWithSource(
|
|||||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||||
private val updateManga: UpdateManga,
|
private val updateManga: UpdateManga,
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,7 +68,7 @@ class SyncChaptersWithSource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Chapters from db.
|
// Chapters from db.
|
||||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||||
|
|
||||||
// Chapters from the source not in db.
|
// Chapters from the source not in db.
|
||||||
val toAdd = mutableListOf<Chapter>()
|
val toAdd = mutableListOf<Chapter>()
|
||||||
@ -116,7 +118,9 @@ class SyncChaptersWithSource(
|
|||||||
} else {
|
} else {
|
||||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
downloadManager.isChapterDownloaded(
|
||||||
|
dbChapter.name, dbChapter.scanlator, manga.title, manga.source,
|
||||||
|
)
|
||||||
|
|
||||||
if (shouldRenameChapter) {
|
if (shouldRenameChapter) {
|
||||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||||
@ -206,6 +210,10 @@ class SyncChaptersWithSource(
|
|||||||
|
|
||||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||||
|
|
||||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||||
|
|
||||||
|
return updatedToAdd.filterNot {
|
||||||
|
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package eu.kanade.domain.chapter.model
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import tachiyomi.data.Chapters
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||||
|
|
||||||
@ -23,18 +22,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
|||||||
url = sChapter.url,
|
url = sChapter.url,
|
||||||
dateUpload = sChapter.date_upload,
|
dateUpload = sChapter.date_upload,
|
||||||
chapterNumber = sChapter.chapter_number.toDouble(),
|
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Chapter.copyFrom(other: Chapters): Chapter {
|
|
||||||
return copy(
|
|
||||||
name = other.name,
|
|
||||||
url = other.url,
|
|
||||||
dateUpload = other.date_upload,
|
|
||||||
chapterNumber = other.chapter_number,
|
|
||||||
scanlator = other.scanlator?.ifBlank { null },
|
|
||||||
lastModifiedAt = other.last_modified_at,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.domain.chapter.model
|
|||||||
|
|
||||||
import eu.kanade.domain.manga.model.downloadedFilter
|
import eu.kanade.domain.manga.model.downloadedFilter
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.service.getChapterSort
|
import tachiyomi.domain.chapter.service.getChapterSort
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
@ -23,7 +23,12 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
|
|||||||
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||||
.filter { chapter ->
|
.filter { chapter ->
|
||||||
applyFilter(downloadedFilter) {
|
applyFilter(downloadedFilter) {
|
||||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
val downloaded = downloadManager.isChapterDownloaded(
|
||||||
|
chapter.name,
|
||||||
|
chapter.scanlator,
|
||||||
|
manga.title,
|
||||||
|
manga.source,
|
||||||
|
)
|
||||||
downloaded || isLocalManga
|
downloaded || isLocalManga
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +39,7 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager):
|
|||||||
* Applies the view filters to the list of chapters obtained from the database.
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
* @return an observable of the list of chapters filtered and sorted.
|
* @return an observable of the list of chapters filtered and sorted.
|
||||||
*/
|
*/
|
||||||
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||||
val isLocalManga = manga.isLocal()
|
val isLocalManga = manga.isLocal()
|
||||||
val unreadFilter = manga.unreadFilter
|
val unreadFilter = manga.unreadFilter
|
||||||
val downloadedFilter = manga.downloadedFilter
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.data.DatabaseHandler
|
||||||
|
|
||||||
|
class GetExcludedScanlators(
|
||||||
|
private val handler: DatabaseHandler,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long): Set<String> {
|
||||||
|
return handler.awaitList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||||
|
}
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||||
|
return handler.subscribeToList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||||
|
}
|
||||||
|
.map { it.toSet() }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
|
import tachiyomi.data.DatabaseHandler
|
||||||
|
|
||||||
|
class SetExcludedScanlators(
|
||||||
|
private val handler: DatabaseHandler,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
|
||||||
|
handler.await(inTransaction = true) {
|
||||||
|
val currentExcluded = handler.awaitList {
|
||||||
|
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||||
|
}.toSet()
|
||||||
|
val toAdd = excludedScanlators.minus(currentExcluded)
|
||||||
|
for (scanlator in toAdd) {
|
||||||
|
excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||||
|
}
|
||||||
|
val toRemove = currentExcluded.minus(excludedScanlators)
|
||||||
|
excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.domain.manga.interactor
|
package eu.kanade.domain.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
|
||||||
@ -9,22 +9,22 @@ class SetMangaViewerFlags(
|
|||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
suspend fun awaitSetReadingMode(id: Long, flag: Long) {
|
||||||
val manga = mangaRepository.getMangaById(id)
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingMode.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
suspend fun awaitSetOrientation(id: Long, flag: Long) {
|
||||||
val manga = mangaRepository.getMangaById(id)
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.update(
|
mangaRepository.update(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
id = id,
|
||||||
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package eu.kanade.domain.manga.model
|
|||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
@ -14,11 +14,11 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
// TODO: move these into the domain model
|
// TODO: move these into the domain model
|
||||||
val Manga.readingModeType: Long
|
val Manga.readingMode: Long
|
||||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||||
|
|
||||||
val Manga.orientationType: Long
|
val Manga.readerOrientation: Long
|
||||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||||
|
|
||||||
val Manga.downloadedFilter: TriState
|
val Manga.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
|
@ -3,12 +3,11 @@ package eu.kanade.domain.source.interactor
|
|||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.core.util.lang.compareToWithCollator
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.domain.source.repository.SourceRepository
|
import tachiyomi.domain.source.repository.SourceRepository
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.text.Collator
|
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class GetSourcesWithFavoriteCount(
|
class GetSourcesWithFavoriteCount(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
@ -31,17 +30,13 @@ class GetSourcesWithFavoriteCount(
|
|||||||
direction: SetMigrateSorting.Direction,
|
direction: SetMigrateSorting.Direction,
|
||||||
sorting: SetMigrateSorting.Mode,
|
sorting: SetMigrateSorting.Mode,
|
||||||
): java.util.Comparator<Pair<Source, Long>> {
|
): java.util.Comparator<Pair<Source, Long>> {
|
||||||
val locale = Locale.getDefault()
|
|
||||||
val collator = Collator.getInstance(locale).apply {
|
|
||||||
strength = Collator.PRIMARY
|
|
||||||
}
|
|
||||||
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||||
when (sorting) {
|
when (sorting) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
when {
|
when {
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
a.first.isStub && b.first.isStub.not() -> -1
|
||||||
b.first.isStub && a.first.isStub.not() -> 1
|
b.first.isStub && a.first.isStub.not() -> 1
|
||||||
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetMigrateSorting.Mode.TOTAL -> {
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.domain.source.service
|
|||||||
|
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
@ -10,7 +11,12 @@ class SourcePreferences(
|
|||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun sourceDisplayMode() = preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
fun sourceDisplayMode() = preferenceStore.getObject(
|
||||||
|
"pref_display_mode_catalogue",
|
||||||
|
LibraryDisplayMode.default,
|
||||||
|
LibraryDisplayMode.Serializer::serialize,
|
||||||
|
LibraryDisplayMode.Serializer::deserialize,
|
||||||
|
)
|
||||||
|
|
||||||
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
||||||
|
|
||||||
@ -18,17 +24,23 @@ class SourcePreferences(
|
|||||||
|
|
||||||
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun lastUsedSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
fun lastUsedSource() = preferenceStore.getLong(
|
||||||
|
Preference.appStateKey("last_catalogue_source"),
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||||
|
|
||||||
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||||
|
|
||||||
fun migrationSortingDirection() = preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
fun migrationSortingDirection() = preferenceStore.getEnum(
|
||||||
|
"pref_migration_direction",
|
||||||
|
SetMigrateSorting.Direction.ASCENDING,
|
||||||
|
)
|
||||||
|
|
||||||
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
fun trustedSignatures() = preferenceStore.getStringSet(Preference.appStateKey("trusted_signatures"), emptySet())
|
||||||
|
|
||||||
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,84 @@
|
|||||||
package eu.kanade.domain.track.interactor
|
package eu.kanade.domain.track.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
import eu.kanade.domain.track.model.toDomainTrack
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
class AddTracks(
|
class AddTracks(
|
||||||
private val getTracks: GetTracks,
|
private val getTracks: GetTracks,
|
||||||
private val insertTrack: InsertTrack,
|
private val insertTrack: InsertTrack,
|
||||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun bindEnhancedTracks(manga: Manga, source: Source) = withNonCancellableContext {
|
// TODO: update all trackers based on common data
|
||||||
|
suspend fun bind(tracker: Tracker, item: Track, mangaId: Long) = withNonCancellableContext {
|
||||||
|
withIOContext {
|
||||||
|
val allChapters = getChaptersByMangaId.await(mangaId)
|
||||||
|
val hasReadChapters = allChapters.any { it.read }
|
||||||
|
tracker.bind(item, hasReadChapters)
|
||||||
|
|
||||||
|
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
||||||
|
|
||||||
|
insertTrack.await(track)
|
||||||
|
|
||||||
|
// TODO: merge into [SyncChapterProgressWithTrack]?
|
||||||
|
// Update chapter progress if newer chapters marked read locally
|
||||||
|
if (hasReadChapters) {
|
||||||
|
val latestLocalReadChapterNumber = allChapters
|
||||||
|
.sortedBy { it.chapterNumber }
|
||||||
|
.takeWhile { it.read }
|
||||||
|
.lastOrNull()
|
||||||
|
?.chapterNumber ?: -1.0
|
||||||
|
|
||||||
|
if (latestLocalReadChapterNumber > track.lastChapterRead) {
|
||||||
|
track = track.copy(
|
||||||
|
lastChapterRead = latestLocalReadChapterNumber,
|
||||||
|
)
|
||||||
|
tracker.setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track.startDate <= 0) {
|
||||||
|
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
|
||||||
|
.sortedBy { it.readAt }
|
||||||
|
.firstOrNull()
|
||||||
|
?.readAt
|
||||||
|
|
||||||
|
firstReadChapterDate?.let {
|
||||||
|
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||||
|
ZoneOffset.systemDefault(),
|
||||||
|
ZoneOffset.UTC,
|
||||||
|
)
|
||||||
|
track = track.copy(
|
||||||
|
startDate = startDate,
|
||||||
|
)
|
||||||
|
tracker.setRemoteStartDate(track.toDbTrack(), startDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncChapterProgressWithTrack.await(mangaId, track, tracker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
||||||
|
withIOContext {
|
||||||
getTracks.await(manga.id)
|
getTracks.await(manga.id)
|
||||||
.filterIsInstance<EnhancedTracker>()
|
.filterIsInstance<EnhancedTracker>()
|
||||||
.filter { it.accept(source) }
|
.filter { it.accept(source) }
|
||||||
@ -43,3 +104,4 @@ class AddTracks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
|||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
@ -14,7 +14,7 @@ import tachiyomi.domain.track.model.Track
|
|||||||
class SyncChapterProgressWithTrack(
|
class SyncChapterProgressWithTrack(
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
private val insertTrack: InsertTrack,
|
private val insertTrack: InsertTrack,
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
@ -26,7 +26,7 @@ class SyncChapterProgressWithTrack(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortedChapters = getChapterByMangaId.await(mangaId)
|
val sortedChapters = getChaptersByMangaId.await(mangaId)
|
||||||
.sortedBy { it.chapterNumber }
|
.sortedBy { it.chapterNumber }
|
||||||
.filter { it.isRecognizedNumber }
|
.filter { it.isRecognizedNumber }
|
||||||
|
|
||||||
|
@ -43,7 +43,9 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
|||||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
}
|
}
|
||||||
.forEach { track ->
|
.forEach { track ->
|
||||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}" }
|
logcat(LogPriority.DEBUG) {
|
||||||
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
|
}
|
||||||
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
@ -79,7 +79,7 @@ fun BrowseSourceContent(
|
|||||||
listOf(
|
listOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringResId = R.string.local_source_help_guide,
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onLocalSourceHelpClick,
|
onClick = onLocalSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -97,7 +97,7 @@ fun BrowseSourceContent(
|
|||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringResId = R.string.label_help,
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -16,7 +16,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.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.History
|
import androidx.compose.material.icons.outlined.History
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
@ -92,7 +92,7 @@ fun ExtensionDetailsScreen(
|
|||||||
add(
|
add(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_faq_and_guides),
|
title = stringResource(R.string.action_faq_and_guides),
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = onClickReadme,
|
onClick = onClickReadme,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -74,7 +74,9 @@ internal fun GlobalSearchContent(
|
|||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = fromSourceId?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
title = fromSourceId?.let {
|
||||||
|
"▶ ${source.name}".takeIf { source.id == fromSourceId }
|
||||||
|
} ?: source.name,
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
|
@ -102,14 +102,26 @@ private fun MigrateSourceList(
|
|||||||
|
|
||||||
IconButton(onClick = onToggleSortingMode) {
|
IconButton(onClick = onToggleSortingMode) {
|
||||||
when (sortingMode) {
|
when (sortingMode) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(Icons.Outlined.SortByAlpha, contentDescription = stringResource(R.string.action_sort_alpha))
|
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||||
SetMigrateSorting.Mode.TOTAL -> Icon(Icons.Outlined.Numbers, contentDescription = stringResource(R.string.action_sort_count))
|
Icons.Outlined.SortByAlpha,
|
||||||
|
contentDescription = stringResource(R.string.action_sort_alpha),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Mode.TOTAL -> Icon(
|
||||||
|
Icons.Outlined.Numbers,
|
||||||
|
contentDescription = stringResource(R.string.action_sort_count),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(onClick = onToggleSortingDirection) {
|
IconButton(onClick = onToggleSortingDirection) {
|
||||||
when (sortingDirection) {
|
when (sortingDirection) {
|
||||||
SetMigrateSorting.Direction.ASCENDING -> Icon(Icons.Outlined.ArrowUpward, contentDescription = stringResource(R.string.action_asc))
|
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||||
SetMigrateSorting.Direction.DESCENDING -> Icon(Icons.Outlined.ArrowDownward, contentDescription = stringResource(R.string.action_desc))
|
Icons.Outlined.ArrowUpward,
|
||||||
|
contentDescription = stringResource(R.string.action_asc),
|
||||||
|
)
|
||||||
|
SetMigrateSorting.Direction.DESCENDING -> Icon(
|
||||||
|
Icons.Outlined.ArrowDownward,
|
||||||
|
contentDescription = stringResource(R.string.action_desc),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,13 @@ private fun SourcePinButton(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
||||||
val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground.copy(alpha = SecondaryItemAlpha)
|
val tint = if (isPinned) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = SecondaryItemAlpha,
|
||||||
|
)
|
||||||
|
}
|
||||||
val description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
val description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -25,7 +25,9 @@ fun BaseSourceItem(
|
|||||||
action: @Composable RowScope.(Source) -> Unit = {},
|
action: @Composable RowScope.(Source) -> Unit = {},
|
||||||
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
content: @Composable RowScope.(Source, String?) -> Unit = defaultContent,
|
||||||
) {
|
) {
|
||||||
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { showLanguageInContent }
|
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf {
|
||||||
|
showLanguageInContent
|
||||||
|
}
|
||||||
BaseBrowseItem(
|
BaseBrowseItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.presentation.browse.components
|
package eu.kanade.presentation.browse.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ViewList
|
import androidx.compose.material.icons.filled.ViewList
|
||||||
import androidx.compose.material.icons.filled.ViewModule
|
import androidx.compose.material.icons.filled.ViewModule
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@ -56,7 +56,11 @@ fun BrowseSourceToolbar(
|
|||||||
actions = listOfNotNull(
|
actions = listOfNotNull(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_display_mode),
|
title = stringResource(R.string.action_display_mode),
|
||||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.AutoMirrored.Filled.ViewList else Icons.Filled.ViewModule,
|
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||||
|
Icons.Filled.ViewList
|
||||||
|
} else {
|
||||||
|
Icons.Filled.ViewModule
|
||||||
|
},
|
||||||
onClick = { selectingDisplayMode = true },
|
onClick = { selectingDisplayMode = true },
|
||||||
),
|
),
|
||||||
if (isLocalSource) {
|
if (isLocalSource) {
|
||||||
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
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.automirrored.outlined.ArrowForward
|
|
||||||
import androidx.compose.material.icons.outlined.ArrowForward
|
import androidx.compose.material.icons.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.Error
|
import androidx.compose.material.icons.outlined.Error
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@ -55,7 +54,7 @@ fun GlobalSearchResultItem(
|
|||||||
Text(text = subtitle)
|
Text(text = subtitle)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content()
|
content()
|
||||||
|
@ -58,7 +58,7 @@ fun GlobalSearchToolbar(
|
|||||||
)
|
)
|
||||||
if (progress in 1..<total) {
|
if (progress in 1..<total) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { progress / total.toFloat() },
|
progress = progress / total.toFloat(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomStart)
|
.align(Alignment.BottomStart)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
|
@ -75,7 +75,9 @@ fun CategoryScreen(
|
|||||||
CategoryContent(
|
CategoryContent(
|
||||||
categories = state.categories,
|
categories = state.categories,
|
||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium),
|
paddingValues = paddingValues +
|
||||||
|
topSmallPaddingValues +
|
||||||
|
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||||
onClickRename = onClickRename,
|
onClickRename = onClickRename,
|
||||||
onClickDelete = onClickDelete,
|
onClickDelete = onClickDelete,
|
||||||
onMoveUp = onClickMoveUp,
|
onMoveUp = onClickMoveUp,
|
||||||
|
@ -74,7 +74,11 @@ fun CategoryCreateDialog(
|
|||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
label = { Text(text = stringResource(R.string.name)) },
|
label = { Text(text = stringResource(R.string.name)) },
|
||||||
supportingText = {
|
supportingText = {
|
||||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain
|
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
|
||||||
|
R.string.error_category_exists
|
||||||
|
} else {
|
||||||
|
R.string.information_required_plain
|
||||||
|
}
|
||||||
Text(text = stringResource(msgRes))
|
Text(text = stringResource(msgRes))
|
||||||
},
|
},
|
||||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||||
@ -134,7 +138,11 @@ fun CategoryRenameDialog(
|
|||||||
},
|
},
|
||||||
label = { Text(text = stringResource(R.string.name)) },
|
label = { Text(text = stringResource(R.string.name)) },
|
||||||
supportingText = {
|
supportingText = {
|
||||||
val msgRes = if (valueHasChanged && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain
|
val msgRes = if (valueHasChanged && nameAlreadyExists) {
|
||||||
|
R.string.error_category_exists
|
||||||
|
} else {
|
||||||
|
R.string.information_required_plain
|
||||||
|
}
|
||||||
Text(text = stringResource(msgRes))
|
Text(text = stringResource(msgRes))
|
||||||
},
|
},
|
||||||
isError = valueHasChanged && nameAlreadyExists,
|
isError = valueHasChanged && nameAlreadyExists,
|
||||||
@ -257,8 +265,12 @@ fun ChangeCategoryDialog(
|
|||||||
onClick = {
|
onClick = {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
onConfirm(
|
onConfirm(
|
||||||
selection.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }.map { it.value.id },
|
selection
|
||||||
selection.filter { it is CheckboxState.State.None || it is CheckboxState.TriState.None }.map { it.value.id },
|
.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }
|
||||||
|
.map { it.value.id },
|
||||||
|
selection
|
||||||
|
.filter { it is CheckboxState.State.None || it is CheckboxState.TriState.None }
|
||||||
|
.map { it.value.id },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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.automirrored.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropDown
|
import androidx.compose.material.icons.outlined.ArrowDropDown
|
||||||
import androidx.compose.material.icons.outlined.ArrowDropUp
|
import androidx.compose.material.icons.outlined.ArrowDropUp
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
@ -50,7 +49,7 @@ fun CategoryListItem(
|
|||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = "")
|
Icon(imageVector = Icons.Outlined.Label, contentDescription = "")
|
||||||
Text(
|
Text(
|
||||||
text = category.name,
|
text = category.name,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -72,7 +71,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 = stringResource(R.string.action_rename_category))
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Edit,
|
||||||
|
contentDescription = stringResource(R.string.action_rename_category),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete))
|
Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete))
|
||||||
|
@ -10,7 +10,8 @@ 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.automirrored.outlined.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
@ -39,12 +40,14 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -367,7 +370,11 @@ fun SearchToolbar(
|
|||||||
@Composable
|
@Composable
|
||||||
fun UpIcon(navigationIcon: ImageVector? = null) {
|
fun UpIcon(navigationIcon: ImageVector? = null) {
|
||||||
val icon = navigationIcon
|
val icon = navigationIcon
|
||||||
?: Icons.AutoMirrored.Outlined.ArrowBack
|
?: if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
|
||||||
|
Icons.Outlined.ArrowBack
|
||||||
|
} else {
|
||||||
|
Icons.Outlined.ArrowForward
|
||||||
|
}
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
|
@ -3,7 +3,8 @@ package eu.kanade.presentation.components
|
|||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
import androidx.compose.material.icons.outlined.ArrowLeft
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowRight
|
||||||
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
import androidx.compose.material.icons.outlined.RadioButtonChecked
|
||||||
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
import androidx.compose.material.icons.outlined.RadioButtonUnchecked
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@ -15,8 +16,10 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
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 eu.kanade.tachiyomi.R
|
||||||
@ -74,13 +77,14 @@ fun NestedMenuItem(
|
|||||||
) {
|
) {
|
||||||
var nestedExpanded by remember { mutableStateOf(false) }
|
var nestedExpanded by remember { mutableStateOf(false) }
|
||||||
val closeMenu = { nestedExpanded = false }
|
val closeMenu = { nestedExpanded = false }
|
||||||
|
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||||
|
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = text,
|
text = text,
|
||||||
onClick = { nestedExpanded = true },
|
onClick = { nestedExpanded = true },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Outlined.ArrowRight,
|
imageVector = if (isLtr) Icons.Outlined.ArrowRight else Icons.Outlined.ArrowLeft,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoActionPreview() {
|
private fun NoActionPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
@ -24,7 +23,7 @@ private fun NoActionPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun WithActionPreview() {
|
private fun WithActionPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
@ -39,7 +38,7 @@ private fun WithActionPreview() {
|
|||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.getting_started_guide,
|
stringResId = R.string.getting_started_guide,
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -14,9 +14,8 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -32,6 +31,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
import tachiyomi.presentation.core.components.HorizontalPager
|
||||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||||
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
|
|
||||||
object TabbedDialogPaddings {
|
object TabbedDialogPaddings {
|
||||||
val Horizontal = 24.dp
|
val Horizontal = 24.dp
|
||||||
@ -55,27 +55,18 @@ fun TabbedDialog(
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Row {
|
Row {
|
||||||
PrimaryTabRow(
|
TabRow(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
||||||
divider = {},
|
divider = {},
|
||||||
) {
|
) {
|
||||||
tabTitles.fastForEachIndexed { i, tab ->
|
tabTitles.fastForEachIndexed { index, tab ->
|
||||||
val selected = pagerState.currentPage == i
|
|
||||||
Tab(
|
Tab(
|
||||||
selected = selected,
|
selected = pagerState.currentPage == index,
|
||||||
onClick = { scope.launch { pagerState.animateScrollToPage(i) } },
|
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
|
||||||
text = {
|
text = { TabText(text = tab) },
|
||||||
Text(
|
unselectedContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
text = tab,
|
|
||||||
color = if (selected) {
|
|
||||||
MaterialTheme.colorScheme.primary
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -67,7 +67,7 @@ fun TabbedScreen(
|
|||||||
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
PrimaryTabRow(
|
TabRow(
|
||||||
selectedTabIndex = state.currentPage,
|
selectedTabIndex = state.currentPage,
|
||||||
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
|
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
|
||||||
) {
|
) {
|
||||||
|
@ -14,13 +14,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.InfoScreen
|
import tachiyomi.presentation.core.screens.InfoScreen
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CrashScreen(
|
fun CrashScreen(
|
||||||
@ -60,7 +60,7 @@ fun CrashScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun CrashScreenPreview() {
|
private fun CrashScreenPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
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.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@ -28,7 +29,6 @@ import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@ -148,7 +148,7 @@ sealed interface HistoryUiModel {
|
|||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HistoryScreenPreviews(
|
internal fun HistoryScreenPreviews(
|
||||||
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
||||||
|
@ -11,11 +11,11 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryDeleteDialog(
|
fun HistoryDeleteDialog(
|
||||||
@ -87,7 +87,7 @@ fun HistoryDeleteAllDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun HistoryDeleteDialogPreview() {
|
private fun HistoryDeleteDialogPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -11,6 +11,7 @@ import androidx.compose.material.icons.outlined.Delete
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
@ -28,7 +30,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
private val HistoryItemHeight = 96.dp
|
private val HistoryItemHeight = 96.dp
|
||||||
|
|
||||||
@ -91,13 +92,14 @@ fun HistoryItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun HistoryItemPreviews(
|
private fun HistoryItemPreviews(
|
||||||
@PreviewParameter(HistoryWithRelationsProvider::class)
|
@PreviewParameter(HistoryWithRelationsProvider::class)
|
||||||
historyWithRelations: HistoryWithRelations,
|
historyWithRelations: HistoryWithRelations,
|
||||||
) {
|
) {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
history = historyWithRelations,
|
history = historyWithRelations,
|
||||||
onClickCover = {},
|
onClickCover = {},
|
||||||
@ -106,3 +108,4 @@ private fun HistoryItemPreviews(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -144,6 +144,13 @@ private fun ColumnScope.SortPage(
|
|||||||
val sortingMode = category.sort.type
|
val sortingMode = category.sort.type
|
||||||
val sortDescending = !category.sort.isAscending
|
val sortDescending = !category.sort.isAscending
|
||||||
|
|
||||||
|
val trackerSortOption =
|
||||||
|
if (screenModel.trackers.isEmpty()) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
listOf(R.string.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
||||||
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
R.string.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
R.string.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
||||||
R.string.action_sort_total to LibrarySort.Type.TotalChapters,
|
R.string.action_sort_total to LibrarySort.Type.TotalChapters,
|
||||||
@ -153,15 +160,23 @@ private fun ColumnScope.SortPage(
|
|||||||
R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
R.string.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
||||||
R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
R.string.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
||||||
R.string.action_sort_date_added to LibrarySort.Type.DateAdded,
|
R.string.action_sort_date_added to LibrarySort.Type.DateAdded,
|
||||||
).map { (titleRes, mode) ->
|
).plus(trackerSortOption).map { (titleRes, mode) ->
|
||||||
SortItem(
|
SortItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
||||||
onClick = {
|
onClick = {
|
||||||
val isTogglingDirection = sortingMode == mode
|
val isTogglingDirection = sortingMode == mode
|
||||||
val direction = when {
|
val direction = when {
|
||||||
isTogglingDirection -> if (sortDescending) LibrarySort.Direction.Ascending else LibrarySort.Direction.Descending
|
isTogglingDirection -> if (sortDescending) {
|
||||||
else -> if (sortDescending) LibrarySort.Direction.Descending else LibrarySort.Direction.Ascending
|
LibrarySort.Direction.Ascending
|
||||||
|
} else {
|
||||||
|
LibrarySort.Direction.Descending
|
||||||
|
}
|
||||||
|
else -> if (sortDescending) {
|
||||||
|
LibrarySort.Direction.Descending
|
||||||
|
} else {
|
||||||
|
LibrarySort.Direction.Ascending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
screenModel.setSort(category, mode, direction)
|
screenModel.setSort(category, mode, direction)
|
||||||
},
|
},
|
||||||
|
@ -5,9 +5,9 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.outlined.Folder
|
import androidx.compose.material.icons.outlined.Folder
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import tachiyomi.presentation.core.components.Badge
|
import tachiyomi.presentation.core.components.Badge
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun DownloadsBadge(count: Long) {
|
internal fun DownloadsBadge(count: Long) {
|
||||||
@ -47,7 +47,7 @@ internal fun LanguageBadge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun BadgePreview() {
|
private fun BadgePreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -21,7 +21,7 @@ internal fun LibraryTabs(
|
|||||||
onTabItemClick: (Int) -> Unit,
|
onTabItemClick: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
PrimaryScrollableTabRow(
|
ScrollableTabRow(
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
edgePadding = 0.dp,
|
edgePadding = 0.dp,
|
||||||
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.PeopleAlt
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
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
|
||||||
@ -15,6 +23,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.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
|
||||||
@ -29,6 +38,7 @@ import tachiyomi.presentation.core.components.LabeledCheckbox
|
|||||||
import tachiyomi.presentation.core.components.RadioItem
|
import tachiyomi.presentation.core.components.RadioItem
|
||||||
import tachiyomi.presentation.core.components.SortItem
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
|
import tachiyomi.presentation.core.theme.active
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChapterSettingsDialog(
|
fun ChapterSettingsDialog(
|
||||||
@ -37,9 +47,12 @@ fun ChapterSettingsDialog(
|
|||||||
onDownloadFilterChanged: (TriState) -> Unit,
|
onDownloadFilterChanged: (TriState) -> Unit,
|
||||||
onUnreadFilterChanged: (TriState) -> Unit,
|
onUnreadFilterChanged: (TriState) -> Unit,
|
||||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||||
|
scanlatorFilterActive: Boolean,
|
||||||
|
onScanlatorFilterClicked: (() -> Unit),
|
||||||
onSortModeChanged: (Long) -> Unit,
|
onSortModeChanged: (Long) -> Unit,
|
||||||
onDisplayModeChanged: (Long) -> Unit,
|
onDisplayModeChanged: (Long) -> Unit,
|
||||||
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
|
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
|
||||||
|
onResetToDefault: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var showSetAsDefaultDialog by rememberSaveable { mutableStateOf(false) }
|
var showSetAsDefaultDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
if (showSetAsDefaultDialog) {
|
if (showSetAsDefaultDialog) {
|
||||||
@ -64,6 +77,13 @@ fun ChapterSettingsDialog(
|
|||||||
closeMenu()
|
closeMenu()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.action_reset)) },
|
||||||
|
onClick = {
|
||||||
|
onResetToDefault()
|
||||||
|
closeMenu()
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
) { page ->
|
) { page ->
|
||||||
Column(
|
Column(
|
||||||
@ -75,11 +95,14 @@ fun ChapterSettingsDialog(
|
|||||||
0 -> {
|
0 -> {
|
||||||
FilterPage(
|
FilterPage(
|
||||||
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
|
||||||
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
|
onDownloadFilterChanged = onDownloadFilterChanged
|
||||||
|
.takeUnless { manga?.forceDownloaded() == true },
|
||||||
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
|
||||||
onUnreadFilterChanged = onUnreadFilterChanged,
|
onUnreadFilterChanged = onUnreadFilterChanged,
|
||||||
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
|
||||||
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
|
||||||
|
scanlatorFilterActive = scanlatorFilterActive,
|
||||||
|
onScanlatorFilterClicked = onScanlatorFilterClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
@ -108,6 +131,8 @@ private fun ColumnScope.FilterPage(
|
|||||||
onUnreadFilterChanged: (TriState) -> Unit,
|
onUnreadFilterChanged: (TriState) -> Unit,
|
||||||
bookmarkedFilter: TriState,
|
bookmarkedFilter: TriState,
|
||||||
onBookmarkedFilterChanged: (TriState) -> Unit,
|
onBookmarkedFilterChanged: (TriState) -> Unit,
|
||||||
|
scanlatorFilterActive: Boolean,
|
||||||
|
onScanlatorFilterClicked: (() -> Unit),
|
||||||
) {
|
) {
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(R.string.label_downloaded),
|
label = stringResource(R.string.label_downloaded),
|
||||||
@ -124,6 +149,39 @@ private fun ColumnScope.FilterPage(
|
|||||||
state = bookmarkedFilter,
|
state = bookmarkedFilter,
|
||||||
onClick = onBookmarkedFilterChanged,
|
onClick = onBookmarkedFilterChanged,
|
||||||
)
|
)
|
||||||
|
ScanlatorFilterItem(
|
||||||
|
active = scanlatorFilterActive,
|
||||||
|
onClick = onScanlatorFilterClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScanlatorFilterItem(
|
||||||
|
active: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.PeopleAlt,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (active) {
|
||||||
|
MaterialTheme.colorScheme.active
|
||||||
|
} else {
|
||||||
|
LocalContentColor.current
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.scanlator),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -136,6 +194,7 @@ private fun ColumnScope.SortPage(
|
|||||||
R.string.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
|
R.string.sort_by_source to Manga.CHAPTER_SORTING_SOURCE,
|
||||||
R.string.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
|
R.string.sort_by_number to Manga.CHAPTER_SORTING_NUMBER,
|
||||||
R.string.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
|
R.string.sort_by_upload_date to Manga.CHAPTER_SORTING_UPLOAD_DATE,
|
||||||
|
R.string.action_sort_alpha to Manga.CHAPTER_SORTING_ALPHABET,
|
||||||
).map { (titleRes, mode) ->
|
).map { (titleRes, mode) ->
|
||||||
SortItem(
|
SortItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
|
@ -48,7 +48,6 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
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 eu.kanade.domain.manga.model.chaptersFiltered
|
|
||||||
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.manga.components.ChapterHeader
|
import eu.kanade.presentation.manga.components.ChapterHeader
|
||||||
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
||||||
@ -57,11 +56,12 @@ import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
|||||||
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
||||||
import eu.kanade.presentation.manga.components.MangaInfoBox
|
import eu.kanade.presentation.manga.components.MangaInfoBox
|
||||||
import eu.kanade.presentation.manga.components.MangaToolbar
|
import eu.kanade.presentation.manga.components.MangaToolbar
|
||||||
|
import eu.kanade.presentation.manga.components.MissingChapterCountListItem
|
||||||
import eu.kanade.presentation.util.formatChapterNumber
|
import eu.kanade.presentation.util.formatChapterNumber
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
@ -92,7 +92,7 @@ fun MangaScreen(
|
|||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
@ -123,10 +123,10 @@ fun MangaScreen(
|
|||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
|
|
||||||
// For chapter swipe
|
// For chapter swipe
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -225,7 +225,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
@ -257,16 +257,17 @@ private fun MangaScreenSmallImpl(
|
|||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
|
|
||||||
// For chapter swipe
|
// For chapter swipe
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val chapters = remember(state) { state.processedChapters }
|
||||||
|
val listItem = remember(state) { state.chapterListItems }
|
||||||
|
|
||||||
val isAnySelected by remember {
|
val isAnySelected by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@ -306,7 +307,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { animatedTitleAlpha },
|
titleAlphaProvider = { animatedTitleAlpha },
|
||||||
backgroundAlphaProvider = { animatedBgAlpha },
|
backgroundAlphaProvider = { animatedBgAlpha },
|
||||||
hasFilters = state.manga.chaptersFiltered(),
|
hasFilters = state.filterActive,
|
||||||
onBackClicked = internalOnBackPressed,
|
onBackClicked = internalOnBackPressed,
|
||||||
onClickFilter = onFilterClicked,
|
onClickFilter = onFilterClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
@ -447,7 +448,8 @@ private fun MangaScreenSmallImpl(
|
|||||||
|
|
||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = chapters,
|
chapters = listItem,
|
||||||
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
dateRelativeTime = dateRelativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
@ -474,7 +476,7 @@ fun MangaScreenLargeImpl(
|
|||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onAddToLibraryClicked: () -> Unit,
|
onAddToLibraryClicked: () -> Unit,
|
||||||
onWebViewClicked: (() -> Unit)?,
|
onWebViewClicked: (() -> Unit)?,
|
||||||
onWebViewLongClicked: (() -> Unit)?,
|
onWebViewLongClicked: (() -> Unit)?,
|
||||||
@ -506,10 +508,10 @@ fun MangaScreenLargeImpl(
|
|||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
|
|
||||||
// For swipe actions
|
// For swipe actions
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
|
|
||||||
// Chapter selection
|
// Chapter selection
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onAllChapterSelected: (Boolean) -> Unit,
|
onAllChapterSelected: (Boolean) -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -517,6 +519,7 @@ fun MangaScreenLargeImpl(
|
|||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val chapters = remember(state) { state.processedChapters }
|
||||||
|
val listItem = remember(state) { state.chapterListItems }
|
||||||
|
|
||||||
val isAnySelected by remember {
|
val isAnySelected by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@ -557,7 +560,7 @@ fun MangaScreenLargeImpl(
|
|||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
||||||
backgroundAlphaProvider = { 1f },
|
backgroundAlphaProvider = { 1f },
|
||||||
hasFilters = state.manga.chaptersFiltered(),
|
hasFilters = state.filterActive,
|
||||||
onBackClicked = internalOnBackPressed,
|
onBackClicked = internalOnBackPressed,
|
||||||
onClickFilter = onFilterButtonClicked,
|
onClickFilter = onFilterButtonClicked,
|
||||||
onClickShare = onShareClicked,
|
onClickShare = onShareClicked,
|
||||||
@ -604,7 +607,9 @@ fun MangaScreenLargeImpl(
|
|||||||
val isReading = remember(state.chapters) {
|
val isReading = remember(state.chapters) {
|
||||||
state.chapters.fastAny { it.chapter.read }
|
state.chapters.fastAny { it.chapter.read }
|
||||||
}
|
}
|
||||||
Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start))
|
Text(
|
||||||
|
text = stringResource(if (isReading) R.string.action_resume else R.string.action_start),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
@ -688,7 +693,8 @@ fun MangaScreenLargeImpl(
|
|||||||
|
|
||||||
sharedChapterItems(
|
sharedChapterItems(
|
||||||
manga = state.manga,
|
manga = state.manga,
|
||||||
chapters = chapters,
|
chapters = listItem,
|
||||||
|
isAnyChapterSelected = chapters.fastAny { it.selected },
|
||||||
dateRelativeTime = dateRelativeTime,
|
dateRelativeTime = dateRelativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = dateFormat,
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
@ -708,12 +714,12 @@ fun MangaScreenLargeImpl(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SharedMangaBottomActionMenu(
|
private fun SharedMangaBottomActionMenu(
|
||||||
selected: List<ChapterItem>,
|
selected: List<ChapterList.Item>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
|
||||||
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
|
||||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||||
fillFraction: Float,
|
fillFraction: Float,
|
||||||
) {
|
) {
|
||||||
@ -750,34 +756,45 @@ private fun SharedMangaBottomActionMenu(
|
|||||||
|
|
||||||
private fun LazyListScope.sharedChapterItems(
|
private fun LazyListScope.sharedChapterItems(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
chapters: List<ChapterItem>,
|
chapters: List<ChapterList>,
|
||||||
|
isAnyChapterSelected: Boolean,
|
||||||
dateRelativeTime: Boolean,
|
dateRelativeTime: Boolean,
|
||||||
dateFormat: DateFormat,
|
dateFormat: DateFormat,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
|
||||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = chapters,
|
items = chapters,
|
||||||
key = { "chapter-${it.chapter.id}" },
|
key = { item ->
|
||||||
|
when (item) {
|
||||||
|
is ChapterList.MissingCount -> "missing-count-${item.id}"
|
||||||
|
is ChapterList.Item -> "chapter-${item.id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
contentType = { MangaScreenItem.CHAPTER },
|
contentType = { MangaScreenItem.CHAPTER },
|
||||||
) { chapterItem ->
|
) { item ->
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
when (item) {
|
||||||
|
is ChapterList.MissingCount -> {
|
||||||
|
MissingChapterCountListItem(count = item.count)
|
||||||
|
}
|
||||||
|
is ChapterList.Item -> {
|
||||||
MangaChapterListItem(
|
MangaChapterListItem(
|
||||||
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.display_mode_chapter,
|
R.string.display_mode_chapter,
|
||||||
formatChapterNumber(chapterItem.chapter.chapterNumber),
|
formatChapterNumber(item.chapter.chapterNumber),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
chapterItem.chapter.name
|
item.chapter.name
|
||||||
},
|
},
|
||||||
date = chapterItem.chapter.dateUpload
|
date = item.chapter.dateUpload
|
||||||
.takeIf { it > 0L }
|
.takeIf { it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
Date(it).toRelativeString(
|
Date(it).toRelativeString(
|
||||||
@ -786,56 +803,58 @@ private fun LazyListScope.sharedChapterItems(
|
|||||||
dateFormat,
|
dateFormat,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
readProgress = chapterItem.chapter.lastPageRead
|
readProgress = item.chapter.lastPageRead
|
||||||
.takeIf { !chapterItem.chapter.read && it > 0L }
|
.takeIf { !item.chapter.read && it > 0L }
|
||||||
?.let {
|
?.let {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.chapter_progress,
|
R.string.chapter_progress,
|
||||||
it + 1,
|
it + 1,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() },
|
scanlator = item.chapter.scanlator.takeIf { !it.isNullOrBlank() },
|
||||||
read = chapterItem.chapter.read,
|
read = item.chapter.read,
|
||||||
bookmark = chapterItem.chapter.bookmark,
|
bookmark = item.chapter.bookmark,
|
||||||
selected = chapterItem.selected,
|
selected = item.selected,
|
||||||
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
|
downloadIndicatorEnabled = !isAnyChapterSelected,
|
||||||
downloadStateProvider = { chapterItem.downloadState },
|
downloadStateProvider = { item.downloadState },
|
||||||
downloadProgressProvider = { chapterItem.downloadProgress },
|
downloadProgressProvider = { item.downloadProgress },
|
||||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
onChapterSelected(chapterItem, !chapterItem.selected, true, true)
|
onChapterSelected(item, !item.selected, true, true)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
onChapterItemClick(
|
onChapterItemClick(
|
||||||
chapterItem = chapterItem,
|
chapterItem = item,
|
||||||
chapters = chapters,
|
isAnyChapterSelected = isAnyChapterSelected,
|
||||||
onToggleSelection = { onChapterSelected(chapterItem, !chapterItem.selected, true, false) },
|
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
|
||||||
onChapterClicked = onChapterClicked,
|
onChapterClicked = onChapterClicked,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onDownloadClick = if (onDownloadChapter != null) {
|
onDownloadClick = if (onDownloadChapter != null) {
|
||||||
{ onDownloadChapter(listOf(chapterItem), it) }
|
{ onDownloadChapter(listOf(item), it) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
onChapterSwipe = {
|
onChapterSwipe = {
|
||||||
onChapterSwipe(chapterItem, it)
|
onChapterSwipe(item, it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onChapterItemClick(
|
private fun onChapterItemClick(
|
||||||
chapterItem: ChapterItem,
|
chapterItem: ChapterList.Item,
|
||||||
chapters: List<ChapterItem>,
|
isAnyChapterSelected: Boolean,
|
||||||
onToggleSelection: (Boolean) -> Unit,
|
onToggleSelection: (Boolean) -> Unit,
|
||||||
onChapterClicked: (Chapter) -> Unit,
|
onChapterClicked: (Chapter) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
chapterItem.selected -> onToggleSelection(false)
|
chapterItem.selected -> onToggleSelection(false)
|
||||||
chapters.fastAny { it.selected } -> onToggleSelection(true)
|
isAnyChapterSelected -> onToggleSelection(true)
|
||||||
else -> onChapterClicked(chapterItem.chapter)
|
else -> onChapterClicked(chapterItem.chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ private fun DownloadingIndicator(
|
|||||||
MaterialTheme.colorScheme.background
|
MaterialTheme.colorScheme.background
|
||||||
}
|
}
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
progress = { animatedProgress },
|
progress = animatedProgress,
|
||||||
modifier = IndicatorModifier,
|
modifier = IndicatorModifier,
|
||||||
color = strokeColor,
|
color = strokeColor,
|
||||||
strokeWidth = IndicatorSize / 2,
|
strokeWidth = IndicatorSize / 2,
|
||||||
|
@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
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.automirrored.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.BookmarkAdd
|
import androidx.compose.material.icons.outlined.BookmarkAdd
|
||||||
import androidx.compose.material.icons.outlined.BookmarkRemove
|
import androidx.compose.material.icons.outlined.BookmarkRemove
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
@ -259,7 +258,7 @@ fun LibraryBottomActionMenu(
|
|||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_move_category),
|
title = stringResource(R.string.action_move_category),
|
||||||
icon = Icons.AutoMirrored.Outlined.Label,
|
icon = Icons.Outlined.Label,
|
||||||
toConfirm = confirm[0],
|
toConfirm = confirm[0],
|
||||||
onLongClick = { onLongClickItem(0) },
|
onLongClick = { onLongClickItem(0) },
|
||||||
onClick = onChangeCategoryClicked,
|
onClick = onChangeCategoryClicked,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
@ -24,8 +23,10 @@ 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.Brush
|
||||||
import androidx.compose.material.icons.filled.Favorite
|
import androidx.compose.material.icons.filled.Favorite
|
||||||
import androidx.compose.material.icons.filled.HourglassEmpty
|
import androidx.compose.material.icons.filled.HourglassEmpty
|
||||||
|
import androidx.compose.material.icons.filled.PersonOutline
|
||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.AttachMoney
|
import androidx.compose.material.icons.outlined.AttachMoney
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.Block
|
||||||
@ -41,6 +42,7 @@ import androidx.compose.material3.DropdownMenuItem
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
import androidx.compose.material3.SuggestionChip
|
import androidx.compose.material3.SuggestionChip
|
||||||
@ -132,7 +134,6 @@ fun MangaInfoBox(
|
|||||||
coverDataProvider = coverDataProvider,
|
coverDataProvider = coverDataProvider,
|
||||||
onCoverClick = onCoverClick,
|
onCoverClick = onCoverClick,
|
||||||
title = title,
|
title = title,
|
||||||
context = LocalContext.current,
|
|
||||||
doSearch = doSearch,
|
doSearch = doSearch,
|
||||||
author = author,
|
author = author,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
@ -146,7 +147,6 @@ fun MangaInfoBox(
|
|||||||
coverDataProvider = coverDataProvider,
|
coverDataProvider = coverDataProvider,
|
||||||
onCoverClick = onCoverClick,
|
onCoverClick = onCoverClick,
|
||||||
title = title,
|
title = title,
|
||||||
context = LocalContext.current,
|
|
||||||
doSearch = doSearch,
|
doSearch = doSearch,
|
||||||
author = author,
|
author = author,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
@ -189,7 +189,11 @@ fun MangaActionRow(
|
|||||||
)
|
)
|
||||||
if (onEditIntervalClicked != null && fetchInterval != null) {
|
if (onEditIntervalClicked != null && fetchInterval != null) {
|
||||||
MangaActionButton(
|
MangaActionButton(
|
||||||
title = pluralStringResource(id = R.plurals.day, count = fetchInterval.absoluteValue, fetchInterval.absoluteValue),
|
title = pluralStringResource(
|
||||||
|
id = R.plurals.day,
|
||||||
|
count = fetchInterval.absoluteValue,
|
||||||
|
fetchInterval.absoluteValue,
|
||||||
|
),
|
||||||
icon = Icons.Default.HourglassEmpty,
|
icon = Icons.Default.HourglassEmpty,
|
||||||
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||||
onClick = onEditIntervalClicked,
|
onClick = onEditIntervalClicked,
|
||||||
@ -321,7 +325,6 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
coverDataProvider: () -> Manga,
|
coverDataProvider: () -> Manga,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
@ -342,102 +345,16 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
MangaContentInfo(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
title = title,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
doSearch = doSearch,
|
||||||
modifier = Modifier.clickableNoIndication(
|
author = author,
|
||||||
onLongClick = { if (title.isNotBlank()) context.copyToClipboard(title, title) },
|
artist = artist,
|
||||||
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
status = status,
|
||||||
),
|
sourceName = sourceName,
|
||||||
|
isStubSource = isStubSource,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
Text(
|
|
||||||
text = author?.takeIf { it.isNotBlank() } ?: stringResource(R.string.unknown_author),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
|
||||||
onLongClick = {
|
|
||||||
if (!author.isNullOrBlank()) {
|
|
||||||
context.copyToClipboard(
|
|
||||||
author,
|
|
||||||
author,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
if (!artist.isNullOrBlank() && author != artist) {
|
|
||||||
Text(
|
|
||||||
text = artist,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier
|
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
|
||||||
onLongClick = { context.copyToClipboard(artist, artist) },
|
|
||||||
onClick = { doSearch(artist, true) },
|
|
||||||
),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = when (status) {
|
|
||||||
SManga.ONGOING.toLong() -> Icons.Outlined.Schedule
|
|
||||||
SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll
|
|
||||||
SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done
|
|
||||||
SManga.CANCELLED.toLong() -> Icons.Outlined.Close
|
|
||||||
SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause
|
|
||||||
else -> Icons.Outlined.Block
|
|
||||||
},
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
)
|
|
||||||
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
|
||||||
Text(
|
|
||||||
text = when (status) {
|
|
||||||
SManga.ONGOING.toLong() -> stringResource(R.string.ongoing)
|
|
||||||
SManga.COMPLETED.toLong() -> stringResource(R.string.completed)
|
|
||||||
SManga.LICENSED.toLong() -> stringResource(R.string.licensed)
|
|
||||||
SManga.PUBLISHING_FINISHED.toLong() -> stringResource(R.string.publishing_finished)
|
|
||||||
SManga.CANCELLED.toLong() -> stringResource(R.string.cancelled)
|
|
||||||
SManga.ON_HIATUS.toLong() -> stringResource(R.string.on_hiatus)
|
|
||||||
else -> stringResource(R.string.unknown)
|
|
||||||
},
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
DotSeparatorText()
|
|
||||||
if (isStubSource) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Warning,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
.size(16.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = sourceName,
|
|
||||||
modifier = Modifier.clickableNoIndication { doSearch(sourceName, false) },
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +364,6 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
coverDataProvider: () -> Manga,
|
coverDataProvider: () -> Manga,
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
@ -459,6 +375,7 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp, top = appBarPadding + 16.dp, end = 16.dp),
|
.padding(start = 16.dp, top = appBarPadding + 16.dp, end = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
@ -469,7 +386,34 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
contentDescription = stringResource(R.string.manga_cover),
|
contentDescription = stringResource(R.string.manga_cover),
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Column(modifier = Modifier.padding(start = 16.dp)) {
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
MangaContentInfo(
|
||||||
|
title = title,
|
||||||
|
doSearch = doSearch,
|
||||||
|
author = author,
|
||||||
|
artist = artist,
|
||||||
|
status = status,
|
||||||
|
sourceName = sourceName,
|
||||||
|
isStubSource = isStubSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MangaContentInfo(
|
||||||
|
title: String,
|
||||||
|
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||||
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
|
author: String?,
|
||||||
|
artist: String?,
|
||||||
|
status: Long,
|
||||||
|
sourceName: String,
|
||||||
|
isStubSource: Boolean,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
text = title.ifBlank { stringResource(R.string.unknown_title) },
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
@ -484,15 +428,26 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
},
|
},
|
||||||
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
onClick = { if (title.isNotBlank()) doSearch(title, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.PersonOutline,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = author?.takeIf { it.isNotBlank() }
|
text = author?.takeIf { it.isNotBlank() }
|
||||||
?: stringResource(R.string.unknown_author),
|
?: stringResource(R.string.unknown_author),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
.clickableNoIndication(
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (!author.isNullOrBlank()) {
|
if (!author.isNullOrBlank()) {
|
||||||
@ -504,21 +459,36 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
},
|
},
|
||||||
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
onClick = { if (!author.isNullOrBlank()) doSearch(author, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!artist.isNullOrBlank() && author != artist) {
|
if (!artist.isNullOrBlank() && author != artist) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Brush,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = artist,
|
text = artist,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.secondaryItemAlpha()
|
|
||||||
.padding(top = 2.dp)
|
|
||||||
.clickableNoIndication(
|
.clickableNoIndication(
|
||||||
onLongClick = { context.copyToClipboard(artist, artist) },
|
onLongClick = { context.copyToClipboard(artist, artist) },
|
||||||
onClick = { doSearch(artist, true) },
|
onClick = { doSearch(artist, true) },
|
||||||
),
|
),
|
||||||
|
textAlign = textAlign,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@ -577,8 +547,6 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaSummary(
|
private fun MangaSummary(
|
||||||
@ -623,7 +591,9 @@ private fun MangaSummary(
|
|||||||
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
||||||
Icon(
|
Icon(
|
||||||
painter = rememberAnimatedVectorPainter(image, !expanded),
|
painter = rememberAnimatedVectorPainter(image, !expanded),
|
||||||
contentDescription = stringResource(if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand),
|
contentDescription = stringResource(
|
||||||
|
if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand,
|
||||||
|
),
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
|
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MissingChapterCountListItem(
|
||||||
|
count: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
)
|
||||||
|
.secondaryItemAlpha(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(id = R.plurals.missing_chapters, count = count, count),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
|
MissingChapterCountListItem(count = 42)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
|
||||||
|
import androidx.compose.material.icons.rounded.DisabledByDefault
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScanlatorFilterDialog(
|
||||||
|
availableScanlators: Set<String>,
|
||||||
|
excludedScanlators: Set<String>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onConfirm: (Set<String>) -> Unit,
|
||||||
|
) {
|
||||||
|
val sortedAvailableScanlators = remember(availableScanlators) {
|
||||||
|
availableScanlators.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it })
|
||||||
|
}
|
||||||
|
val mutableExcludedScanlators = remember(excludedScanlators) { excludedScanlators.toMutableStateList() }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(R.string.exclude_scanlators)) },
|
||||||
|
text = textFunc@{
|
||||||
|
if (sortedAvailableScanlators.isEmpty()) {
|
||||||
|
Text(text = stringResource(R.string.no_scanlators_found))
|
||||||
|
return@textFunc
|
||||||
|
}
|
||||||
|
Box {
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
LazyColumn(state = state) {
|
||||||
|
sortedAvailableScanlators.forEach { scanlator ->
|
||||||
|
item {
|
||||||
|
val isExcluded = mutableExcludedScanlators.contains(scanlator)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
if (isExcluded) {
|
||||||
|
mutableExcludedScanlators.remove(scanlator)
|
||||||
|
} else {
|
||||||
|
mutableExcludedScanlators.add(scanlator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isExcluded) {
|
||||||
|
Icons.Rounded.DisabledByDefault
|
||||||
|
} else {
|
||||||
|
Icons.Rounded.CheckBoxOutlineBlank
|
||||||
|
},
|
||||||
|
tint = if (isExcluded) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
LocalContentColor.current
|
||||||
|
},
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = scanlator,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(start = 24.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
|
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = true,
|
||||||
|
),
|
||||||
|
confirmButton = {
|
||||||
|
if (sortedAvailableScanlators.isEmpty()) {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FlowRow {
|
||||||
|
TextButton(onClick = mutableExcludedScanlators::clear) {
|
||||||
|
Text(text = stringResource(R.string.action_reset))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onConfirm(mutableExcludedScanlators.toSet())
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
|
||||||
import androidx.compose.material.icons.outlined.CloudOff
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
import androidx.compose.material.icons.outlined.GetApp
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
@ -18,7 +16,7 @@ import androidx.compose.material.icons.outlined.Info
|
|||||||
import androidx.compose.material.icons.outlined.Label
|
import androidx.compose.material.icons.outlined.Label
|
||||||
import androidx.compose.material.icons.outlined.QueryStats
|
import androidx.compose.material.icons.outlined.QueryStats
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
import androidx.compose.material.icons.outlined.Storage
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -47,7 +45,7 @@ fun MoreScreen(
|
|||||||
onClickDownloadQueue: () -> Unit,
|
onClickDownloadQueue: () -> Unit,
|
||||||
onClickCategories: () -> Unit,
|
onClickCategories: () -> Unit,
|
||||||
onClickStats: () -> Unit,
|
onClickStats: () -> Unit,
|
||||||
onClickBackupAndRestore: () -> Unit,
|
onClickDataAndStorage: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
onClickAbout: () -> Unit,
|
onClickAbout: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -64,7 +62,9 @@ fun MoreScreen(
|
|||||||
WarningBanner(
|
WarningBanner(
|
||||||
textRes = R.string.fdroid_warning,
|
textRes = R.string.fdroid_warning,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
uriHandler.openUri("https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds")
|
uriHandler.openUri(
|
||||||
|
"https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds",
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ fun MoreScreen(
|
|||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(R.string.categories),
|
||||||
icon = Icons.AutoMirrored.Outlined.Label,
|
icon = Icons.Outlined.Label,
|
||||||
onPreferenceClick = onClickCategories,
|
onPreferenceClick = onClickCategories,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -144,8 +144,8 @@ fun MoreScreen(
|
|||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_backup_and_sync),
|
title = stringResource(R.string.label_backup_and_sync),
|
||||||
icon = Icons.Outlined.SettingsBackupRestore,
|
icon = Icons.Outlined.Storage,
|
||||||
onPreferenceClick = onClickBackupAndRestore,
|
onPreferenceClick = onClickDataAndStorage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ fun MoreScreen(
|
|||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_help),
|
title = stringResource(R.string.label_help),
|
||||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
|
|
||||||
import androidx.compose.material.icons.filled.OpenInNew
|
import androidx.compose.material.icons.filled.OpenInNew
|
||||||
import androidx.compose.material.icons.outlined.NewReleases
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -16,6 +15,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import com.halilibo.richtext.markdown.Markdown
|
import com.halilibo.richtext.markdown.Markdown
|
||||||
import com.halilibo.richtext.ui.RichTextStyle
|
import com.halilibo.richtext.ui.RichTextStyle
|
||||||
import com.halilibo.richtext.ui.material3.Material3RichText
|
import com.halilibo.richtext.ui.material3.Material3RichText
|
||||||
@ -24,7 +24,6 @@ import eu.kanade.presentation.theme.TachiyomiTheme
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.InfoScreen
|
import tachiyomi.presentation.core.screens.InfoScreen
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewUpdateScreen(
|
fun NewUpdateScreen(
|
||||||
@ -61,13 +60,13 @@ fun NewUpdateScreen(
|
|||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.update_check_open))
|
Text(text = stringResource(R.string.update_check_open))
|
||||||
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
|
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
|
||||||
Icon(imageVector = Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = null)
|
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun NewUpdateScreenPreview() {
|
private fun NewUpdateScreenPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -31,7 +31,8 @@ fun getCategoriesLabel(
|
|||||||
|
|
||||||
val includedItemsText = when {
|
val includedItemsText = when {
|
||||||
// Some selected, but not all
|
// Some selected, but not all
|
||||||
includedCategories.isNotEmpty() && includedCategories.size != allCategories.size -> includedCategories.joinToString { it.visualName(context) }
|
includedCategories.isNotEmpty() && includedCategories.size != allCategories.size ->
|
||||||
|
includedCategories.joinToString { it.visualName(context) }
|
||||||
// All explicitly selected
|
// All explicitly selected
|
||||||
includedCategories.size == allCategories.size -> stringResource(R.string.all)
|
includedCategories.size == allCategories.size -> stringResource(R.string.all)
|
||||||
allExcluded -> stringResource(R.string.none)
|
allExcluded -> stringResource(R.string.none)
|
||||||
|
@ -14,7 +14,6 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@ -31,7 +30,6 @@ import eu.kanade.presentation.more.settings.Preference
|
|||||||
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
|
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
|
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
@ -61,8 +59,7 @@ import okhttp3.Headers
|
|||||||
import tachiyomi.core.util.lang.launchNonCancellable
|
import tachiyomi.core.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -183,40 +180,12 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDataGroup(): Preference.PreferenceGroup {
|
private fun getDataGroup(): Preference.PreferenceGroup {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
|
||||||
|
|
||||||
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
|
||||||
var readableSizeSema by remember { mutableIntStateOf(0) }
|
|
||||||
val readableSize = remember(readableSizeSema) { chapterCache.readableSize }
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.label_data),
|
title = stringResource(R.string.label_data),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_clear_chapter_cache),
|
|
||||||
subtitle = stringResource(R.string.used_cache, readableSize),
|
|
||||||
onClick = {
|
|
||||||
scope.launchNonCancellable {
|
|
||||||
try {
|
|
||||||
val deletedFiles = chapterCache.clear()
|
|
||||||
withUIContext {
|
|
||||||
context.toast(context.getString(R.string.cache_deleted, deletedFiles))
|
|
||||||
readableSizeSema++
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
withUIContext { context.toast(R.string.cache_delete_error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = libraryPreferences.autoClearChapterCache(),
|
|
||||||
title = stringResource(R.string.pref_auto_clear_chapter_cache),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_invalidate_download_cache),
|
title = stringResource(R.string.pref_invalidate_download_cache),
|
||||||
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
|
subtitle = stringResource(R.string.pref_invalidate_download_cache_summary),
|
||||||
@ -340,7 +309,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
subtitle = stringResource(R.string.pref_reset_viewer_flags_summary),
|
subtitle = stringResource(R.string.pref_reset_viewer_flags_summary),
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launchNonCancellable {
|
scope.launchNonCancellable {
|
||||||
val success = Injekt.get<MangaRepository>().resetViewerFlags()
|
val success = Injekt.get<ResetViewerFlags>().await()
|
||||||
withUIContext {
|
withUIContext {
|
||||||
val message = if (success) {
|
val message = if (success) {
|
||||||
R.string.pref_reset_viewer_flags_success
|
R.string.pref_reset_viewer_flags_success
|
||||||
|
@ -120,7 +120,9 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
uiPreferences: UiPreferences,
|
uiPreferences: UiPreferences,
|
||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
val langs = remember { getLangs(context) }
|
val langs = remember { getLangs(context) }
|
||||||
var currentLanguage by remember { mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") }
|
var currentLanguage by remember {
|
||||||
|
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
|
||||||
|
}
|
||||||
val now = remember { Date().time }
|
val now = remember { Date().time }
|
||||||
|
|
||||||
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
||||||
|
@ -1,627 +0,0 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.toMutableStateList
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
|
||||||
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
|
||||||
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
|
||||||
import eu.kanade.tachiyomi.data.sync.SyncManager
|
|
||||||
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService
|
|
||||||
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveSyncService
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
|
||||||
import tachiyomi.domain.sync.SyncPreferences
|
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
object SettingsBackupAndSyncScreen : SearchableSettings {
|
|
||||||
|
|
||||||
@ReadOnlyComposable
|
|
||||||
@Composable
|
|
||||||
@StringRes
|
|
||||||
override fun getTitleRes() = R.string.label_backup_and_sync
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun getPreferences(): List<Preference> {
|
|
||||||
val backupPreferences = Injekt.get<BackupPreferences>()
|
|
||||||
|
|
||||||
PermissionRequestHelper.requestStoragePermission()
|
|
||||||
val syncPreferences = remember { Injekt.get<SyncPreferences>() }
|
|
||||||
val syncService by syncPreferences.syncService().collectAsState()
|
|
||||||
|
|
||||||
return listOf(
|
|
||||||
getManualBackupGroup(),
|
|
||||||
getAutomaticBackupGroup(backupPreferences = backupPreferences),
|
|
||||||
) + listOf(
|
|
||||||
Preference.PreferenceGroup(
|
|
||||||
title = stringResource(R.string.label_sync),
|
|
||||||
preferenceItems = listOf(
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = syncPreferences.syncService(),
|
|
||||||
title = stringResource(R.string.pref_sync_service),
|
|
||||||
entries = mapOf(
|
|
||||||
SyncManager.SyncService.NONE.value to stringResource(R.string.off),
|
|
||||||
SyncManager.SyncService.SYNCYOMI.value to stringResource(R.string.syncyomi),
|
|
||||||
SyncManager.SyncService.GOOGLE_DRIVE.value to stringResource(R.string.google_drive),
|
|
||||||
),
|
|
||||||
onValueChanged = { true },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
) + getSyncServicePreferences(syncPreferences, syncService)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getManualBackupGroup(): Preference.PreferenceGroup {
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(R.string.pref_backup_manual_category),
|
|
||||||
preferenceItems = listOf(
|
|
||||||
getCreateBackupPref(),
|
|
||||||
getRestoreBackupPref(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getAutomaticBackupGroup(
|
|
||||||
backupPreferences: BackupPreferences,
|
|
||||||
): Preference.PreferenceGroup {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val backupIntervalPref = backupPreferences.backupInterval()
|
|
||||||
val backupInterval by backupIntervalPref.collectAsState()
|
|
||||||
val backupDirPref = backupPreferences.backupsDirectory()
|
|
||||||
val backupDir by backupDirPref.collectAsState()
|
|
||||||
val pickBackupLocation = rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
|
||||||
) { uri ->
|
|
||||||
if (uri != null) {
|
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
|
||||||
|
|
||||||
val file = UniFile.fromUri(context, uri)
|
|
||||||
backupDirPref.set(file.uri.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(R.string.pref_backup_service_category),
|
|
||||||
preferenceItems = listOf(
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = backupIntervalPref,
|
|
||||||
title = stringResource(R.string.pref_backup_interval),
|
|
||||||
entries = mapOf(
|
|
||||||
0 to stringResource(R.string.off),
|
|
||||||
6 to stringResource(R.string.update_6hour),
|
|
||||||
12 to stringResource(R.string.update_12hour),
|
|
||||||
24 to stringResource(R.string.update_24hour),
|
|
||||||
48 to stringResource(R.string.update_48hour),
|
|
||||||
168 to stringResource(R.string.update_weekly),
|
|
||||||
),
|
|
||||||
onValueChanged = {
|
|
||||||
BackupCreateJob.setupTask(context, it)
|
|
||||||
true
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_backup_directory),
|
|
||||||
enabled = backupInterval != 0,
|
|
||||||
subtitle = remember(backupDir) {
|
|
||||||
(UniFile.fromUri(context, backupDir.toUri())?.filePath)?.let {
|
|
||||||
"$it/automatic"
|
|
||||||
}
|
|
||||||
} ?: stringResource(R.string.invalid_location, backupDir),
|
|
||||||
onClick = {
|
|
||||||
try {
|
|
||||||
pickBackupLocation.launch(null)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
context.toast(R.string.file_picker_error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = backupPreferences.numberOfBackups(),
|
|
||||||
enabled = backupInterval != 0,
|
|
||||||
title = stringResource(R.string.pref_backup_slots),
|
|
||||||
entries = listOf(2, 3, 4, 5).associateWith { it.toString() },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.InfoPreference(stringResource(R.string.backup_info)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getCreateBackupPref(): Preference.PreferenceItem.TextPreference {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
var flag by rememberSaveable { mutableIntStateOf(0) }
|
|
||||||
val chooseBackupDir = rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.CreateDocument("application/*"),
|
|
||||||
) {
|
|
||||||
if (it != null) {
|
|
||||||
context.contentResolver.takePersistableUriPermission(
|
|
||||||
it,
|
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
|
||||||
)
|
|
||||||
BackupCreateJob.startNow(context, it, flag)
|
|
||||||
}
|
|
||||||
flag = 0
|
|
||||||
}
|
|
||||||
var showCreateDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
if (showCreateDialog) {
|
|
||||||
CreateBackupDialog(
|
|
||||||
onConfirm = {
|
|
||||||
showCreateDialog = false
|
|
||||||
flag = it
|
|
||||||
try {
|
|
||||||
chooseBackupDir.launch(Backup.getFilename())
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
flag = 0
|
|
||||||
context.toast(R.string.file_picker_error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = { showCreateDialog = false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_create_backup),
|
|
||||||
subtitle = stringResource(R.string.pref_create_backup_summ),
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
if (!BackupCreateJob.isManualJobRunning(context)) {
|
|
||||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
||||||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
showCreateDialog = true
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.backup_in_progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CreateBackupDialog(
|
|
||||||
onConfirm: (flag: Int) -> Unit,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
) {
|
|
||||||
val choices = remember {
|
|
||||||
mapOf(
|
|
||||||
BackupConst.BACKUP_CATEGORY to R.string.categories,
|
|
||||||
BackupConst.BACKUP_CHAPTER to R.string.chapters,
|
|
||||||
BackupConst.BACKUP_TRACK to R.string.track,
|
|
||||||
BackupConst.BACKUP_HISTORY to R.string.history,
|
|
||||||
BackupConst.BACKUP_APP_PREFS to R.string.app_settings,
|
|
||||||
BackupConst.BACKUP_SOURCE_PREFS to R.string.source_settings,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val flags = remember { choices.keys.toMutableStateList() }
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(R.string.backup_choice)) },
|
|
||||||
text = {
|
|
||||||
Box {
|
|
||||||
val state = rememberLazyListState()
|
|
||||||
ScrollbarLazyColumn(state = state) {
|
|
||||||
item {
|
|
||||||
LabeledCheckbox(
|
|
||||||
label = stringResource(R.string.manga),
|
|
||||||
checked = true,
|
|
||||||
onCheckedChange = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
choices.forEach { (k, v) ->
|
|
||||||
item {
|
|
||||||
val isSelected = flags.contains(k)
|
|
||||||
LabeledCheckbox(
|
|
||||||
label = stringResource(v),
|
|
||||||
checked = isSelected,
|
|
||||||
onCheckedChange = {
|
|
||||||
if (it) {
|
|
||||||
flags.add(k)
|
|
||||||
} else {
|
|
||||||
flags.remove(k)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
|
||||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
val flag = flags.fold(initial = 0, operation = { a, b -> a or b })
|
|
||||||
onConfirm(flag)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.action_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
|
||||||
val context = LocalContext.current
|
|
||||||
var error by remember { mutableStateOf<Any?>(null) }
|
|
||||||
if (error != null) {
|
|
||||||
val onDismissRequest = { error = null }
|
|
||||||
when (val err = error) {
|
|
||||||
is InvalidRestore -> {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(R.string.invalid_backup_file)) },
|
|
||||||
text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) },
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
context.copyToClipboard(err.message, err.message)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.action_copy_to_clipboard))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(R.string.action_ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is MissingRestoreComponents -> {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(R.string.pref_restore_backup)) },
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
|
||||||
val msg = buildString {
|
|
||||||
append(stringResource(R.string.backup_restore_content_full))
|
|
||||||
if (err.sources.isNotEmpty()) {
|
|
||||||
append("\n\n").append(stringResource(R.string.backup_restore_missing_sources))
|
|
||||||
err.sources.joinTo(
|
|
||||||
this,
|
|
||||||
separator = "\n- ",
|
|
||||||
prefix = "\n- ",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (err.trackers.isNotEmpty()) {
|
|
||||||
append("\n\n").append(stringResource(R.string.backup_restore_missing_trackers))
|
|
||||||
err.trackers.joinTo(
|
|
||||||
this,
|
|
||||||
separator = "\n- ",
|
|
||||||
prefix = "\n- ",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(text = msg)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
BackupRestoreJob.start(context, err.uri)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.action_restore))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> error = null // Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val chooseBackup = rememberLauncherForActivityResult(
|
|
||||||
object : ActivityResultContracts.GetContent() {
|
|
||||||
override fun createIntent(context: Context, input: String): Intent {
|
|
||||||
val intent = super.createIntent(context, input)
|
|
||||||
return Intent.createChooser(intent, context.getString(R.string.file_select_backup))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
if (it == null) {
|
|
||||||
error = InvalidRestore(message = context.getString(R.string.file_null_uri_error))
|
|
||||||
return@rememberLauncherForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val results = try {
|
|
||||||
BackupFileValidator().validate(context, it)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = InvalidRestore(it, e.message.toString())
|
|
||||||
return@rememberLauncherForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
|
|
||||||
BackupRestoreJob.start(context, it)
|
|
||||||
return@rememberLauncherForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
error = MissingRestoreComponents(it, results.missingSources, results.missingTrackers)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_restore_backup),
|
|
||||||
subtitle = stringResource(R.string.pref_restore_backup_summ),
|
|
||||||
onClick = {
|
|
||||||
if (!BackupRestoreJob.isRunning(context)) {
|
|
||||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
||||||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
// no need to catch because it's wrapped with a chooser
|
|
||||||
chooseBackup.launch("*/*")
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.restore_in_progress)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getSyncServicePreferences(syncPreferences: SyncPreferences, syncService: Int): List<Preference> {
|
|
||||||
val syncServiceType = SyncManager.SyncService.fromInt(syncService)
|
|
||||||
return when (syncServiceType) {
|
|
||||||
SyncManager.SyncService.NONE -> emptyList()
|
|
||||||
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
|
|
||||||
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
|
|
||||||
} +
|
|
||||||
if (syncServiceType == SyncManager.SyncService.NONE) {
|
|
||||||
emptyList()
|
|
||||||
} else {
|
|
||||||
listOf(getSyncNowPref(), getAutomaticSyncGroup(syncPreferences))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getGoogleDrivePreferences(): List<Preference> {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val googleDriveSync = Injekt.get<GoogleDriveService>()
|
|
||||||
return listOf(
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_google_drive_sign_in),
|
|
||||||
onClick = {
|
|
||||||
val intent = googleDriveSync.getSignInIntent()
|
|
||||||
context.startActivity(intent)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
getGoogleDrivePurge(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getGoogleDrivePurge(): Preference.PreferenceItem.TextPreference {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val showPurgeDialog = remember { mutableStateOf(false) }
|
|
||||||
val context = LocalContext.current
|
|
||||||
val googleDriveSync = remember { GoogleDriveSyncService(context) }
|
|
||||||
|
|
||||||
if (showPurgeDialog.value) {
|
|
||||||
PurgeConfirmationDialog(
|
|
||||||
onConfirm = {
|
|
||||||
showPurgeDialog.value = false
|
|
||||||
scope.launch {
|
|
||||||
val result = googleDriveSync.deleteSyncDataFromGoogleDrive()
|
|
||||||
when (result) {
|
|
||||||
GoogleDriveSyncService.DeleteSyncDataStatus.NOT_INITIALIZED -> context.toast(R.string.google_drive_not_signed_in)
|
|
||||||
GoogleDriveSyncService.DeleteSyncDataStatus.NO_FILES -> context.toast(R.string.google_drive_sync_data_not_found)
|
|
||||||
GoogleDriveSyncService.DeleteSyncDataStatus.SUCCESS -> context.toast(R.string.google_drive_sync_data_purged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = { showPurgeDialog.value = false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_google_drive_purge_sync_data),
|
|
||||||
onClick = { showPurgeDialog.value = true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PurgeConfirmationDialog(
|
|
||||||
onConfirm: () -> Unit,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(R.string.pref_purge_confirmation_title)) },
|
|
||||||
text = { Text(text = stringResource(R.string.pref_purge_confirmation_message)) },
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = onConfirm) {
|
|
||||||
Text(text = stringResource(android.R.string.ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List<Preference> {
|
|
||||||
return listOf(
|
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
|
||||||
title = stringResource(R.string.pref_sync_device_name),
|
|
||||||
subtitle = stringResource(R.string.pref_sync_device_name_summ),
|
|
||||||
pref = syncPreferences.deviceName(),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
|
||||||
title = stringResource(R.string.pref_sync_host),
|
|
||||||
subtitle = stringResource(R.string.pref_sync_host_summ),
|
|
||||||
pref = syncPreferences.syncHost(),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
|
||||||
title = stringResource(R.string.pref_sync_api_key),
|
|
||||||
subtitle = stringResource(R.string.pref_sync_api_key_summ),
|
|
||||||
pref = syncPreferences.syncAPIKey(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getSyncNowPref(): Preference.PreferenceGroup {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
|
||||||
val context = LocalContext.current
|
|
||||||
if (showDialog) {
|
|
||||||
SyncConfirmationDialog(
|
|
||||||
onConfirm = {
|
|
||||||
showDialog = false
|
|
||||||
scope.launch {
|
|
||||||
if (!SyncDataJob.isAnyJobRunning(context)) {
|
|
||||||
SyncDataJob.startNow(context)
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.sync_in_progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismissRequest = { showDialog = false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(R.string.pref_sync_now_group_title),
|
|
||||||
preferenceItems = listOf(
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_sync_now),
|
|
||||||
subtitle = stringResource(R.string.pref_sync_now_subtitle),
|
|
||||||
onClick = {
|
|
||||||
showDialog = true
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getAutomaticSyncGroup(syncPreferences: SyncPreferences): Preference.PreferenceGroup {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val syncIntervalPref = syncPreferences.syncInterval()
|
|
||||||
val lastSync by syncPreferences.syncLastSync().collectAsState()
|
|
||||||
val formattedLastSync = DateUtils.getRelativeTimeSpanString(lastSync.toEpochMilli(), System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS)
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
|
||||||
title = stringResource(R.string.pref_sync_service_category),
|
|
||||||
preferenceItems = listOf(
|
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = syncIntervalPref,
|
|
||||||
title = stringResource(R.string.pref_sync_interval),
|
|
||||||
entries = mapOf(
|
|
||||||
0 to stringResource(R.string.off),
|
|
||||||
30 to stringResource(R.string.update_30min),
|
|
||||||
60 to stringResource(R.string.update_1hour),
|
|
||||||
180 to stringResource(R.string.update_3hour),
|
|
||||||
360 to stringResource(R.string.update_6hour),
|
|
||||||
720 to stringResource(R.string.update_12hour),
|
|
||||||
1440 to stringResource(R.string.update_24hour),
|
|
||||||
2880 to stringResource(R.string.update_48hour),
|
|
||||||
10080 to stringResource(R.string.update_weekly),
|
|
||||||
),
|
|
||||||
onValueChanged = {
|
|
||||||
SyncDataJob.setupTask(context, it)
|
|
||||||
true
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.InfoPreference(stringResource(R.string.last_synchronization, formattedLastSync)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SyncConfirmationDialog(
|
|
||||||
onConfirm: () -> Unit,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
title = { Text(text = stringResource(R.string.pref_sync_confirmation_title)) },
|
|
||||||
text = { Text(text = stringResource(R.string.pref_sync_confirmation_message)) },
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(R.string.action_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = onConfirm) {
|
|
||||||
Text(text = stringResource(android.R.string.ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class MissingRestoreComponents(
|
|
||||||
val uri: Uri,
|
|
||||||
val sources: List<String>,
|
|
||||||
val trackers: List<String>,
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class InvalidRestore(
|
|
||||||
val uri: Uri? = null,
|
|
||||||
val message: String,
|
|
||||||
)
|
|
@ -0,0 +1,549 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.text.format.Formatter
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
||||||
|
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||||
|
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||||
|
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||||
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
||||||
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
|
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
||||||
|
import eu.kanade.tachiyomi.data.sync.SyncManager
|
||||||
|
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService
|
||||||
|
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveSyncService
|
||||||
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.util.lang.launchNonCancellable
|
||||||
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object SettingsDataScreen : SearchableSettings {
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
@StringRes
|
||||||
|
override fun getTitleRes() = R.string.label_backup_and_sync
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun getPreferences(): List<Preference> {
|
||||||
|
val backupPreferences = Injekt.get<BackupPreferences>()
|
||||||
|
|
||||||
|
PermissionRequestHelper.requestStoragePermission()
|
||||||
|
|
||||||
|
val syncPreferences = remember { Injekt.get<SyncPreferences>() }
|
||||||
|
val syncService by syncPreferences.syncService().collectAsState()
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
getBackupAndRestoreGroup(backupPreferences = backupPreferences),
|
||||||
|
getDataGroup()) + listOf(
|
||||||
|
Preference.PreferenceGroup(
|
||||||
|
title = stringResource(R.string.label_sync),
|
||||||
|
preferenceItems = listOf(
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = syncPreferences.syncService(),
|
||||||
|
title = stringResource(R.string.pref_sync_service),
|
||||||
|
entries = mapOf(
|
||||||
|
SyncManager.SyncService.NONE.value to stringResource(R.string.off),
|
||||||
|
SyncManager.SyncService.SYNCYOMI.value to stringResource(R.string.syncyomi),
|
||||||
|
SyncManager.SyncService.GOOGLE_DRIVE.value to stringResource(R.string.google_drive),
|
||||||
|
),
|
||||||
|
onValueChanged = { true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) + getSyncServicePreferences(syncPreferences, syncService)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val backupIntervalPref = backupPreferences.backupInterval()
|
||||||
|
val backupInterval by backupIntervalPref.collectAsState()
|
||||||
|
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(R.string.label_backup),
|
||||||
|
preferenceItems = listOf(
|
||||||
|
// Manual actions
|
||||||
|
getCreateBackupPref(),
|
||||||
|
getRestoreBackupPref(),
|
||||||
|
|
||||||
|
// Automatic backups
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = backupIntervalPref,
|
||||||
|
title = stringResource(R.string.pref_backup_interval),
|
||||||
|
entries = mapOf(
|
||||||
|
0 to stringResource(R.string.off),
|
||||||
|
6 to stringResource(R.string.update_6hour),
|
||||||
|
12 to stringResource(R.string.update_12hour),
|
||||||
|
24 to stringResource(R.string.update_24hour),
|
||||||
|
48 to stringResource(R.string.update_48hour),
|
||||||
|
168 to stringResource(R.string.update_weekly),
|
||||||
|
),
|
||||||
|
onValueChanged = {
|
||||||
|
BackupCreateJob.setupTask(context, it)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = backupPreferences.numberOfBackups(),
|
||||||
|
enabled = backupInterval != 0,
|
||||||
|
title = stringResource(R.string.pref_backup_slots),
|
||||||
|
entries = listOf(2, 3, 4, 5).associateWith { it.toString() },
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(
|
||||||
|
stringResource(R.string.backup_info) + "\n\n" +
|
||||||
|
stringResource(R.string.last_auto_backup_info, relativeTimeSpanString(lastAutoBackup)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getCreateBackupPref(): Preference.PreferenceItem.TextPreference {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
return Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(R.string.pref_create_backup),
|
||||||
|
subtitle = stringResource(R.string.pref_create_backup_summ),
|
||||||
|
onClick = { navigator.push(CreateBackupScreen()) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var error by remember { mutableStateOf<Any?>(null) }
|
||||||
|
if (error != null) {
|
||||||
|
val onDismissRequest = { error = null }
|
||||||
|
when (val err = error) {
|
||||||
|
is InvalidRestore -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(R.string.invalid_backup_file)) },
|
||||||
|
text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
context.copyToClipboard(err.message, err.message)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_copy_to_clipboard))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(R.string.action_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is MissingRestoreComponents -> {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(R.string.pref_restore_backup)) },
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
val msg = buildString {
|
||||||
|
append(stringResource(R.string.backup_restore_content_full))
|
||||||
|
if (err.sources.isNotEmpty()) {
|
||||||
|
append("\n\n").append(stringResource(R.string.backup_restore_missing_sources))
|
||||||
|
err.sources.joinTo(
|
||||||
|
this,
|
||||||
|
separator = "\n- ",
|
||||||
|
prefix = "\n- ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (err.trackers.isNotEmpty()) {
|
||||||
|
append("\n\n").append(stringResource(R.string.backup_restore_missing_trackers))
|
||||||
|
err.trackers.joinTo(
|
||||||
|
this,
|
||||||
|
separator = "\n- ",
|
||||||
|
prefix = "\n- ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(text = msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
BackupRestoreJob.start(context, err.uri)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_restore))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> error = null // Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chooseBackup = rememberLauncherForActivityResult(
|
||||||
|
object : ActivityResultContracts.GetContent() {
|
||||||
|
override fun createIntent(context: Context, input: String): Intent {
|
||||||
|
val intent = super.createIntent(context, input)
|
||||||
|
return Intent.createChooser(intent, context.getString(R.string.file_select_backup))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (it == null) {
|
||||||
|
context.toast(R.string.file_null_uri_error)
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
val results = try {
|
||||||
|
BackupFileValidator().validate(context, it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
error = InvalidRestore(it, e.message.toString())
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
|
||||||
|
BackupRestoreJob.start(context, it)
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
|
||||||
|
error = MissingRestoreComponents(it, results.missingSources, results.missingTrackers)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(R.string.pref_restore_backup),
|
||||||
|
subtitle = stringResource(R.string.pref_restore_backup_summ),
|
||||||
|
onClick = {
|
||||||
|
if (!BackupRestoreJob.isRunning(context)) {
|
||||||
|
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
||||||
|
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
// no need to catch because it's wrapped with a chooser
|
||||||
|
chooseBackup.launch("*/*")
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.restore_in_progress)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getDataGroup(): Preference.PreferenceGroup {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||||
|
|
||||||
|
val chapterCache = remember { Injekt.get<ChapterCache>() }
|
||||||
|
var cacheReadableSizeSema by remember { mutableIntStateOf(0) }
|
||||||
|
val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(R.string.label_data),
|
||||||
|
preferenceItems = listOf(
|
||||||
|
getStorageInfoPref(cacheReadableSize),
|
||||||
|
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(R.string.pref_clear_chapter_cache),
|
||||||
|
subtitle = stringResource(R.string.used_cache, cacheReadableSize),
|
||||||
|
onClick = {
|
||||||
|
scope.launchNonCancellable {
|
||||||
|
try {
|
||||||
|
val deletedFiles = chapterCache.clear()
|
||||||
|
withUIContext {
|
||||||
|
context.toast(context.getString(R.string.cache_deleted, deletedFiles))
|
||||||
|
cacheReadableSizeSema++
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
withUIContext { context.toast(R.string.cache_delete_error) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = libraryPreferences.autoClearChapterCache(),
|
||||||
|
title = stringResource(R.string.pref_auto_clear_chapter_cache),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getStorageInfoPref(
|
||||||
|
chapterCacheReadableSize: String,
|
||||||
|
): Preference.PreferenceItem.CustomPreference {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val available = remember {
|
||||||
|
Formatter.formatFileSize(context, DiskUtil.getAvailableStorageSpace(Environment.getDataDirectory()))
|
||||||
|
}
|
||||||
|
val total = remember {
|
||||||
|
Formatter.formatFileSize(context, DiskUtil.getTotalStorageSpace(Environment.getDataDirectory()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Preference.PreferenceItem.CustomPreference(
|
||||||
|
title = stringResource(R.string.pref_storage_usage),
|
||||||
|
) {
|
||||||
|
BasePreferenceWidget(
|
||||||
|
title = stringResource(R.string.pref_storage_usage),
|
||||||
|
subcomponent = {
|
||||||
|
// TODO: downloads, SD cards, bar representation?, i18n
|
||||||
|
Box(modifier = Modifier.padding(horizontal = PrefsHorizontalPadding)) {
|
||||||
|
Text(text = "Available: $available / $total (chapter cache: $chapterCacheReadableSize)")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getSyncServicePreferences(syncPreferences: SyncPreferences, syncService: Int): List<Preference> {
|
||||||
|
val syncServiceType = SyncManager.SyncService.fromInt(syncService)
|
||||||
|
return when (syncServiceType) {
|
||||||
|
SyncManager.SyncService.NONE -> emptyList()
|
||||||
|
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
|
||||||
|
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
|
||||||
|
} +
|
||||||
|
if (syncServiceType == SyncManager.SyncService.NONE) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
listOf(getSyncNowPref(), getAutomaticSyncGroup(syncPreferences))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getGoogleDrivePreferences(): List<Preference> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val googleDriveSync = Injekt.get<GoogleDriveService>()
|
||||||
|
return listOf(
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(R.string.pref_google_drive_sign_in),
|
||||||
|
onClick = {
|
||||||
|
val intent = googleDriveSync.getSignInIntent()
|
||||||
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
getGoogleDrivePurge(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getGoogleDrivePurge(): Preference.PreferenceItem.TextPreference {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val showPurgeDialog = remember { mutableStateOf(false) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
val googleDriveSync = remember { GoogleDriveSyncService(context) }
|
||||||
|
|
||||||
|
if (showPurgeDialog.value) {
|
||||||
|
PurgeConfirmationDialog(
|
||||||
|
onConfirm = {
|
||||||
|
showPurgeDialog.value = false
|
||||||
|
scope.launch {
|
||||||
|
val result = googleDriveSync.deleteSyncDataFromGoogleDrive()
|
||||||
|
when (result) {
|
||||||
|
GoogleDriveSyncService.DeleteSyncDataStatus.NOT_INITIALIZED -> context.toast(R.string.google_drive_not_signed_in)
|
||||||
|
GoogleDriveSyncService.DeleteSyncDataStatus.NO_FILES -> context.toast(R.string.google_drive_sync_data_not_found)
|
||||||
|
GoogleDriveSyncService.DeleteSyncDataStatus.SUCCESS -> context.toast(R.string.google_drive_sync_data_purged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = { showPurgeDialog.value = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(R.string.pref_google_drive_purge_sync_data),
|
||||||
|
onClick = { showPurgeDialog.value = true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PurgeConfirmationDialog(
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(R.string.pref_purge_confirmation_title)) },
|
||||||
|
text = { Text(text = stringResource(R.string.pref_purge_confirmation_message)) },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List<Preference> {
|
||||||
|
return listOf(
|
||||||
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
|
title = stringResource(R.string.pref_sync_device_name),
|
||||||
|
subtitle = stringResource(R.string.pref_sync_device_name_summ),
|
||||||
|
pref = syncPreferences.deviceName(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
|
title = stringResource(R.string.pref_sync_host),
|
||||||
|
subtitle = stringResource(R.string.pref_sync_host_summ),
|
||||||
|
pref = syncPreferences.syncHost(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
|
title = stringResource(R.string.pref_sync_api_key),
|
||||||
|
subtitle = stringResource(R.string.pref_sync_api_key_summ),
|
||||||
|
pref = syncPreferences.syncAPIKey(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getSyncNowPref(): Preference.PreferenceGroup {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (showDialog) {
|
||||||
|
SyncConfirmationDialog(
|
||||||
|
onConfirm = {
|
||||||
|
showDialog = false
|
||||||
|
scope.launch {
|
||||||
|
if (!SyncDataJob.isAnyJobRunning(context)) {
|
||||||
|
SyncDataJob.startNow(context)
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.sync_in_progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(R.string.pref_sync_now_group_title),
|
||||||
|
preferenceItems = listOf(
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(R.string.pref_sync_now),
|
||||||
|
subtitle = stringResource(R.string.pref_sync_now_subtitle),
|
||||||
|
onClick = {
|
||||||
|
showDialog = true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getAutomaticSyncGroup(syncPreferences: SyncPreferences): Preference.PreferenceGroup {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val syncIntervalPref = syncPreferences.syncInterval()
|
||||||
|
val lastSync by syncPreferences.syncLastSync().collectAsState()
|
||||||
|
val formattedLastSync = DateUtils.getRelativeTimeSpanString(lastSync.toEpochMilli(), System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS)
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(R.string.pref_sync_service_category),
|
||||||
|
preferenceItems = listOf(
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = syncIntervalPref,
|
||||||
|
title = stringResource(R.string.pref_sync_interval),
|
||||||
|
entries = mapOf(
|
||||||
|
0 to stringResource(R.string.off),
|
||||||
|
30 to stringResource(R.string.update_30min),
|
||||||
|
60 to stringResource(R.string.update_1hour),
|
||||||
|
180 to stringResource(R.string.update_3hour),
|
||||||
|
360 to stringResource(R.string.update_6hour),
|
||||||
|
720 to stringResource(R.string.update_12hour),
|
||||||
|
1440 to stringResource(R.string.update_24hour),
|
||||||
|
2880 to stringResource(R.string.update_48hour),
|
||||||
|
10080 to stringResource(R.string.update_weekly),
|
||||||
|
),
|
||||||
|
onValueChanged = {
|
||||||
|
SyncDataJob.setupTask(context, it)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(stringResource(R.string.last_synchronization, formattedLastSync)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SyncConfirmationDialog(
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = { Text(text = stringResource(R.string.pref_sync_confirmation_title)) },
|
||||||
|
text = { Text(text = stringResource(R.string.pref_sync_confirmation_message)) },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class MissingRestoreComponents(
|
||||||
|
val uri: Uri,
|
||||||
|
val sources: List<String>,
|
||||||
|
val trackers: List<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class InvalidRestore(
|
||||||
|
val uri: Uri? = null,
|
||||||
|
val message: String,
|
||||||
|
)
|
@ -225,20 +225,28 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
pref = libraryPreferences.swipeToStartAction(),
|
pref = libraryPreferences.swipeToStartAction(),
|
||||||
title = stringResource(R.string.pref_chapter_swipe_start),
|
title = stringResource(R.string.pref_chapter_swipe_start),
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled),
|
LibraryPreferences.ChapterSwipeAction.Disabled to
|
||||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
|
stringResource(R.string.disabled),
|
||||||
LibraryPreferences.ChapterSwipeAction.ToggleRead to stringResource(R.string.action_mark_as_read),
|
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
|
||||||
LibraryPreferences.ChapterSwipeAction.Download to stringResource(R.string.action_download),
|
stringResource(R.string.action_bookmark),
|
||||||
|
LibraryPreferences.ChapterSwipeAction.ToggleRead to
|
||||||
|
stringResource(R.string.action_mark_as_read),
|
||||||
|
LibraryPreferences.ChapterSwipeAction.Download to
|
||||||
|
stringResource(R.string.action_download),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryPreferences.swipeToEndAction(),
|
pref = libraryPreferences.swipeToEndAction(),
|
||||||
title = stringResource(R.string.pref_chapter_swipe_end),
|
title = stringResource(R.string.pref_chapter_swipe_end),
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.disabled),
|
LibraryPreferences.ChapterSwipeAction.Disabled to
|
||||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
|
stringResource(R.string.disabled),
|
||||||
LibraryPreferences.ChapterSwipeAction.ToggleRead to stringResource(R.string.action_mark_as_read),
|
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
|
||||||
LibraryPreferences.ChapterSwipeAction.Download to stringResource(R.string.action_download),
|
stringResource(R.string.action_bookmark),
|
||||||
|
LibraryPreferences.ChapterSwipeAction.ToggleRead to
|
||||||
|
stringResource(R.string.action_mark_as_read),
|
||||||
|
LibraryPreferences.ChapterSwipeAction.Download to
|
||||||
|
stringResource(R.string.action_download),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -9,7 +9,6 @@ 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.automirrored.outlined.ChromeReaderMode
|
|
||||||
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
|
||||||
@ -19,7 +18,7 @@ import androidx.compose.material.icons.outlined.Info
|
|||||||
import androidx.compose.material.icons.outlined.Palette
|
import androidx.compose.material.icons.outlined.Palette
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material.icons.outlined.Security
|
import androidx.compose.material.icons.outlined.Security
|
||||||
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
import androidx.compose.material.icons.outlined.Storage
|
||||||
import androidx.compose.material.icons.outlined.Sync
|
import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -187,7 +186,7 @@ object SettingsMainScreen : Screen() {
|
|||||||
Item(
|
Item(
|
||||||
titleRes = R.string.pref_category_reader,
|
titleRes = R.string.pref_category_reader,
|
||||||
subtitleRes = R.string.pref_reader_summary,
|
subtitleRes = R.string.pref_reader_summary,
|
||||||
icon = Icons.AutoMirrored.Outlined.ChromeReaderMode,
|
icon = Icons.Outlined.ChromeReaderMode,
|
||||||
screen = SettingsReaderScreen,
|
screen = SettingsReaderScreen,
|
||||||
),
|
),
|
||||||
Item(
|
Item(
|
||||||
@ -210,9 +209,9 @@ object SettingsMainScreen : Screen() {
|
|||||||
),
|
),
|
||||||
Item(
|
Item(
|
||||||
titleRes = R.string.label_backup_and_sync,
|
titleRes = R.string.label_backup_and_sync,
|
||||||
subtitleRes = R.string.pref_backup_and_sync_summary,
|
subtitleRes = R.string.pref_backup_summary,
|
||||||
icon = Icons.Outlined.SettingsBackupRestore,
|
icon = Icons.Outlined.Storage,
|
||||||
screen = SettingsBackupAndSyncScreen,
|
screen = SettingsDataScreen,
|
||||||
),
|
),
|
||||||
Item(
|
Item(
|
||||||
titleRes = R.string.pref_category_security,
|
titleRes = R.string.pref_category_security,
|
||||||
|
@ -10,9 +10,9 @@ import androidx.compose.ui.platform.LocalView
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -32,7 +32,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPref.defaultReadingMode(),
|
pref = readerPref.defaultReadingMode(),
|
||||||
title = stringResource(R.string.pref_viewer_type),
|
title = stringResource(R.string.pref_viewer_type),
|
||||||
entries = ReadingModeType.entries.drop(1)
|
entries = ReadingMode.entries.drop(1)
|
||||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
@ -64,6 +64,11 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
pref = readerPref.pageTransitions(),
|
pref = readerPref.pageTransitions(),
|
||||||
title = stringResource(R.string.pref_page_transitions),
|
title = stringResource(R.string.pref_page_transitions),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = readerPref.flashOnPageChange(),
|
||||||
|
title = stringResource(R.string.pref_flash_page),
|
||||||
|
subtitle = stringResource(R.string.pref_flash_page_summ),
|
||||||
|
),
|
||||||
getDisplayGroup(readerPreferences = readerPref),
|
getDisplayGroup(readerPreferences = readerPref),
|
||||||
getReadingGroup(readerPreferences = readerPref),
|
getReadingGroup(readerPreferences = readerPref),
|
||||||
getPagedGroup(readerPreferences = readerPref),
|
getPagedGroup(readerPreferences = readerPref),
|
||||||
@ -83,7 +88,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.defaultOrientationType(),
|
pref = readerPreferences.defaultOrientationType(),
|
||||||
title = stringResource(R.string.pref_rotation_type),
|
title = stringResource(R.string.pref_rotation_type),
|
||||||
entries = OrientationType.entries.drop(1)
|
entries = ReaderOrientation.entries.drop(1)
|
||||||
.associate { it.flagValue to stringResource(it.stringRes) },
|
.associate { it.flagValue to stringResource(it.stringRes) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
@ -169,12 +174,12 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.pagerNavInverted(),
|
pref = readerPreferences.pagerNavInverted(),
|
||||||
title = stringResource(R.string.pref_read_with_tapping_inverted),
|
title = stringResource(R.string.pref_read_with_tapping_inverted),
|
||||||
entries = mapOf(
|
entries = listOf(
|
||||||
ReaderPreferences.TappingInvertMode.NONE to stringResource(R.string.none),
|
ReaderPreferences.TappingInvertMode.NONE,
|
||||||
ReaderPreferences.TappingInvertMode.HORIZONTAL to stringResource(R.string.tapping_inverted_horizontal),
|
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
||||||
ReaderPreferences.TappingInvertMode.VERTICAL to stringResource(R.string.tapping_inverted_vertical),
|
ReaderPreferences.TappingInvertMode.VERTICAL,
|
||||||
ReaderPreferences.TappingInvertMode.BOTH to stringResource(R.string.tapping_inverted_both),
|
ReaderPreferences.TappingInvertMode.BOTH,
|
||||||
),
|
).associateWith { stringResource(it.titleResId) },
|
||||||
enabled = navMode != 5,
|
enabled = navMode != 5,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
@ -261,12 +266,12 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPreferences.webtoonNavInverted(),
|
pref = readerPreferences.webtoonNavInverted(),
|
||||||
title = stringResource(R.string.pref_read_with_tapping_inverted),
|
title = stringResource(R.string.pref_read_with_tapping_inverted),
|
||||||
entries = mapOf(
|
entries = listOf(
|
||||||
ReaderPreferences.TappingInvertMode.NONE to stringResource(R.string.none),
|
ReaderPreferences.TappingInvertMode.NONE,
|
||||||
ReaderPreferences.TappingInvertMode.HORIZONTAL to stringResource(R.string.tapping_inverted_horizontal),
|
ReaderPreferences.TappingInvertMode.HORIZONTAL,
|
||||||
ReaderPreferences.TappingInvertMode.VERTICAL to stringResource(R.string.tapping_inverted_vertical),
|
ReaderPreferences.TappingInvertMode.VERTICAL,
|
||||||
ReaderPreferences.TappingInvertMode.BOTH to stringResource(R.string.tapping_inverted_both),
|
ReaderPreferences.TappingInvertMode.BOTH,
|
||||||
),
|
).associateWith { stringResource(it.titleResId) },
|
||||||
enabled = navMode != 5,
|
enabled = navMode != 5,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
@ -342,6 +347,11 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
pref = readerPreferences.readWithLongTap(),
|
pref = readerPreferences.readWithLongTap(),
|
||||||
title = stringResource(R.string.pref_read_with_long_tap),
|
title = stringResource(R.string.pref_read_with_long_tap),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = readerPreferences.folderPerManga(),
|
||||||
|
title = stringResource(R.string.pref_create_folder_per_manga),
|
||||||
|
subtitle = stringResource(R.string.pref_create_folder_per_manga_summary),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,11 @@ private fun SearchResult(
|
|||||||
SearchResultItem(
|
SearchResultItem(
|
||||||
route = settingsData.route,
|
route = settingsData.route,
|
||||||
title = p.title,
|
title = p.title,
|
||||||
breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle, isLtr = isLtr),
|
breadcrumbs = getLocalizedBreadcrumb(
|
||||||
|
path = settingsData.title,
|
||||||
|
node = categoryTitle,
|
||||||
|
isLtr = isLtr,
|
||||||
|
),
|
||||||
highlightKey = p.title,
|
highlightKey = p.title,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -291,7 +295,7 @@ private val settingScreens = listOf(
|
|||||||
SettingsDownloadScreen,
|
SettingsDownloadScreen,
|
||||||
SettingsTrackingScreen,
|
SettingsTrackingScreen,
|
||||||
SettingsBrowseScreen,
|
SettingsBrowseScreen,
|
||||||
SettingsBackupAndSyncScreen,
|
SettingsDataScreen,
|
||||||
SettingsSecurityScreen,
|
SettingsSecurityScreen,
|
||||||
SettingsAdvancedScreen,
|
SettingsAdvancedScreen,
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,6 @@ 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.automirrored.outlined.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.Close
|
||||||
@ -73,7 +72,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
|
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
|
imageVector = Icons.Outlined.HelpOutline,
|
||||||
contentDescription = stringResource(R.string.tracking_guide),
|
contentDescription = stringResource(R.string.tracking_guide),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,9 @@ object AboutScreen : Screen() {
|
|||||||
is GetApplicationRelease.Result.NoNewUpdate -> {
|
is GetApplicationRelease.Result.NoNewUpdate -> {
|
||||||
context.toast(R.string.update_check_no_new_updates)
|
context.toast(R.string.update_check_no_new_updates)
|
||||||
}
|
}
|
||||||
|
is GetApplicationRelease.Result.OsTooOld -> {
|
||||||
|
context.toast(R.string.update_check_eol)
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.screen.data
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags
|
||||||
|
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
|
class CreateBackupScreen : Screen() {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val model = rememberScreenModel { CreateBackupScreenModel() }
|
||||||
|
val state by model.state.collectAsState()
|
||||||
|
|
||||||
|
val chooseBackupDir = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.CreateDocument("application/*"),
|
||||||
|
) {
|
||||||
|
if (it != null) {
|
||||||
|
context.contentResolver.takePersistableUriPermission(
|
||||||
|
it,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||||
|
)
|
||||||
|
model.createBackup(context, it)
|
||||||
|
navigator.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(R.string.pref_create_backup),
|
||||||
|
navigateUp = navigator::pop,
|
||||||
|
scrollBehavior = it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { contentPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(contentPadding)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
LabeledCheckbox(
|
||||||
|
label = stringResource(R.string.manga),
|
||||||
|
checked = true,
|
||||||
|
onCheckedChange = {},
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
BackupChoices.forEach { (k, v) ->
|
||||||
|
item {
|
||||||
|
LabeledCheckbox(
|
||||||
|
label = stringResource(v),
|
||||||
|
checked = state.flags.contains(k),
|
||||||
|
onCheckedChange = {
|
||||||
|
model.toggleFlag(k)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
if (!BackupCreateJob.isManualJobRunning(context)) {
|
||||||
|
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
||||||
|
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
chooseBackupDir.launch(Backup.getFilename())
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
context.toast(R.string.file_picker_error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.backup_in_progress)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.action_create),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel.State>(State()) {
|
||||||
|
|
||||||
|
fun toggleFlag(flag: Int) {
|
||||||
|
mutableState.update {
|
||||||
|
if (it.flags.contains(flag)) {
|
||||||
|
it.copy(flags = it.flags - flag)
|
||||||
|
} else {
|
||||||
|
it.copy(flags = it.flags + flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createBackup(context: Context, uri: Uri) {
|
||||||
|
val flags = state.value.flags.fold(initial = 0, operation = { a, b -> a or b })
|
||||||
|
BackupCreateJob.startNow(context, uri, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class State(
|
||||||
|
val flags: Set<Int> = BackupChoices.keys,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val BackupChoices = mapOf(
|
||||||
|
BackupCreateFlags.BACKUP_CATEGORY to R.string.categories,
|
||||||
|
BackupCreateFlags.BACKUP_CHAPTER to R.string.chapters,
|
||||||
|
BackupCreateFlags.BACKUP_TRACK to R.string.track,
|
||||||
|
BackupCreateFlags.BACKUP_HISTORY to R.string.history,
|
||||||
|
BackupCreateFlags.BACKUP_APP_PREFS to R.string.app_settings,
|
||||||
|
BackupCreateFlags.BACKUP_SOURCE_PREFS to R.string.source_settings,
|
||||||
|
)
|
@ -78,7 +78,8 @@ class DebugInfoScreen : Screen() {
|
|||||||
value = when (result) {
|
value = when (result) {
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING -> "Compiled non-matching"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
|
||||||
|
"Compiled non-matching"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ,
|
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ,
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE,
|
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE,
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST,
|
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST,
|
||||||
|
@ -115,7 +115,9 @@ class WorkerInfoScreen : Screen() {
|
|||||||
private val workManager = context.workManager
|
private val workManager = context.workManager
|
||||||
|
|
||||||
val finished = workManager
|
val finished = workManager
|
||||||
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
|
.getWorkInfosLiveData(
|
||||||
|
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
||||||
|
)
|
||||||
.asFlow()
|
.asFlow()
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
@ -38,6 +38,7 @@ import androidx.compose.ui.draw.alpha
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
import eu.kanade.domain.ui.model.AppTheme
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
@ -46,7 +47,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -249,11 +249,12 @@ fun AppThemePreviewItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun AppThemesListPreview() {
|
private fun AppThemesListPreview() {
|
||||||
var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) }
|
var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) }
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
AppThemesList(
|
AppThemesList(
|
||||||
currentTheme = appTheme,
|
currentTheme = appTheme,
|
||||||
amoled = false,
|
amoled = false,
|
||||||
@ -261,3 +262,4 @@ private fun AppThemesListPreview() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -12,10 +12,10 @@ 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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -40,7 +40,7 @@ internal fun InfoWidget(text: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoWidgetPreview() {
|
private fun InfoWidgetPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -9,8 +9,8 @@ import androidx.compose.material3.Switch
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchPreferenceWidget(
|
fun SwitchPreferenceWidget(
|
||||||
@ -37,7 +37,7 @@ fun SwitchPreferenceWidget(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun SwitchPreferenceWidgetPreview() {
|
private fun SwitchPreferenceWidgetPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -12,8 +12,8 @@ import androidx.compose.runtime.Composable
|
|||||||
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.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -59,7 +59,7 @@ fun TextPreferenceWidget(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TextPreferenceWidgetPreview() {
|
private fun TextPreferenceWidgetPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -115,8 +115,16 @@ fun <T> TriStateListDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listState.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
if (!listState.isScrolledToStart()) {
|
||||||
if (!listState.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!listState.isScrolledToEnd()) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package eu.kanade.presentation.reader
|
||||||
|
|
||||||
|
import androidx.annotation.IntRange
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BrightnessOverlay(
|
||||||
|
@IntRange(from = -100, to = 100) value: Int,
|
||||||
|
) {
|
||||||
|
if (value >= 0) return
|
||||||
|
|
||||||
|
Canvas(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.graphicsLayer {
|
||||||
|
alpha = abs(value) / 100f
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
drawRect(Color.Black)
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.widthIn
|
|||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.foundation.text.appendInlineContent
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
import androidx.compose.material.icons.outlined.OfflinePin
|
|
||||||
import androidx.compose.material.icons.outlined.Warning
|
import androidx.compose.material.icons.outlined.Warning
|
||||||
import androidx.compose.material3.CardColors
|
import androidx.compose.material3.CardColors
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@ -32,6 +32,7 @@ import androidx.compose.ui.text.Placeholder
|
|||||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
@ -42,7 +43,6 @@ import eu.kanade.tachiyomi.data.database.models.toDomainChapter
|
|||||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||||
import tachiyomi.domain.chapter.service.calculateChapterGap
|
import tachiyomi.domain.chapter.service.calculateChapterGap
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -244,7 +244,7 @@ private fun ChapterText(
|
|||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.OfflinePin,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = stringResource(R.string.label_downloaded),
|
contentDescription = stringResource(R.string.label_downloaded),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -304,7 +304,7 @@ private val FakeChapterLongTitle = previewChapter(
|
|||||||
chapterNumber = 1f,
|
chapterNumber = 1f,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TransitionTextPreview() {
|
private fun TransitionTextPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
@ -318,7 +318,7 @@ private fun TransitionTextPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TransitionTextLongTitlePreview() {
|
private fun TransitionTextLongTitlePreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
@ -332,7 +332,7 @@ private fun TransitionTextLongTitlePreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TransitionTextWithGapPreview() {
|
private fun TransitionTextWithGapPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
@ -346,7 +346,7 @@ private fun TransitionTextWithGapPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TransitionTextNoNextPreview() {
|
private fun TransitionTextNoNextPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
@ -360,7 +360,7 @@ private fun TransitionTextNoNextPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TransitionTextNoPreviousPreview() {
|
private fun TransitionTextNoPreviousPreview() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package eu.kanade.presentation.reader
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class DisplayRefreshHost {
|
||||||
|
|
||||||
|
internal var currentDisplayRefresh by mutableStateOf(false)
|
||||||
|
|
||||||
|
fun flash() {
|
||||||
|
currentDisplayRefresh = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayRefreshHost(
|
||||||
|
hostState: DisplayRefreshHost,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val currentDisplayRefresh = hostState.currentDisplayRefresh
|
||||||
|
LaunchedEffect(currentDisplayRefresh) {
|
||||||
|
if (currentDisplayRefresh) {
|
||||||
|
delay(200)
|
||||||
|
hostState.currentDisplayRefresh = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDisplayRefresh) {
|
||||||
|
Canvas(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
drawRect(Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
package eu.kanade.presentation.reader
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.res.vectorResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.domain.manga.model.orientationType
|
|
||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
|
||||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
|
||||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
|
||||||
|
|
||||||
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun OrientationModeSelectDialog(
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
screenModel: ReaderSettingsScreenModel,
|
|
||||||
onChange: (Int) -> Unit,
|
|
||||||
) {
|
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
|
||||||
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
|
|
||||||
|
|
||||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
|
||||||
Box(modifier = Modifier.padding(vertical = 16.dp)) {
|
|
||||||
SettingsIconGrid(R.string.rotation_type) {
|
|
||||||
items(orientationTypeOptions) { (stringRes, mode) ->
|
|
||||||
IconToggleButton(
|
|
||||||
checked = mode == orientationType,
|
|
||||||
onCheckedChange = {
|
|
||||||
screenModel.onChangeOrientation(mode)
|
|
||||||
onChange(stringRes)
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
|
||||||
title = stringResource(stringRes),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,100 @@
|
|||||||
|
package eu.kanade.presentation.reader
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import eu.kanade.domain.manga.model.readerOrientation
|
||||||
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
|
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
|
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||||
|
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||||
|
|
||||||
|
private val ReaderOrientationsWithoutDefault = ReaderOrientation.entries - ReaderOrientation.DEFAULT
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OrientationSelectDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
screenModel: ReaderSettingsScreenModel,
|
||||||
|
onChange: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
val manga by screenModel.mangaFlow.collectAsState()
|
||||||
|
val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
|
||||||
|
|
||||||
|
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||||
|
DialogContent(
|
||||||
|
orientation = orientation,
|
||||||
|
onChangeOrientation = {
|
||||||
|
screenModel.onChangeOrientation(it)
|
||||||
|
onChange(it.stringRes)
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DialogContent(
|
||||||
|
orientation: ReaderOrientation,
|
||||||
|
onChangeOrientation: (ReaderOrientation) -> Unit,
|
||||||
|
) {
|
||||||
|
var selected by remember { mutableStateOf(orientation) }
|
||||||
|
|
||||||
|
ModeSelectionDialog(
|
||||||
|
onUseDefault = {
|
||||||
|
onChangeOrientation(
|
||||||
|
ReaderOrientation.DEFAULT,
|
||||||
|
)
|
||||||
|
}.takeIf { orientation != ReaderOrientation.DEFAULT },
|
||||||
|
onApply = { onChangeOrientation(selected) },
|
||||||
|
) {
|
||||||
|
SettingsIconGrid(R.string.rotation_type) {
|
||||||
|
items(ReaderOrientationsWithoutDefault) { mode ->
|
||||||
|
IconToggleButton(
|
||||||
|
checked = mode == selected,
|
||||||
|
onCheckedChange = {
|
||||||
|
selected = mode
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||||
|
title = stringResource(mode.stringRes),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
private fun DialogContentPreview() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
|
Column {
|
||||||
|
DialogContent(
|
||||||
|
orientation = ReaderOrientation.DEFAULT,
|
||||||
|
onChangeOrientation = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
DialogContent(
|
||||||
|
orientation = ReaderOrientation.FREE,
|
||||||
|
onChangeOrientation = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,17 @@ package eu.kanade.presentation.reader
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PageIndicatorText(
|
fun PageIndicatorText(
|
||||||
@ -19,24 +23,37 @@ fun PageIndicatorText(
|
|||||||
|
|
||||||
val text = "$currentPage / $totalPages"
|
val text = "$currentPage / $totalPages"
|
||||||
|
|
||||||
Box {
|
val style = TextStyle(
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
color = Color(45, 45, 45),
|
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
letterSpacing = 1.sp,
|
|
||||||
style = TextStyle.Default.copy(
|
|
||||||
drawStyle = Stroke(width = 4f),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
color = Color(235, 235, 235),
|
color = Color(235, 235, 235),
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
letterSpacing = 1.sp,
|
letterSpacing = 1.sp,
|
||||||
)
|
)
|
||||||
|
val strokeStyle = style.copy(
|
||||||
|
color = Color(45, 45, 45),
|
||||||
|
drawStyle = Stroke(width = 4f),
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = strokeStyle,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = style,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
private fun PageIndicatorTextPreview() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
|
PageIndicatorText(currentPage = 10, totalPages = 69)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
package eu.kanade.presentation.reader
|
package eu.kanade.presentation.reader
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import eu.kanade.domain.manga.model.readingModeType
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import eu.kanade.domain.manga.model.readingMode
|
||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
|
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
|
|
||||||
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
|
private val ReadingModesWithoutDefault = ReadingMode.entries - ReadingMode.DEFAULT
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReadingModeSelectDialog(
|
fun ReadingModeSelectDialog(
|
||||||
@ -31,25 +34,63 @@ fun ReadingModeSelectDialog(
|
|||||||
onChange: (Int) -> Unit,
|
onChange: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by screenModel.mangaFlow.collectAsState()
|
||||||
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
|
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
||||||
|
|
||||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||||
Box(modifier = Modifier.padding(vertical = MaterialTheme.padding.medium)) {
|
DialogContent(
|
||||||
SettingsIconGrid(R.string.pref_category_reading_mode) {
|
readingMode = readingMode,
|
||||||
items(readingModeOptions) { (stringRes, mode) ->
|
onChangeReadingMode = {
|
||||||
IconToggleButton(
|
screenModel.onChangeReadingMode(it)
|
||||||
checked = mode == readingMode,
|
onChange(it.stringRes)
|
||||||
onCheckedChange = {
|
|
||||||
screenModel.onChangeReadingMode(mode)
|
|
||||||
onChange(stringRes)
|
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DialogContent(
|
||||||
|
readingMode: ReadingMode,
|
||||||
|
onChangeReadingMode: (ReadingMode) -> Unit,
|
||||||
|
) {
|
||||||
|
var selected by remember { mutableStateOf(readingMode) }
|
||||||
|
|
||||||
|
ModeSelectionDialog(
|
||||||
|
onUseDefault = { onChangeReadingMode(ReadingMode.DEFAULT) }.takeIf { readingMode != ReadingMode.DEFAULT },
|
||||||
|
onApply = { onChangeReadingMode(selected) },
|
||||||
|
) {
|
||||||
|
SettingsIconGrid(R.string.pref_category_reading_mode) {
|
||||||
|
items(ReadingModesWithoutDefault) { mode ->
|
||||||
|
IconToggleButton(
|
||||||
|
checked = mode == selected,
|
||||||
|
onCheckedChange = {
|
||||||
|
selected = mode
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||||
title = stringResource(stringRes),
|
title = stringResource(mode.stringRes),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
private fun DialogContentPreview() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
|
Column {
|
||||||
|
DialogContent(
|
||||||
|
readingMode = ReadingMode.DEFAULT,
|
||||||
|
onChangeReadingMode = {},
|
||||||
|
)
|
||||||
|
|
||||||
|
DialogContent(
|
||||||
|
readingMode = ReadingMode.LEFT_TO_RIGHT,
|
||||||
|
onChangeReadingMode = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,16 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomReaderBar(
|
fun BottomReaderBar(
|
||||||
backgroundColor: Color,
|
backgroundColor: Color,
|
||||||
readingMode: ReadingModeType,
|
readingMode: ReadingMode,
|
||||||
onClickReadingMode: () -> Unit,
|
onClickReadingMode: () -> Unit,
|
||||||
orientationMode: OrientationType,
|
orientation: ReaderOrientation,
|
||||||
onClickOrientationMode: () -> Unit,
|
onClickOrientation: () -> Unit,
|
||||||
cropEnabled: Boolean,
|
cropEnabled: Boolean,
|
||||||
onClickCropBorder: () -> Unit,
|
onClickCropBorder: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
@ -46,6 +46,13 @@ fun BottomReaderBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = onClickOrientation) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(orientation.iconRes),
|
||||||
|
contentDescription = stringResource(R.string.rotation_type),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(onClick = onClickCropBorder) {
|
IconButton(onClick = onClickCropBorder) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp),
|
painter = painterResource(if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp),
|
||||||
@ -53,13 +60,6 @@ fun BottomReaderBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(onClick = onClickOrientationMode) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(orientationMode.iconRes),
|
|
||||||
contentDescription = stringResource(R.string.pref_rotation_type),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = onClickSettings) {
|
IconButton(onClick = onClickSettings) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Settings,
|
imageVector = Icons.Outlined.Settings,
|
||||||
|
@ -9,10 +9,8 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Bookmark
|
import androidx.compose.material.icons.outlined.Bookmark
|
||||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||||
@ -25,9 +23,10 @@ import androidx.compose.ui.unit.IntOffset
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.reader.components.ChapterNavigator
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
|
|
||||||
@ -56,10 +55,10 @@ fun ReaderAppBars(
|
|||||||
totalPages: Int,
|
totalPages: Int,
|
||||||
onSliderValueChange: (Int) -> Unit,
|
onSliderValueChange: (Int) -> Unit,
|
||||||
|
|
||||||
readingMode: ReadingModeType,
|
readingMode: ReadingMode,
|
||||||
onClickReadingMode: () -> Unit,
|
onClickReadingMode: () -> Unit,
|
||||||
orientationMode: OrientationType,
|
orientation: ReaderOrientation,
|
||||||
onClickOrientationMode: () -> Unit,
|
onClickOrientation: () -> Unit,
|
||||||
cropEnabled: Boolean,
|
cropEnabled: Boolean,
|
||||||
onClickCropBorder: () -> Unit,
|
onClickCropBorder: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
@ -69,8 +68,8 @@ fun ReaderAppBars(
|
|||||||
.surfaceColorAtElevation(3.dp)
|
.surfaceColorAtElevation(3.dp)
|
||||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||||
|
|
||||||
val appBarModifier = if (fullscreen) {
|
val modifierWithInsetsPadding = if (fullscreen) {
|
||||||
Modifier.windowInsetsPadding(WindowInsets.systemBars)
|
Modifier.systemBarsPadding()
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
@ -91,7 +90,7 @@ fun ReaderAppBars(
|
|||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
AppBar(
|
AppBar(
|
||||||
modifier = appBarModifier
|
modifier = modifierWithInsetsPadding
|
||||||
.clickable(onClick = onClickTopAppBar),
|
.clickable(onClick = onClickTopAppBar),
|
||||||
backgroundColor = backgroundColor,
|
backgroundColor = backgroundColor,
|
||||||
title = mangaTitle,
|
title = mangaTitle,
|
||||||
@ -101,7 +100,9 @@ fun ReaderAppBars(
|
|||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(if (bookmarked) R.string.action_remove_bookmark else R.string.action_bookmark),
|
title = stringResource(
|
||||||
|
if (bookmarked) R.string.action_remove_bookmark else R.string.action_bookmark,
|
||||||
|
),
|
||||||
icon = if (bookmarked) Icons.Outlined.Bookmark else Icons.Outlined.BookmarkBorder,
|
icon = if (bookmarked) Icons.Outlined.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||||
onClick = onToggleBookmarked,
|
onClick = onToggleBookmarked,
|
||||||
),
|
),
|
||||||
@ -137,6 +138,7 @@ fun ReaderAppBars(
|
|||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
modifier = modifierWithInsetsPadding,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
ChapterNavigator(
|
ChapterNavigator(
|
||||||
@ -154,8 +156,8 @@ fun ReaderAppBars(
|
|||||||
backgroundColor = backgroundColor,
|
backgroundColor = backgroundColor,
|
||||||
readingMode = readingMode,
|
readingMode = readingMode,
|
||||||
onClickReadingMode = onClickReadingMode,
|
onClickReadingMode = onClickReadingMode,
|
||||||
orientationMode = orientationMode,
|
orientation = orientation,
|
||||||
onClickOrientationMode = onClickOrientationMode,
|
onClickOrientation = onClickOrientation,
|
||||||
cropEnabled = cropEnabled,
|
cropEnabled = cropEnabled,
|
||||||
onClickCropBorder = onClickCropBorder,
|
onClickCropBorder = onClickCropBorder,
|
||||||
onClickSettings = onClickSettings,
|
onClickSettings = onClickSettings,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.presentation.reader.appbars
|
package eu.kanade.presentation.reader.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
@ -77,7 +77,9 @@ fun ChapterNavigator(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.SkipPrevious,
|
imageVector = Icons.Outlined.SkipPrevious,
|
||||||
contentDescription = stringResource(if (isRtl) R.string.action_next_chapter else R.string.action_previous_chapter),
|
contentDescription = stringResource(
|
||||||
|
if (isRtl) R.string.action_next_chapter else R.string.action_previous_chapter,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +129,9 @@ fun ChapterNavigator(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.SkipNext,
|
imageVector = Icons.Outlined.SkipNext,
|
||||||
contentDescription = stringResource(if (isRtl) R.string.action_previous_chapter else R.string.action_next_chapter),
|
contentDescription = stringResource(
|
||||||
|
if (isRtl) R.string.action_previous_chapter else R.string.action_next_chapter,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package eu.kanade.presentation.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Check
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.presentation.core.components.SettingsItemsPaddings
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModeSelectionDialog(
|
||||||
|
onApply: () -> Unit,
|
||||||
|
onUseDefault: (() -> Unit)? = null,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||||
|
Column {
|
||||||
|
content()
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = SettingsItemsPaddings.Horizontal,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
onUseDefault?.let {
|
||||||
|
OutlinedButton(onClick = it) {
|
||||||
|
Text(text = stringResource(R.string.action_revert_to_default))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = onApply,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(text = stringResource(R.string.action_apply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewLightDark
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
|
Column {
|
||||||
|
ModeSelectionDialog(
|
||||||
|
onApply = {},
|
||||||
|
onUseDefault = {},
|
||||||
|
) {
|
||||||
|
Text("Dummy content")
|
||||||
|
}
|
||||||
|
|
||||||
|
ModeSelectionDialog(
|
||||||
|
onApply = {},
|
||||||
|
) {
|
||||||
|
Text("Dummy content without default")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,4 +68,9 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
label = stringResource(R.string.pref_page_transitions),
|
label = stringResource(R.string.pref_page_transitions),
|
||||||
pref = screenModel.preferences.pageTransitions(),
|
pref = screenModel.preferences.pageTransitions(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CheckboxItem(
|
||||||
|
label = stringResource(R.string.pref_flash_page),
|
||||||
|
pref = screenModel.preferences.flashOnPageChange(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,13 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.domain.manga.model.orientationType
|
import eu.kanade.domain.manga.model.readerOrientation
|
||||||
import eu.kanade.domain.manga.model.readingModeType
|
import eu.kanade.domain.manga.model.readingMode
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
@ -23,33 +23,29 @@ import tachiyomi.presentation.core.components.SliderItem
|
|||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
|
|
||||||
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
|
|
||||||
private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.entries.map { it.titleResId to it }
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
||||||
HeadingItem(R.string.pref_category_for_this_series)
|
HeadingItem(R.string.pref_category_for_this_series)
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by screenModel.mangaFlow.collectAsState()
|
||||||
|
|
||||||
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
|
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
||||||
SettingsChipRow(R.string.pref_category_reading_mode) {
|
SettingsChipRow(R.string.pref_category_reading_mode) {
|
||||||
readingModeOptions.map { (stringRes, it) ->
|
ReadingMode.entries.map {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = it == readingMode,
|
selected = it == readingMode,
|
||||||
onClick = { screenModel.onChangeReadingMode(it) },
|
onClick = { screenModel.onChangeReadingMode(it) },
|
||||||
label = { Text(stringResource(stringRes)) },
|
label = { Text(stringResource(it.stringRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
|
val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
|
||||||
SettingsChipRow(R.string.rotation_type) {
|
SettingsChipRow(R.string.rotation_type) {
|
||||||
orientationTypeOptions.map { (stringRes, it) ->
|
ReaderOrientation.entries.map {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = it == orientationType,
|
selected = it == orientation,
|
||||||
onClick = { screenModel.onChangeOrientation(it) },
|
onClick = { screenModel.onChangeOrientation(it) },
|
||||||
label = { Text(stringResource(stringRes)) },
|
label = { Text(stringResource(it.stringRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,11 +205,11 @@ private fun ColumnScope.TapZonesItems(
|
|||||||
|
|
||||||
if (selected != 5) {
|
if (selected != 5) {
|
||||||
SettingsChipRow(R.string.pref_read_with_tapping_inverted) {
|
SettingsChipRow(R.string.pref_read_with_tapping_inverted) {
|
||||||
tappingInvertModeOptions.map { (stringRes, mode) ->
|
ReaderPreferences.TappingInvertMode.entries.map {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = mode == invertMode,
|
selected = it == invertMode,
|
||||||
onClick = { onSelectInvertMode(mode) },
|
onClick = { onSelectInvertMode(it) },
|
||||||
label = { Text(stringResource(stringRes)) },
|
label = { Text(stringResource(it.titleResId)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.VerticalDivider
|
import androidx.compose.material3.VerticalDivider
|
||||||
@ -44,6 +45,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
@ -54,7 +56,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
|
|
||||||
private const val UnsetStatusTextAlpha = 0.5F
|
private const val UnsetStatusTextAlpha = 0.5F
|
||||||
@ -318,11 +319,15 @@ private fun TrackInfoItemMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TrackInfoDialogHomePreviews(
|
private fun TrackInfoDialogHomePreviews(
|
||||||
@PreviewParameter(TrackInfoDialogHomePreviewProvider::class)
|
@PreviewParameter(TrackInfoDialogHomePreviewProvider::class)
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
TachiyomiTheme { content() }
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.SelectableDates
|
import androidx.compose.material3.SelectableDates
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||||
@ -29,6 +30,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -37,7 +39,6 @@ import tachiyomi.presentation.core.components.WheelNumberPicker
|
|||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
import tachiyomi.presentation.core.util.isScrolledToStart
|
||||||
|
|
||||||
@ -221,10 +222,11 @@ private fun BaseSelector(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TrackStatusSelectorPreviews() {
|
private fun TrackStatusSelectorPreviews() {
|
||||||
TachiyomiTheme {
|
TachiyomiTheme {
|
||||||
|
Surface {
|
||||||
TrackStatusSelector(
|
TrackStatusSelector(
|
||||||
selection = 1,
|
selection = 1,
|
||||||
onSelectionChange = {},
|
onSelectionChange = {},
|
||||||
@ -242,3 +244,4 @@ private fun TrackStatusSelectorPreviews() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -28,7 +28,6 @@ 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.automirrored.outlined.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
@ -57,6 +56,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||||||
import androidx.compose.ui.text.intl.Locale
|
import androidx.compose.ui.text.intl.Locale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.text.toLowerCase
|
import androidx.compose.ui.text.toLowerCase
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
@ -68,7 +68,6 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
@ -98,7 +97,7 @@ fun TrackerSearch(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onDismissRequest) {
|
IconButton(onClick = onDismissRequest) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
imageVector = Icons.Default.ArrowBack,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@ -241,7 +240,7 @@ private fun SearchResultItem(
|
|||||||
) {
|
) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Filled.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.align(Alignment.TopEnd),
|
modifier = Modifier.align(Alignment.TopEnd),
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
@ -320,7 +319,7 @@ private fun SearchResultItemDetails(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TrackerSearchPreviews(
|
private fun TrackerSearchPreviews(
|
||||||
@PreviewParameter(TrackerSearchPreviewProvider::class)
|
@PreviewParameter(TrackerSearchPreviewProvider::class)
|
||||||
|
@ -11,11 +11,11 @@ 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.tooling.preview.PreviewLightDark
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
|
||||||
import tachiyomi.presentation.core.util.clickableNoIndication
|
import tachiyomi.presentation.core.util.clickableNoIndication
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -43,7 +43,7 @@ fun TrackLogoIcon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun TrackLogoIconPreviews(
|
private fun TrackLogoIconPreviews(
|
||||||
@PreviewParameter(TrackLogoIconPreviewProvider::class)
|
@PreviewParameter(TrackLogoIconPreviewProvider::class)
|
||||||
|
@ -109,9 +109,7 @@ fun UpdateScreen(
|
|||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
if (lastUpdated > 0L) {
|
|
||||||
updatesLastUpdatedItem(lastUpdated)
|
updatesLastUpdatedItem(lastUpdated)
|
||||||
}
|
|
||||||
|
|
||||||
updatesUiItems(
|
updatesUiItems(
|
||||||
uiModels = state.getUiModel(context, relativeTime),
|
uiModels = state.getUiModel(context, relativeTime),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.presentation.updates
|
package eu.kanade.presentation.updates
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -27,7 +26,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
@ -39,6 +37,7 @@ import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
|||||||
import eu.kanade.presentation.manga.components.ChapterDownloadIndicator
|
import eu.kanade.presentation.manga.components.ChapterDownloadIndicator
|
||||||
import eu.kanade.presentation.manga.components.DotSeparatorText
|
import eu.kanade.presentation.manga.components.DotSeparatorText
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
|
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||||
@ -47,33 +46,18 @@ import tachiyomi.presentation.core.components.ListGroupHeader
|
|||||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import java.util.Date
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
|
||||||
|
|
||||||
internal fun LazyListScope.updatesLastUpdatedItem(
|
internal fun LazyListScope.updatesLastUpdatedItem(
|
||||||
lastUpdated: Long,
|
lastUpdated: Long,
|
||||||
) {
|
) {
|
||||||
item(key = "updates-lastUpdated") {
|
item(key = "updates-lastUpdated") {
|
||||||
val time = remember(lastUpdated) {
|
|
||||||
val now = Date().time
|
|
||||||
if (now - lastUpdated < 1.minutes.inWholeMilliseconds) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
DateUtils.getRelativeTimeSpanString(lastUpdated, now, DateUtils.MINUTE_IN_MILLIS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (time.isNullOrEmpty()) {
|
text = stringResource(R.string.updates_last_update_info, relativeTimeSpanString(lastUpdated)),
|
||||||
stringResource(R.string.updates_last_update_info, stringResource(R.string.updates_last_update_info_just_now))
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.updates_last_update_info, time)
|
|
||||||
},
|
|
||||||
fontStyle = FontStyle.Italic,
|
fontStyle = FontStyle.Italic,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import java.util.Date
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
fun Duration.toDurationString(context: Context, fallback: String): String {
|
fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||||
return toComponents { days, hours, minutes, seconds, _ ->
|
return toComponents { days, hours, minutes, seconds, _ ->
|
||||||
@ -14,3 +20,14 @@ fun Duration.toDurationString(context: Context, fallback: String): String {
|
|||||||
}.joinToString(" ").ifBlank { fallback }
|
}.joinToString(" ").ifBlank { fallback }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
fun relativeTimeSpanString(epochMillis: Long): String {
|
||||||
|
val now = Date().time
|
||||||
|
return when {
|
||||||
|
epochMillis <= 0L -> stringResource(R.string.relative_time_span_never)
|
||||||
|
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(R.string.updates_last_update_info_just_now)
|
||||||
|
else -> DateUtils.getRelativeTimeSpanString(epochMillis, now, DateUtils.MINUTE_IN_MILLIS).toString()
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
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.automirrored.outlined.ArrowBack
|
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
|
||||||
import androidx.compose.material.icons.outlined.ArrowBack
|
import androidx.compose.material.icons.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.ArrowForward
|
import androidx.compose.material.icons.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
@ -100,6 +98,12 @@ fun WebViewScreenContent(
|
|||||||
request: WebResourceRequest?,
|
request: WebResourceRequest?,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
request?.let {
|
request?.let {
|
||||||
|
// Don't attempt to open blobs as webpages
|
||||||
|
if (it.url.toString().startsWith("blob:http")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with request, but with custom headers
|
||||||
view?.loadUrl(it.url.toString(), headers)
|
view?.loadUrl(it.url.toString(), headers)
|
||||||
}
|
}
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
@ -121,7 +125,7 @@ fun WebViewScreenContent(
|
|||||||
listOf(
|
listOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_webview_back),
|
title = stringResource(R.string.action_webview_back),
|
||||||
icon = Icons.AutoMirrored.Outlined.ArrowBack,
|
icon = Icons.Outlined.ArrowBack,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (navigator.canGoBack) {
|
if (navigator.canGoBack) {
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
@ -131,7 +135,7 @@ fun WebViewScreenContent(
|
|||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_webview_forward),
|
title = stringResource(R.string.action_webview_forward),
|
||||||
icon = Icons.AutoMirrored.Outlined.ArrowForward,
|
icon = Icons.Outlined.ArrowForward,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (navigator.canGoForward) {
|
if (navigator.canGoForward) {
|
||||||
navigator.navigateForward()
|
navigator.navigateForward()
|
||||||
@ -169,7 +173,9 @@ fun WebViewScreenContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.clickable {
|
.clickable {
|
||||||
uriHandler.openUri("https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare")
|
uriHandler.openUri(
|
||||||
|
"https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare",
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -182,7 +188,7 @@ fun WebViewScreenContent(
|
|||||||
.align(Alignment.BottomCenter),
|
.align(Alignment.BottomCenter),
|
||||||
)
|
)
|
||||||
is LoadingState.Loading -> LinearProgressIndicator(
|
is LoadingState.Loading -> LinearProgressIndicator(
|
||||||
progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f },
|
progress = (loadingState as? LoadingState.Loading)?.progress ?: 1f,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.BottomCenter),
|
.align(Alignment.BottomCenter),
|
||||||
|
@ -11,6 +11,7 @@ import android.content.IntentFilter
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
@ -185,7 +186,7 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) {
|
if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) {
|
||||||
return WebViewUtil.SPOOF_PACKAGE_NAME
|
return WebViewUtil.SPOOF_PACKAGE_NAME
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.getPackageName()
|
return super.getPackageName()
|
||||||
@ -222,7 +223,12 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
|
|||||||
|
|
||||||
fun register() {
|
fun register() {
|
||||||
if (!registered) {
|
if (!registered) {
|
||||||
registerReceiver(this, IntentFilter(ACTION_DISABLE_INCOGNITO_MODE))
|
ContextCompat.registerReceiver(
|
||||||
|
this@App,
|
||||||
|
this,
|
||||||
|
IntentFilter(ACTION_DISABLE_INCOGNITO_MODE),
|
||||||
|
ContextCompat.RECEIVER_NOT_EXPORTED,
|
||||||
|
)
|
||||||
registered = true
|
registered = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +247,7 @@ private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNIT
|
|||||||
/**
|
/**
|
||||||
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
|
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
|
||||||
*/
|
*/
|
||||||
internal object CoilDiskCache {
|
private object CoilDiskCache {
|
||||||
|
|
||||||
private const val FOLDER_NAME = "image_cache"
|
private const val FOLDER_NAME = "image_cache"
|
||||||
private var instance: DiskCache? = null
|
private var instance: DiskCache? = null
|
||||||
|
@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -121,13 +121,22 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
putInt(libraryPreferences.filterDownloaded().key(), convertBooleanPrefToTriState("pref_filter_downloaded_key"))
|
putInt(
|
||||||
|
libraryPreferences.filterDownloaded().key(),
|
||||||
|
convertBooleanPrefToTriState("pref_filter_downloaded_key"),
|
||||||
|
)
|
||||||
remove("pref_filter_downloaded_key")
|
remove("pref_filter_downloaded_key")
|
||||||
|
|
||||||
putInt(libraryPreferences.filterUnread().key(), convertBooleanPrefToTriState("pref_filter_unread_key"))
|
putInt(
|
||||||
|
libraryPreferences.filterUnread().key(),
|
||||||
|
convertBooleanPrefToTriState("pref_filter_unread_key"),
|
||||||
|
)
|
||||||
remove("pref_filter_unread_key")
|
remove("pref_filter_unread_key")
|
||||||
|
|
||||||
putInt(libraryPreferences.filterCompleted().key(), convertBooleanPrefToTriState("pref_filter_completed_key"))
|
putInt(
|
||||||
|
libraryPreferences.filterCompleted().key(),
|
||||||
|
convertBooleanPrefToTriState("pref_filter_completed_key"),
|
||||||
|
)
|
||||||
remove("pref_filter_completed_key")
|
remove("pref_filter_completed_key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,12 +170,12 @@ object Migrations {
|
|||||||
if (oldVersion < 60) {
|
if (oldVersion < 60) {
|
||||||
// Migrate Rotation and Viewer values to default values for viewer_flags
|
// Migrate Rotation and Viewer values to default values for viewer_flags
|
||||||
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
|
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
|
||||||
1 -> OrientationType.FREE.flagValue
|
1 -> ReaderOrientation.FREE.flagValue
|
||||||
2 -> OrientationType.PORTRAIT.flagValue
|
2 -> ReaderOrientation.PORTRAIT.flagValue
|
||||||
3 -> OrientationType.LANDSCAPE.flagValue
|
3 -> ReaderOrientation.LANDSCAPE.flagValue
|
||||||
4 -> OrientationType.LOCKED_PORTRAIT.flagValue
|
4 -> ReaderOrientation.LOCKED_PORTRAIT.flagValue
|
||||||
5 -> OrientationType.LOCKED_LANDSCAPE.flagValue
|
5 -> ReaderOrientation.LOCKED_LANDSCAPE.flagValue
|
||||||
else -> OrientationType.FREE.flagValue
|
else -> ReaderOrientation.FREE.flagValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reading mode flag and prefValue is the same value
|
// Reading mode flag and prefValue is the same value
|
||||||
@ -242,7 +251,10 @@ object Migrations {
|
|||||||
if (oldSecureScreen) {
|
if (oldSecureScreen) {
|
||||||
securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS)
|
securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS)
|
||||||
}
|
}
|
||||||
if (DeviceUtil.isMiui && basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) {
|
if (
|
||||||
|
DeviceUtil.isMiui &&
|
||||||
|
basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER
|
||||||
|
) {
|
||||||
basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY)
|
basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +271,12 @@ object Migrations {
|
|||||||
if (oldVersion < 81) {
|
if (oldVersion < 81) {
|
||||||
// Handle renamed enum values
|
// Handle renamed enum values
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
val newSortingMode = when (val oldSortingMode = prefs.getString(libraryPreferences.sortingMode().key(), "ALPHABETICAL")) {
|
val newSortingMode = when (
|
||||||
|
val oldSortingMode = prefs.getString(
|
||||||
|
libraryPreferences.sortingMode().key(),
|
||||||
|
"ALPHABETICAL",
|
||||||
|
)
|
||||||
|
) {
|
||||||
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
|
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
|
||||||
"UNREAD" -> "UNREAD_COUNT"
|
"UNREAD" -> "UNREAD_COUNT"
|
||||||
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
|
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
|
||||||
@ -375,17 +392,28 @@ object Migrations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 107) {
|
if (oldVersion < 107) {
|
||||||
preferenceStore.getAll()
|
replacePreferences(
|
||||||
.filter { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }
|
preferenceStore = preferenceStore,
|
||||||
.forEach { (key, value) ->
|
filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") },
|
||||||
if (value is String) {
|
newKey = { Preference.privateKey(it) },
|
||||||
preferenceStore
|
)
|
||||||
.getString(Preference.privateKey(key))
|
|
||||||
.set(value)
|
|
||||||
|
|
||||||
preferenceStore.getString(key).delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 108) {
|
||||||
|
val prefsToReplace = listOf(
|
||||||
|
"pref_download_only",
|
||||||
|
"incognito_mode",
|
||||||
|
"last_catalogue_source",
|
||||||
|
"trusted_signatures",
|
||||||
|
"last_app_closed",
|
||||||
|
"library_update_last_timestamp",
|
||||||
|
"library_unseen_updates_count",
|
||||||
|
"last_used_category",
|
||||||
|
)
|
||||||
|
replacePreferences(
|
||||||
|
preferenceStore = preferenceStore,
|
||||||
|
filterPredicate = { it.key in prefsToReplace },
|
||||||
|
newKey = { Preference.appStateKey(it) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -393,3 +421,41 @@ object Migrations {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun replacePreferences(
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
filterPredicate: (Map.Entry<String, Any?>) -> Boolean,
|
||||||
|
newKey: (String) -> String,
|
||||||
|
) {
|
||||||
|
preferenceStore.getAll()
|
||||||
|
.filter(filterPredicate)
|
||||||
|
.forEach { (key, value) ->
|
||||||
|
when (value) {
|
||||||
|
is Int -> {
|
||||||
|
preferenceStore.getInt(newKey(key)).set(value)
|
||||||
|
preferenceStore.getInt(key).delete()
|
||||||
|
}
|
||||||
|
is Long -> {
|
||||||
|
preferenceStore.getLong(newKey(key)).set(value)
|
||||||
|
preferenceStore.getLong(key).delete()
|
||||||
|
}
|
||||||
|
is Float -> {
|
||||||
|
preferenceStore.getFloat(newKey(key)).set(value)
|
||||||
|
preferenceStore.getFloat(key).delete()
|
||||||
|
}
|
||||||
|
is String -> {
|
||||||
|
preferenceStore.getString(newKey(key)).set(value)
|
||||||
|
preferenceStore.getString(key).delete()
|
||||||
|
}
|
||||||
|
is Boolean -> {
|
||||||
|
preferenceStore.getBoolean(newKey(key)).set(value)
|
||||||
|
preferenceStore.getBoolean(key).delete()
|
||||||
|
}
|
||||||
|
is Set<*> -> (value as? Set<String>)?.let {
|
||||||
|
preferenceStore.getStringSet(newKey(key)).set(value)
|
||||||
|
preferenceStore.getStringSet(key).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.backup
|
|
||||||
|
|
||||||
// Filter options
|
|
||||||
internal object BackupConst {
|
|
||||||
const val BACKUP_CATEGORY = 0x1
|
|
||||||
const val BACKUP_CATEGORY_MASK = 0x1
|
|
||||||
|
|
||||||
const val BACKUP_CHAPTER = 0x2
|
|
||||||
const val BACKUP_CHAPTER_MASK = 0x2
|
|
||||||
|
|
||||||
const val BACKUP_HISTORY = 0x4
|
|
||||||
const val BACKUP_HISTORY_MASK = 0x4
|
|
||||||
|
|
||||||
const val BACKUP_TRACK = 0x8
|
|
||||||
const val BACKUP_TRACK_MASK = 0x8
|
|
||||||
|
|
||||||
const val BACKUP_APP_PREFS = 0x10
|
|
||||||
const val BACKUP_APP_PREFS_MASK = 0x10
|
|
||||||
|
|
||||||
const val BACKUP_SOURCE_PREFS = 0x20
|
|
||||||
const val BACKUP_SOURCE_PREFS_MASK = 0x20
|
|
||||||
|
|
||||||
const val BACKUP_ALL = 0x3F
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.backup
|
||||||
|
|
||||||
|
internal object BackupCreateFlags {
|
||||||
|
const val BACKUP_CATEGORY = 0x1
|
||||||
|
const val BACKUP_CHAPTER = 0x2
|
||||||
|
const val BACKUP_HISTORY = 0x4
|
||||||
|
const val BACKUP_TRACK = 0x8
|
||||||
|
const val BACKUP_APP_PREFS = 0x10
|
||||||
|
const val BACKUP_SOURCE_PREFS = 0x20
|
||||||
|
|
||||||
|
const val AutomaticDefaults = BACKUP_CATEGORY or
|
||||||
|
BACKUP_CHAPTER or
|
||||||
|
BACKUP_HISTORY or
|
||||||
|
BACKUP_TRACK or
|
||||||
|
BACKUP_APP_PREFS or
|
||||||
|
BACKUP_SOURCE_PREFS
|
||||||
|
}
|
@ -23,6 +23,7 @@ import tachiyomi.core.util.system.logcat
|
|||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
import kotlin.time.toJavaDuration
|
import kotlin.time.toJavaDuration
|
||||||
@ -40,7 +41,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
|||||||
val backupPreferences = Injekt.get<BackupPreferences>()
|
val backupPreferences = Injekt.get<BackupPreferences>()
|
||||||
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
||||||
?: backupPreferences.backupsDirectory().get().toUri()
|
?: backupPreferences.backupsDirectory().get().toUri()
|
||||||
val flags = inputData.getInt(BACKUP_FLAGS_KEY, BackupConst.BACKUP_ALL)
|
val flags = inputData.getInt(BACKUP_FLAGS_KEY, BackupCreateFlags.AutomaticDefaults)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setForeground(getForegroundInfo())
|
setForeground(getForegroundInfo())
|
||||||
@ -50,7 +51,11 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
|||||||
|
|
||||||
return try {
|
return try {
|
||||||
val location = BackupCreator(context).createBackup(uri, flags, isAutoBackup)
|
val location = BackupCreator(context).createBackup(uri, flags, isAutoBackup)
|
||||||
if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
|
if (isAutoBackup) {
|
||||||
|
backupPreferences.lastAutoBackupTimestamp().set(Date().time)
|
||||||
|
} else {
|
||||||
|
notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
|
||||||
|
}
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
|
@ -5,20 +5,15 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_APP_PREFS
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_APP_PREFS
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_APP_PREFS_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_CATEGORY
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_CHAPTER
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_HISTORY
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_SOURCE_PREFS
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER_MASK
|
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_TRACK
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||||
|
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
|
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||||
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
||||||
@ -160,7 +155,7 @@ class BackupCreator(
|
|||||||
*/
|
*/
|
||||||
suspend fun backupCategories(options: Int): List<BackupCategory> {
|
suspend fun backupCategories(options: Int): List<BackupCategory> {
|
||||||
// Check if user wants category information in backup
|
// Check if user wants category information in backup
|
||||||
return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
return if (options and BACKUP_CATEGORY == BACKUP_CATEGORY) {
|
||||||
getCategories.await()
|
getCategories.await()
|
||||||
.filterNot(Category::isSystemCategory)
|
.filterNot(Category::isSystemCategory)
|
||||||
.map(backupCategoryMapper)
|
.map(backupCategoryMapper)
|
||||||
@ -182,21 +177,26 @@ class BackupCreator(
|
|||||||
* @param options options for the backup
|
* @param options options for the backup
|
||||||
* @return [BackupManga] containing manga in a serializable form
|
* @return [BackupManga] containing manga in a serializable form
|
||||||
*/
|
*/
|
||||||
private suspend fun backupManga(manga: Manga, options: Int): BackupManga {
|
suspend fun backupManga(manga: Manga, options: Int): BackupManga {
|
||||||
// Entry for this manga
|
// Entry for this manga
|
||||||
val mangaObject = BackupManga.copyFrom(manga)
|
val mangaObject = BackupManga.copyFrom(manga)
|
||||||
|
|
||||||
// Check if user wants chapter information in backup
|
// Check if user wants chapter information in backup
|
||||||
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
if (options and BACKUP_CHAPTER == BACKUP_CHAPTER) {
|
||||||
// Backup all the chapters
|
// Backup all the chapters
|
||||||
val chapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id, backupChapterMapper) }
|
handler.awaitList {
|
||||||
if (chapters.isNotEmpty()) {
|
chaptersQueries.getChaptersByMangaId(
|
||||||
mangaObject.chapters = chapters
|
mangaId = manga.id,
|
||||||
|
applyScanlatorFilter = 0, // false
|
||||||
|
mapper = backupChapterMapper,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
.takeUnless(List<BackupChapter>::isEmpty)
|
||||||
|
?.let { mangaObject.chapters = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user wants category information in backup
|
// Check if user wants category information in backup
|
||||||
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
if (options and BACKUP_CATEGORY == BACKUP_CATEGORY) {
|
||||||
// Backup categories for this manga
|
// Backup categories for this manga
|
||||||
val categoriesForManga = getCategories.await(manga.id)
|
val categoriesForManga = getCategories.await(manga.id)
|
||||||
if (categoriesForManga.isNotEmpty()) {
|
if (categoriesForManga.isNotEmpty()) {
|
||||||
@ -205,7 +205,7 @@ class BackupCreator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if user wants track information in backup
|
// Check if user wants track information in backup
|
||||||
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
if (options and BACKUP_TRACK == BACKUP_TRACK) {
|
||||||
val tracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id, backupTrackMapper) }
|
val tracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id, backupTrackMapper) }
|
||||||
if (tracks.isNotEmpty()) {
|
if (tracks.isNotEmpty()) {
|
||||||
mangaObject.tracking = tracks
|
mangaObject.tracking = tracks
|
||||||
@ -213,7 +213,7 @@ class BackupCreator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if user wants history information in backup
|
// Check if user wants history information in backup
|
||||||
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
if (options and BACKUP_HISTORY == BACKUP_HISTORY) {
|
||||||
val historyByMangaId = getHistory.await(manga.id)
|
val historyByMangaId = getHistory.await(manga.id)
|
||||||
if (historyByMangaId.isNotEmpty()) {
|
if (historyByMangaId.isNotEmpty()) {
|
||||||
val history = historyByMangaId.map { history ->
|
val history = historyByMangaId.map { history ->
|
||||||
@ -230,13 +230,13 @@ class BackupCreator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun backupAppPreferences(flags: Int): List<BackupPreference> {
|
fun backupAppPreferences(flags: Int): List<BackupPreference> {
|
||||||
if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList()
|
if (flags and BACKUP_APP_PREFS != BACKUP_APP_PREFS) return emptyList()
|
||||||
|
|
||||||
return preferenceStore.getAll().toBackupPreferences()
|
return preferenceStore.getAll().toBackupPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backupSourcePreferences(flags: Int): List<BackupSourcePreferences> {
|
fun backupSourcePreferences(flags: Int): List<BackupSourcePreferences> {
|
||||||
if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList()
|
if (flags and BACKUP_SOURCE_PREFS != BACKUP_SOURCE_PREFS) return emptyList()
|
||||||
|
|
||||||
return sourceManager.getCatalogueSources()
|
return sourceManager.getCatalogueSources()
|
||||||
.filterIsInstance<ConfigurableSource>()
|
.filterIsInstance<ConfigurableSource>()
|
||||||
@ -250,7 +250,9 @@ class BackupCreator(
|
|||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
|
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
|
||||||
return this.filterKeys { !Preference.isPrivate(it) }
|
return this.filterKeys {
|
||||||
|
!Preference.isPrivate(it) && !Preference.isAppState(it)
|
||||||
|
}
|
||||||
.mapNotNull { (key, value) ->
|
.mapNotNull { (key, value) ->
|
||||||
when (value) {
|
when (value) {
|
||||||
is Int -> BackupPreference(key, IntPreferenceValue(value))
|
is Int -> BackupPreference(key, IntPreferenceValue(value))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user