mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
fix: conflict.
Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
commit
fd63383d74
5
.github/renovate.json5
vendored
5
.github/renovate.json5
vendored
@ -5,11 +5,6 @@
|
|||||||
],
|
],
|
||||||
"schedule": ["every sunday"],
|
"schedule": ["every sunday"],
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
|
||||||
"managers": ["maven"],
|
|
||||||
"packageNames": ["com.google.guava:guava"],
|
|
||||||
"versionScheme": "docker"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// Compiler plugins are tightly coupled to Kotlin version
|
// Compiler plugins are tightly coupled to Kotlin version
|
||||||
"groupName": "Kotlin",
|
"groupName": "Kotlin",
|
||||||
|
@ -24,6 +24,10 @@ Before you start, please note that the ability to use following technologies is
|
|||||||
- [Android Studio](https://developer.android.com/studio)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
- Emulator or phone with developer options enabled to test changes.
|
||||||
|
|
||||||
|
## Linting
|
||||||
|
|
||||||
|
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
- Join [the Discord server](https://discord.gg/tachiyomi) for online help and to ask questions while developing.
|
||||||
|
@ -38,7 +38,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||||||
|
|
||||||
* Include version (More → About → Version)
|
* Include version (More → About → Version)
|
||||||
* If not latest, try updating, it may have already been solved
|
* If not latest, try updating, it may have already been solved
|
||||||
* Preview version is equal to the number of commits as seen in the main page
|
* Preview version is equal to the number of commits as seen on the main page
|
||||||
* Include steps to reproduce (if not obvious from description)
|
* Include steps to reproduce (if not obvious from description)
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||||
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.iml
|
*.iml
|
||||||
custom.gradle
|
|
||||||
|
@ -192,7 +192,7 @@ dependencies {
|
|||||||
implementation(androidx.bundles.lifecycle)
|
implementation(androidx.bundles.lifecycle)
|
||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
implementation(androidx.bundles.workmanager)
|
implementation(androidx.workmanager)
|
||||||
|
|
||||||
// RxJava
|
// RxJava
|
||||||
implementation(libs.rxjava)
|
implementation(libs.rxjava)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<shortcut
|
<shortcut
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||||
|
@ -41,6 +41,7 @@ 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.GetChapterByMangaId
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
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
|
||||||
@ -56,6 +57,7 @@ import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
|||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
|
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
||||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
@ -99,6 +101,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetFavorites(get()) }
|
addFactory { GetFavorites(get()) }
|
||||||
addFactory { GetLibraryManga(get()) }
|
addFactory { GetLibraryManga(get()) }
|
||||||
addFactory { GetMangaWithChapters(get(), get()) }
|
addFactory { GetMangaWithChapters(get(), get()) }
|
||||||
|
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
@ -126,6 +129,7 @@ class DomainModule : InjektModule {
|
|||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
addFactory { GetChapter(get()) }
|
addFactory { GetChapter(get()) }
|
||||||
addFactory { GetChapterByMangaId(get()) }
|
addFactory { GetChapterByMangaId(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() }
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.domain.track.interactor
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
|
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
|
||||||
import eu.kanade.domain.track.store.DelayedTrackingStore
|
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
@ -31,14 +32,17 @@ class TrackChapter(
|
|||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
|
|
||||||
val updatedTrack = track.copy(lastChapterRead = chapterNumber)
|
|
||||||
async {
|
async {
|
||||||
runCatching {
|
runCatching {
|
||||||
try {
|
try {
|
||||||
|
val updatedTrack = service.refresh(track.toDbTrack())
|
||||||
|
.toDomainTrack(idRequired = true)!!
|
||||||
|
.copy(lastChapterRead = chapterNumber)
|
||||||
service.update(updatedTrack.toDbTrack(), true)
|
service.update(updatedTrack.toDbTrack(), true)
|
||||||
insertTrack.await(updatedTrack)
|
insertTrack.await(updatedTrack)
|
||||||
|
delayedTrackingStore.remove(track.id)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
delayedTrackingStore.addItem(updatedTrack)
|
delayedTrackingStore.add(track.id, chapterNumber)
|
||||||
DelayedTrackingUpdateJob.setupTask(context)
|
DelayedTrackingUpdateJob.setupTask(context)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
@ -8,21 +8,19 @@ import androidx.work.ExistingWorkPolicy
|
|||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
import eu.kanade.domain.track.store.DelayedTrackingStore
|
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
import kotlin.time.toJavaDuration
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) :
|
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
@ -31,9 +29,8 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
val getTracks = Injekt.get<GetTracks>()
|
val getTracks = Injekt.get<GetTracks>()
|
||||||
val insertTrack = Injekt.get<InsertTrack>()
|
val trackChapter = Injekt.get<TrackChapter>()
|
||||||
|
|
||||||
val trackerManager = Injekt.get<TrackerManager>()
|
|
||||||
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
|
||||||
|
|
||||||
withIOContext {
|
withIOContext {
|
||||||
@ -46,17 +43,8 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
|
|||||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
}
|
}
|
||||||
.forEach { track ->
|
.forEach { track ->
|
||||||
try {
|
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}" }
|
||||||
val service = trackerManager.get(track.syncId)
|
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
||||||
if (service != null && service.isLoggedIn) {
|
|
||||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
|
||||||
service.update(track.toDbTrack(), true)
|
|
||||||
insertTrack.await(track)
|
|
||||||
}
|
|
||||||
delayedTrackingStore.remove(track.id)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.model.Track
|
|
||||||
|
|
||||||
class DelayedTrackingStore(context: Context) {
|
class DelayedTrackingStore(context: Context) {
|
||||||
|
|
||||||
@ -13,13 +12,12 @@ class DelayedTrackingStore(context: Context) {
|
|||||||
*/
|
*/
|
||||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun addItem(track: Track) {
|
fun add(trackId: Long, lastChapterRead: Double) {
|
||||||
val trackId = track.id.toString()
|
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||||
val lastChapterRead = preferences.getFloat(trackId, 0f)
|
if (lastChapterRead > previousLastChapterRead) {
|
||||||
if (track.lastChapterRead > lastChapterRead) {
|
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: $lastChapterRead" }
|
||||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: ${track.lastChapterRead}" }
|
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
putFloat(trackId, track.lastChapterRead.toFloat())
|
putFloat(trackId.toString(), lastChapterRead.toFloat())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.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.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.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.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.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.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.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.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onClickReadme,
|
onClick = onClickReadme,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -244,7 +244,10 @@ private fun ExtensionItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val padding by animateDpAsState(targetValue = if (idle) 0.dp else 8.dp)
|
val padding by animateDpAsState(
|
||||||
|
targetValue = if (idle) 0.dp else 8.dp,
|
||||||
|
label = "iconPadding",
|
||||||
|
)
|
||||||
ExtensionIcon(
|
ExtensionIcon(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -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.filled.ViewList
|
import androidx.compose.material.icons.automirrored.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,7 @@ 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.Filled.ViewList else Icons.Filled.ViewModule,
|
icon = if (displayMode == LibraryDisplayMode.List) Icons.AutoMirrored.Filled.ViewList else Icons.Filled.ViewModule,
|
||||||
onClick = { selectingDisplayMode = true },
|
onClick = { selectingDisplayMode = true },
|
||||||
),
|
),
|
||||||
if (isLocalSource) {
|
if (isLocalSource) {
|
||||||
|
@ -11,6 +11,7 @@ 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
|
||||||
@ -54,7 +55,7 @@ fun GlobalSearchResultItem(
|
|||||||
Text(text = subtitle)
|
Text(text = subtitle)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
Icon(imageVector = Icons.AutoMirrored.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(),
|
||||||
|
@ -8,17 +8,11 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.ArrowBack
|
|
||||||
import androidx.compose.material.icons.outlined.SortByAlpha
|
import androidx.compose.material.icons.outlined.SortByAlpha
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||||
import eu.kanade.presentation.category.components.CategoryListItem
|
import eu.kanade.presentation.category.components.CategoryListItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@ -46,21 +40,9 @@ fun CategoryScreen(
|
|||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = {
|
title = stringResource(R.string.action_edit_categories),
|
||||||
Text(
|
navigateUp = navigateUp,
|
||||||
text = stringResource(R.string.action_edit_categories),
|
|
||||||
modifier = Modifier.padding(start = 8.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = navigateUp) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.ArrowBack,
|
|
||||||
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -6,6 +6,7 @@ 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
|
||||||
@ -49,7 +50,7 @@ fun CategoryListItem(
|
|||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.Outlined.Label, contentDescription = "")
|
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = "")
|
||||||
Text(
|
Text(
|
||||||
text = category.name,
|
text = category.name,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
@ -9,8 +10,7 @@ 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.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.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,14 +39,12 @@ 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
|
||||||
@ -60,6 +58,7 @@ const val SEARCH_DEBOUNCE_MILLIS = 250L
|
|||||||
@Composable
|
@Composable
|
||||||
fun AppBar(
|
fun AppBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundColor: Color? = null,
|
||||||
// Text
|
// Text
|
||||||
title: String?,
|
title: String?,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
@ -81,6 +80,7 @@ fun AppBar(
|
|||||||
|
|
||||||
AppBar(
|
AppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
titleContent = {
|
titleContent = {
|
||||||
if (isActionMode) {
|
if (isActionMode) {
|
||||||
AppBarTitle(actionModeCounter.toString())
|
AppBarTitle(actionModeCounter.toString())
|
||||||
@ -106,6 +106,7 @@ fun AppBar(
|
|||||||
@Composable
|
@Composable
|
||||||
fun AppBar(
|
fun AppBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundColor: Color? = null,
|
||||||
// Title
|
// Title
|
||||||
titleContent: @Composable () -> Unit,
|
titleContent: @Composable () -> Unit,
|
||||||
// Up button
|
// Up button
|
||||||
@ -142,7 +143,7 @@ fun AppBar(
|
|||||||
title = titleContent,
|
title = titleContent,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
containerColor = backgroundColor ?: MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
elevation = if (isActionMode) 3.dp else 0.dp,
|
elevation = if (isActionMode) 3.dp else 0.dp,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -170,6 +171,9 @@ fun AppBarTitle(
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.basicMarquee(
|
||||||
|
delayMillis = 2_000,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,7 +367,7 @@ fun SearchToolbar(
|
|||||||
@Composable
|
@Composable
|
||||||
fun UpIcon(navigationIcon: ImageVector? = null) {
|
fun UpIcon(navigationIcon: ImageVector? = null) {
|
||||||
val icon = navigationIcon
|
val icon = navigationIcon
|
||||||
?: if (LocalLayoutDirection.current == LayoutDirection.Ltr) Icons.Outlined.ArrowBack else Icons.Outlined.ArrowForward
|
?: Icons.AutoMirrored.Outlined.ArrowBack
|
||||||
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,8 +3,7 @@ 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.outlined.ArrowLeft
|
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
||||||
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
|
||||||
@ -16,10 +15,8 @@ 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
|
||||||
@ -77,14 +74,13 @@ 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 = if (isLtr) Icons.Outlined.ArrowRight else Icons.Outlined.ArrowLeft,
|
imageVector = Icons.AutoMirrored.Outlined.ArrowRight,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@ -38,7 +39,7 @@ private fun WithActionPreview() {
|
|||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.getting_started_guide,
|
stringResId = R.string.getting_started_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -14,8 +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.TabRow
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -55,7 +55,7 @@ fun TabbedDialog(
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
Row {
|
Row {
|
||||||
TabRow(
|
PrimaryTabRow(
|
||||||
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) },
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
TabRow(
|
PrimaryTabRow(
|
||||||
selectedTabIndex = state.currentPage,
|
selectedTabIndex = state.currentPage,
|
||||||
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
|
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
|
||||||
) {
|
) {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package eu.kanade.presentation.extensions
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Launches request for [Manifest.permission.WRITE_EXTERNAL_STORAGE] permission
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun DiskUtil.RequestStoragePermission() {
|
|
||||||
val permissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
permissionState.launchPermissionRequest()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.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
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
@ -18,13 +19,16 @@ import eu.kanade.presentation.components.AppBarTitle
|
|||||||
import eu.kanade.presentation.components.RelativeDateHeader
|
import eu.kanade.presentation.components.RelativeDateHeader
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
|
import tachiyomi.core.preference.InMemoryPreferenceStore
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
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
|
||||||
@ -37,6 +41,7 @@ fun HistoryScreen(
|
|||||||
onClickCover: (mangaId: Long) -> Unit,
|
onClickCover: (mangaId: Long) -> Unit,
|
||||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
||||||
|
preferences: UiPreferences = Injekt.get(),
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
@ -82,6 +87,7 @@ fun HistoryScreen(
|
|||||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
||||||
|
preferences = preferences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +101,7 @@ private fun HistoryScreenContent(
|
|||||||
onClickCover: (HistoryWithRelations) -> Unit,
|
onClickCover: (HistoryWithRelations) -> Unit,
|
||||||
onClickResume: (HistoryWithRelations) -> Unit,
|
onClickResume: (HistoryWithRelations) -> Unit,
|
||||||
onClickDelete: (HistoryWithRelations) -> Unit,
|
onClickDelete: (HistoryWithRelations) -> Unit,
|
||||||
preferences: UiPreferences = Injekt.get(),
|
preferences: UiPreferences,
|
||||||
) {
|
) {
|
||||||
val relativeTime = remember { preferences.relativeTime().get() }
|
val relativeTime = remember { preferences.relativeTime().get() }
|
||||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||||
@ -141,3 +147,32 @@ sealed interface HistoryUiModel {
|
|||||||
data class Header(val date: Date) : HistoryUiModel
|
data class Header(val date: Date) : HistoryUiModel
|
||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
internal fun HistoryScreenPreviews(
|
||||||
|
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
||||||
|
historyState: HistoryScreenModel.State,
|
||||||
|
) {
|
||||||
|
TachiyomiTheme {
|
||||||
|
HistoryScreen(
|
||||||
|
state = historyState,
|
||||||
|
snackbarHostState = SnackbarHostState(),
|
||||||
|
onSearchQueryChange = {},
|
||||||
|
onClickCover = {},
|
||||||
|
onClickResume = { _, _ -> run {} },
|
||||||
|
onDialogChange = {},
|
||||||
|
preferences = UiPreferences(
|
||||||
|
InMemoryPreferenceStore(
|
||||||
|
sequenceOf(
|
||||||
|
InMemoryPreferenceStore.InMemoryPreference(
|
||||||
|
key = "relative_time_v2",
|
||||||
|
data = false,
|
||||||
|
defaultValue = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
package eu.kanade.presentation.history
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenModel.State> {
|
||||||
|
|
||||||
|
private val multiPage = HistoryScreenModel.State(
|
||||||
|
searchQuery = null,
|
||||||
|
list =
|
||||||
|
listOf(HistoryUiModelExamples.headerToday)
|
||||||
|
.asSequence()
|
||||||
|
.plus(HistoryUiModelExamples.items().take(3))
|
||||||
|
.plus(HistoryUiModelExamples.header { it.minus(1, ChronoUnit.DAYS) })
|
||||||
|
.plus(HistoryUiModelExamples.items().take(1))
|
||||||
|
.plus(HistoryUiModelExamples.header { it.minus(2, ChronoUnit.DAYS) })
|
||||||
|
.plus(HistoryUiModelExamples.items().take(7))
|
||||||
|
.toList(),
|
||||||
|
dialog = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val shortRecent = HistoryScreenModel.State(
|
||||||
|
searchQuery = null,
|
||||||
|
list = listOf(
|
||||||
|
HistoryUiModelExamples.headerToday,
|
||||||
|
HistoryUiModelExamples.items().first(),
|
||||||
|
),
|
||||||
|
dialog = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val shortFuture = HistoryScreenModel.State(
|
||||||
|
searchQuery = null,
|
||||||
|
list = listOf(
|
||||||
|
HistoryUiModelExamples.headerTomorrow,
|
||||||
|
HistoryUiModelExamples.items().first(),
|
||||||
|
),
|
||||||
|
dialog = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val empty = HistoryScreenModel.State(
|
||||||
|
searchQuery = null,
|
||||||
|
list = listOf(),
|
||||||
|
dialog = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val loadingWithSearchQuery = HistoryScreenModel.State(
|
||||||
|
searchQuery = "Example Search Query",
|
||||||
|
)
|
||||||
|
|
||||||
|
private val loading = HistoryScreenModel.State(
|
||||||
|
searchQuery = null,
|
||||||
|
list = null,
|
||||||
|
dialog = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val values: Sequence<HistoryScreenModel.State> = sequenceOf(
|
||||||
|
multiPage,
|
||||||
|
shortRecent,
|
||||||
|
shortFuture,
|
||||||
|
empty,
|
||||||
|
loadingWithSearchQuery,
|
||||||
|
loading,
|
||||||
|
)
|
||||||
|
|
||||||
|
private object HistoryUiModelExamples {
|
||||||
|
val headerToday = header()
|
||||||
|
val headerTomorrow =
|
||||||
|
HistoryUiModel.Header(Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))
|
||||||
|
|
||||||
|
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
||||||
|
HistoryUiModel.Header(Date.from(instantBuilder(Instant.now())))
|
||||||
|
|
||||||
|
fun items() = sequence {
|
||||||
|
var count = 1
|
||||||
|
while (true) {
|
||||||
|
yield(randItem { it.copy(title = "Example Title $count") })
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randItem(historyBuilder: (HistoryWithRelations) -> HistoryWithRelations = { it }) =
|
||||||
|
HistoryUiModel.Item(
|
||||||
|
historyBuilder(
|
||||||
|
HistoryWithRelations(
|
||||||
|
id = Random.nextLong(),
|
||||||
|
chapterId = Random.nextLong(),
|
||||||
|
mangaId = Random.nextLong(),
|
||||||
|
title = "Test Title",
|
||||||
|
chapterNumber = Random.nextDouble(),
|
||||||
|
readAt = Date.from(Instant.now()),
|
||||||
|
readDuration = Random.nextLong(),
|
||||||
|
coverData = MangaCover(
|
||||||
|
mangaId = Random.nextLong(),
|
||||||
|
sourceId = Random.nextLong(),
|
||||||
|
isMangaFavorite = Random.nextBoolean(),
|
||||||
|
url = "https://example.com/cover.png",
|
||||||
|
lastModified = Random.nextLong(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.presentation.history
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
object HistoryUiModelProviders {
|
||||||
|
|
||||||
|
class HeadNow : PreviewParameterProvider<HistoryUiModel> {
|
||||||
|
override val values: Sequence<HistoryUiModel> =
|
||||||
|
sequenceOf(HistoryUiModel.Header(Date.from(Instant.now())))
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,8 @@
|
|||||||
package eu.kanade.presentation.history.components
|
package eu.kanade.presentation.history.components
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.selection.toggleable
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
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
|
||||||
@ -14,11 +10,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import 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.util.ThemePreviews
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryDeleteDialog(
|
fun HistoryDeleteDialog(
|
||||||
@ -32,28 +29,16 @@ fun HistoryDeleteDialog(
|
|||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(R.string.action_remove))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
|
Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
LabeledCheckbox(
|
||||||
.padding(top = 16.dp)
|
label = stringResource(R.string.dialog_with_checkbox_reset),
|
||||||
.toggleable(
|
checked = removeEverything,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
onCheckedChange = { removeEverything = it },
|
||||||
indication = null,
|
)
|
||||||
value = removeEverything,
|
|
||||||
onValueChange = { removeEverything = it },
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = removeEverything,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
|
||||||
text = stringResource(R.string.dialog_with_checkbox_reset),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@ -101,3 +86,14 @@ fun HistoryDeleteAllDialog(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
private fun HistoryDeleteDialogPreview() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
HistoryDeleteDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
onDelete = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,15 +19,18 @@ 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.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
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
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.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 HISTORY_ITEM_HEIGHT = 96.dp
|
private val HistoryItemHeight = 96.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryItem(
|
fun HistoryItem(
|
||||||
@ -40,7 +43,7 @@ fun HistoryItem(
|
|||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clickable(onClick = onClickResume)
|
.clickable(onClick = onClickResume)
|
||||||
.height(HISTORY_ITEM_HEIGHT)
|
.height(HistoryItemHeight)
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@ -87,3 +90,19 @@ fun HistoryItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
private fun HistoryItemPreviews(
|
||||||
|
@PreviewParameter(HistoryWithRelationsProvider::class)
|
||||||
|
historyWithRelations: HistoryWithRelations,
|
||||||
|
) {
|
||||||
|
TachiyomiTheme {
|
||||||
|
HistoryItem(
|
||||||
|
history = historyWithRelations,
|
||||||
|
onClickCover = {},
|
||||||
|
onClickResume = {},
|
||||||
|
onClickDelete = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.presentation.history.components
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
internal class HistoryWithRelationsProvider : PreviewParameterProvider<HistoryWithRelations> {
|
||||||
|
|
||||||
|
private val simple = HistoryWithRelations(
|
||||||
|
id = 1L,
|
||||||
|
chapterId = 2L,
|
||||||
|
mangaId = 3L,
|
||||||
|
title = "Test Title",
|
||||||
|
chapterNumber = 10.2,
|
||||||
|
readAt = Date(1697247357L),
|
||||||
|
readDuration = 123L,
|
||||||
|
coverData = tachiyomi.domain.manga.model.MangaCover(
|
||||||
|
mangaId = 3L,
|
||||||
|
sourceId = 4L,
|
||||||
|
isMangaFavorite = false,
|
||||||
|
url = "https://example.com/cover.png",
|
||||||
|
lastModified = 5L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val historyWithoutReadAt = HistoryWithRelations(
|
||||||
|
id = 1L,
|
||||||
|
chapterId = 2L,
|
||||||
|
mangaId = 3L,
|
||||||
|
title = "Test Title",
|
||||||
|
chapterNumber = 10.2,
|
||||||
|
readAt = null,
|
||||||
|
readDuration = 123L,
|
||||||
|
coverData = tachiyomi.domain.manga.model.MangaCover(
|
||||||
|
mangaId = 3L,
|
||||||
|
sourceId = 4L,
|
||||||
|
isMangaFavorite = false,
|
||||||
|
url = "https://example.com/cover.png",
|
||||||
|
lastModified = 5L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val historyWithNegativeChapterNumber = HistoryWithRelations(
|
||||||
|
id = 1L,
|
||||||
|
chapterId = 2L,
|
||||||
|
mangaId = 3L,
|
||||||
|
title = "Test Title",
|
||||||
|
chapterNumber = -2.0,
|
||||||
|
readAt = Date(1697247357L),
|
||||||
|
readDuration = 123L,
|
||||||
|
coverData = tachiyomi.domain.manga.model.MangaCover(
|
||||||
|
mangaId = 3L,
|
||||||
|
sourceId = 4L,
|
||||||
|
isMangaFavorite = false,
|
||||||
|
url = "https://example.com/cover.png",
|
||||||
|
lastModified = 5L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
override val values: Sequence<HistoryWithRelations>
|
||||||
|
get() = sequenceOf(simple, historyWithoutReadAt, historyWithNegativeChapterNumber)
|
||||||
|
}
|
@ -1,11 +1,7 @@
|
|||||||
package eu.kanade.presentation.library
|
package eu.kanade.presentation.library
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
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
|
||||||
@ -13,11 +9,10 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.core.preference.CheckboxState
|
import tachiyomi.core.preference.CheckboxState
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteLibraryMangaDialog(
|
fun DeleteLibraryMangaDialog(
|
||||||
@ -62,27 +57,18 @@ fun DeleteLibraryMangaDialog(
|
|||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
list.forEach { state ->
|
list.forEach { state ->
|
||||||
val onCheck = {
|
LabeledCheckbox(
|
||||||
val index = list.indexOf(state)
|
label = stringResource(state.value),
|
||||||
if (index != -1) {
|
checked = state.isChecked,
|
||||||
val mutableList = list.toMutableList()
|
onCheckedChange = {
|
||||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
val index = list.indexOf(state)
|
||||||
list = mutableList.toList()
|
if (index != -1) {
|
||||||
}
|
val mutableList = list.toMutableList()
|
||||||
}
|
mutableList[index] = state.next() as CheckboxState.State<Int>
|
||||||
|
list = mutableList.toList()
|
||||||
Row(
|
}
|
||||||
modifier = Modifier
|
},
|
||||||
.fillMaxWidth()
|
)
|
||||||
.clickable { onCheck() },
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = state.isChecked,
|
|
||||||
onCheckedChange = { onCheck() },
|
|
||||||
)
|
|
||||||
Text(text = stringResource(state.value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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.ScrollableTabRow
|
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||||
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 {
|
||||||
ScrollableTabRow(
|
PrimaryScrollableTabRow(
|
||||||
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,16 +1,12 @@
|
|||||||
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.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -19,7 +15,6 @@ 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
|
||||||
@ -30,6 +25,7 @@ import eu.kanade.presentation.components.TabbedDialogPaddings
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
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
|
||||||
@ -172,6 +168,7 @@ private fun SetAsDefaultDialog(
|
|||||||
onConfirmed: (optionalChecked: Boolean) -> Unit,
|
onConfirmed: (optionalChecked: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
var optionalChecked by rememberSaveable { mutableStateOf(false) }
|
var optionalChecked by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = stringResource(R.string.chapter_settings)) },
|
title = { Text(text = stringResource(R.string.chapter_settings)) },
|
||||||
@ -181,25 +178,16 @@ private fun SetAsDefaultDialog(
|
|||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.confirm_set_chapter_settings))
|
Text(text = stringResource(R.string.confirm_set_chapter_settings))
|
||||||
|
|
||||||
Row(
|
LabeledCheckbox(
|
||||||
modifier = Modifier
|
label = stringResource(R.string.also_set_chapter_settings_for_library),
|
||||||
.clickable { optionalChecked = !optionalChecked }
|
checked = optionalChecked,
|
||||||
.padding(vertical = 8.dp)
|
onCheckedChange = { optionalChecked = it },
|
||||||
.fillMaxWidth(),
|
)
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = optionalChecked,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
Text(text = stringResource(R.string.also_set_chapter_settings_for_library))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -140,6 +140,7 @@ private fun DownloadingIndicator(
|
|||||||
val animatedProgress by animateFloatAsState(
|
val animatedProgress by animateFloatAsState(
|
||||||
targetValue = downloadProgress / 100f,
|
targetValue = downloadProgress / 100f,
|
||||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||||
|
label = "progress",
|
||||||
)
|
)
|
||||||
arrowColor = if (animatedProgress < 0.5f) {
|
arrowColor = if (animatedProgress < 0.5f) {
|
||||||
strokeColor
|
strokeColor
|
||||||
@ -147,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,6 +23,7 @@ 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
|
||||||
@ -258,7 +259,7 @@ fun LibraryBottomActionMenu(
|
|||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_move_category),
|
title = stringResource(R.string.action_move_category),
|
||||||
icon = Icons.Outlined.Label,
|
icon = Icons.AutoMirrored.Outlined.Label,
|
||||||
toConfirm = confirm[0],
|
toConfirm = confirm[0],
|
||||||
onLongClick = { onLongClickItem(0) },
|
onLongClick = { onLongClickItem(0) },
|
||||||
onClick = onChangeCategoryClicked,
|
onClick = onChangeCategoryClicked,
|
||||||
|
@ -48,7 +48,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
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.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@ -63,9 +62,8 @@ import androidx.compose.ui.graphics.Brush
|
|||||||
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.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.SubcomposeLayout
|
import androidx.compose.ui.layout.Layout
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@ -589,67 +587,70 @@ private fun MangaSummary(
|
|||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var expandedHeight by remember { mutableIntStateOf(0) }
|
|
||||||
var shrunkHeight by remember { mutableIntStateOf(0) }
|
|
||||||
val heightDelta = remember(expandedHeight, shrunkHeight) { expandedHeight - shrunkHeight }
|
|
||||||
val animProgress by animateFloatAsState(if (expanded) 1f else 0f)
|
val animProgress by animateFloatAsState(if (expanded) 1f else 0f)
|
||||||
val scrimHeight = with(LocalDensity.current) { remember { 24.sp.roundToPx() } }
|
Layout(
|
||||||
|
modifier = modifier.clipToBounds(),
|
||||||
SubcomposeLayout(modifier = modifier.clipToBounds()) { constraints ->
|
contents = listOf(
|
||||||
val shrunkPlaceable = subcompose("description-s") {
|
{
|
||||||
Text(
|
|
||||||
text = "\n\n", // Shows at least 3 lines
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}.map { it.measure(constraints) }
|
|
||||||
shrunkHeight = shrunkPlaceable.maxByOrNull { it.height }?.height ?: 0
|
|
||||||
|
|
||||||
val expandedPlaceable = subcompose("description-l") {
|
|
||||||
Text(
|
|
||||||
text = expandedDescription,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}.map { it.measure(constraints) }
|
|
||||||
expandedHeight = expandedPlaceable.maxByOrNull { it.height }?.height?.coerceAtLeast(shrunkHeight) ?: 0
|
|
||||||
|
|
||||||
val actualPlaceable = subcompose("description") {
|
|
||||||
SelectionContainer {
|
|
||||||
Text(
|
Text(
|
||||||
text = if (expanded) expandedDescription else shrunkDescription,
|
text = "\n\n", // Shows at least 3 lines
|
||||||
maxLines = Int.MAX_VALUE,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
|
||||||
modifier = Modifier.secondaryItemAlpha(),
|
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}.map { it.measure(constraints) }
|
{
|
||||||
|
Text(
|
||||||
|
text = expandedDescription,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
text = if (expanded) expandedDescription else shrunkDescription,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.background(Brush.verticalGradient(colors = colors)),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
||||||
|
Icon(
|
||||||
|
painter = rememberAnimatedVectorPainter(image, !expanded),
|
||||||
|
contentDescription = stringResource(if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand),
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) { (shrunk, expanded, actual, scrim), constraints ->
|
||||||
|
val shrunkHeight = shrunk.single()
|
||||||
|
.measure(constraints)
|
||||||
|
.height
|
||||||
|
val expandedHeight = expanded.single()
|
||||||
|
.measure(constraints)
|
||||||
|
.height
|
||||||
|
val heightDelta = expandedHeight - shrunkHeight
|
||||||
|
val scrimHeight = 24.dp.roundToPx()
|
||||||
|
|
||||||
val scrimPlaceable = subcompose("scrim") {
|
val actualPlaceable = actual.single()
|
||||||
val colors = listOf(Color.Transparent, MaterialTheme.colorScheme.background)
|
.measure(constraints)
|
||||||
Box(
|
val scrimPlaceable = scrim.single()
|
||||||
modifier = Modifier.background(Brush.verticalGradient(colors = colors)),
|
.measure(Constraints.fixed(width = constraints.maxWidth, height = scrimHeight))
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down)
|
|
||||||
Icon(
|
|
||||||
painter = rememberAnimatedVectorPainter(image, !expanded),
|
|
||||||
contentDescription = stringResource(if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand),
|
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
|
||||||
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.map { it.measure(Constraints.fixed(width = constraints.maxWidth, height = scrimHeight)) }
|
|
||||||
|
|
||||||
val currentHeight = shrunkHeight + ((heightDelta + scrimHeight) * animProgress).roundToInt()
|
val currentHeight = shrunkHeight + ((heightDelta + scrimHeight) * animProgress).roundToInt()
|
||||||
layout(constraints.maxWidth, currentHeight) {
|
layout(constraints.maxWidth, currentHeight) {
|
||||||
actualPlaceable.forEach {
|
actualPlaceable.place(0, 0)
|
||||||
it.place(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrimY = currentHeight - scrimHeight
|
val scrimY = currentHeight - scrimHeight
|
||||||
scrimPlaceable.forEach {
|
scrimPlaceable.place(0, scrimY)
|
||||||
it.place(0, scrimY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ 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
|
||||||
@ -128,7 +130,7 @@ fun MoreScreen(
|
|||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.categories),
|
title = stringResource(R.string.categories),
|
||||||
icon = Icons.Outlined.Label,
|
icon = Icons.AutoMirrored.Outlined.Label,
|
||||||
onPreferenceClick = onClickCategories,
|
onPreferenceClick = onClickCategories,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -166,7 +168,7 @@ fun MoreScreen(
|
|||||||
item {
|
item {
|
||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_help),
|
title = stringResource(R.string.label_help),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ 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
|
||||||
@ -60,7 +61,7 @@ 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.Default.OpenInNew, contentDescription = null)
|
Icon(imageVector = Icons.AutoMirrored.Outlined.OpenInNew, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,9 @@ package eu.kanade.presentation.more.settings
|
|||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.UpIcon
|
import eu.kanade.presentation.components.AppBar
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -19,15 +16,9 @@ fun PreferenceScaffold(
|
|||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = { Text(text = stringResource(titleRes)) },
|
title = stringResource(titleRes),
|
||||||
navigationIcon = {
|
navigateUp = onBackPressed,
|
||||||
if (onBackPressed != null) {
|
|
||||||
IconButton(onClick = onBackPressed) {
|
|
||||||
UpIcon()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = actions,
|
actions = actions,
|
||||||
scrollBehavior = it,
|
scrollBehavior = it,
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_360
|
import eu.kanade.tachiyomi.network.PREF_DOH_360
|
||||||
@ -328,7 +327,6 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
private fun getLibraryGroup(): Preference.PreferenceGroup {
|
private fun getLibraryGroup(): Preference.PreferenceGroup {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
title = stringResource(R.string.label_library),
|
title = stringResource(R.string.label_library),
|
||||||
@ -337,12 +335,6 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
title = stringResource(R.string.pref_refresh_library_covers),
|
title = stringResource(R.string.pref_refresh_library_covers),
|
||||||
onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.COVERS) },
|
onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.COVERS) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_refresh_library_tracking),
|
|
||||||
subtitle = stringResource(R.string.pref_refresh_library_tracking_summary),
|
|
||||||
enabled = trackerManager.hasLoggedIn(),
|
|
||||||
onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.TRACKING) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_reset_viewer_flags),
|
title = stringResource(R.string.pref_reset_viewer_flags),
|
||||||
subtitle = stringResource(R.string.pref_reset_viewer_flags_summary),
|
subtitle = stringResource(R.string.pref_reset_viewer_flags_summary),
|
||||||
|
@ -9,20 +9,13 @@ import android.widget.Toast
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
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
|
||||||
@ -39,11 +32,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
import eu.kanade.tachiyomi.data.backup.BackupConst
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||||
@ -54,12 +46,12 @@ import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
|||||||
import eu.kanade.tachiyomi.data.sync.SyncManager
|
import eu.kanade.tachiyomi.data.sync.SyncManager
|
||||||
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService
|
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveService
|
||||||
import eu.kanade.tachiyomi.data.sync.service.GoogleDriveSyncService
|
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.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.domain.sync.SyncPreferences
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
@ -79,7 +71,7 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val backupPreferences = Injekt.get<BackupPreferences>()
|
val backupPreferences = Injekt.get<BackupPreferences>()
|
||||||
|
|
||||||
DiskUtil.RequestStoragePermission()
|
PermissionRequestHelper.requestStoragePermission()
|
||||||
val syncPreferences = remember { Injekt.get<SyncPreferences>() }
|
val syncPreferences = remember { Injekt.get<SyncPreferences>() }
|
||||||
val syncService by syncPreferences.syncService().collectAsState()
|
val syncService by syncPreferences.syncService().collectAsState()
|
||||||
|
|
||||||
@ -263,22 +255,23 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
ScrollbarLazyColumn(state = state) {
|
ScrollbarLazyColumn(state = state) {
|
||||||
item {
|
item {
|
||||||
CreateBackupDialogItem(
|
LabeledCheckbox(
|
||||||
isSelected = true,
|
label = stringResource(R.string.manga),
|
||||||
title = stringResource(R.string.manga),
|
checked = true,
|
||||||
|
onCheckedChange = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
choices.forEach { (k, v) ->
|
choices.forEach { (k, v) ->
|
||||||
item {
|
item {
|
||||||
val isSelected = flags.contains(k)
|
val isSelected = flags.contains(k)
|
||||||
CreateBackupDialogItem(
|
LabeledCheckbox(
|
||||||
isSelected = isSelected,
|
label = stringResource(v),
|
||||||
title = stringResource(v),
|
checked = isSelected,
|
||||||
modifier = Modifier.clickable {
|
onCheckedChange = {
|
||||||
if (isSelected) {
|
if (it) {
|
||||||
flags.remove(k)
|
|
||||||
} else {
|
|
||||||
flags.add(k)
|
flags.add(k)
|
||||||
|
} else {
|
||||||
|
flags.remove(k)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -307,29 +300,6 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CreateBackupDialogItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
isSelected: Boolean,
|
|
||||||
title: String,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
modifier = Modifier.heightIn(min = 48.dp),
|
|
||||||
checked = isSelected,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.bodyMedium.merge(),
|
|
||||||
modifier = Modifier.padding(start = 24.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -341,7 +311,7 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = stringResource(R.string.invalid_backup_file)) },
|
title = { Text(text = stringResource(R.string.invalid_backup_file)) },
|
||||||
text = { Text(text = "${err.uri}\n\n${err.message}") },
|
text = { Text(text = listOfNotNull(err.uri, err.message).joinToString("\n\n")) },
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -349,7 +319,7 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(android.R.string.copy))
|
Text(text = stringResource(R.string.action_copy_to_clipboard))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
@ -413,21 +383,24 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (it != null) {
|
if (it == null) {
|
||||||
val results = try {
|
error = InvalidRestore(message = context.getString(R.string.file_null_uri_error))
|
||||||
BackupFileValidator().validate(context, it)
|
return@rememberLauncherForActivityResult
|
||||||
} 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
return Preference.PreferenceItem.TextPreference(
|
||||||
@ -649,6 +622,6 @@ private data class MissingRestoreComponents(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private data class InvalidRestore(
|
private data class InvalidRestore(
|
||||||
val uri: Uri,
|
val uri: Uri? = null,
|
||||||
val message: String,
|
val message: String,
|
||||||
)
|
)
|
||||||
|
@ -23,7 +23,6 @@ import eu.kanade.presentation.more.settings.Preference
|
|||||||
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@ -197,12 +196,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
title = stringResource(R.string.pref_library_update_refresh_metadata),
|
title = stringResource(R.string.pref_library_update_refresh_metadata),
|
||||||
subtitle = stringResource(R.string.pref_library_update_refresh_metadata_summary),
|
subtitle = stringResource(R.string.pref_library_update_refresh_metadata_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = libraryPreferences.autoUpdateTrackers(),
|
|
||||||
enabled = Injekt.get<TrackerManager>().hasLoggedIn(),
|
|
||||||
title = stringResource(R.string.pref_library_update_refresh_trackers),
|
|
||||||
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.MultiSelectListPreference(
|
Preference.PreferenceItem.MultiSelectListPreference(
|
||||||
pref = libraryPreferences.autoUpdateMangaRestrictions(),
|
pref = libraryPreferences.autoUpdateMangaRestrictions(),
|
||||||
title = stringResource(R.string.pref_library_update_manga_restriction),
|
title = stringResource(R.string.pref_library_update_manga_restriction),
|
||||||
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.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
|
||||||
@ -20,11 +21,8 @@ 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.SettingsBackupRestore
|
||||||
import androidx.compose.material.icons.outlined.Sync
|
import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -44,7 +42,6 @@ import cafe.adriel.voyager.navigator.Navigator
|
|||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.UpIcon
|
|
||||||
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
@ -82,21 +79,13 @@ object SettingsMainScreen : Screen() {
|
|||||||
val backPress = LocalBackPress.currentOrThrow
|
val backPress = LocalBackPress.currentOrThrow
|
||||||
val containerColor = if (twoPane) getPalerSurface() else MaterialTheme.colorScheme.surface
|
val containerColor = if (twoPane) getPalerSurface() else MaterialTheme.colorScheme.surface
|
||||||
val topBarState = rememberTopAppBarState()
|
val topBarState = rememberTopAppBarState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topBarState),
|
topBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topBarState),
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = {
|
title = stringResource(R.string.label_settings),
|
||||||
Text(
|
navigateUp = backPress::invoke,
|
||||||
text = stringResource(R.string.label_settings),
|
|
||||||
modifier = Modifier.padding(start = 8.dp),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = backPress::invoke) {
|
|
||||||
UpIcon()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
listOf(
|
||||||
@ -198,7 +187,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.Outlined.ChromeReaderMode,
|
icon = Icons.AutoMirrored.Outlined.ChromeReaderMode,
|
||||||
screen = SettingsReaderScreen,
|
screen = SettingsReaderScreen,
|
||||||
),
|
),
|
||||||
Item(
|
Item(
|
||||||
|
@ -211,7 +211,10 @@ private fun SearchResult(
|
|||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
Crossfade(targetState = result) {
|
Crossfade(
|
||||||
|
targetState = result,
|
||||||
|
label = "results",
|
||||||
|
) {
|
||||||
when {
|
when {
|
||||||
it == null -> {}
|
it == null -> {}
|
||||||
it.isEmpty() -> {
|
it.isEmpty() -> {
|
||||||
|
@ -9,6 +9,7 @@ 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
|
||||||
@ -72,7 +73,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.Outlined.HelpOutline,
|
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
contentDescription = stringResource(R.string.tracking_guide),
|
contentDescription = stringResource(R.string.tracking_guide),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,11 @@ 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.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.Public
|
import androidx.compose.material.icons.filled.Public
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
@ -41,19 +35,9 @@ class OpenSourceLibraryLicenseScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = {
|
title = name,
|
||||||
Text(
|
navigateUp = navigator::pop,
|
||||||
text = name,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = navigator::pop) {
|
|
||||||
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
actions = {
|
||||||
if (!website.isNullOrEmpty()) {
|
if (!website.isNullOrEmpty()) {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
|
@ -31,8 +31,8 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
@ -210,7 +210,7 @@ private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenMod
|
|||||||
private val database: Database = Injekt.get()
|
private val database: Database = Injekt.get()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
getSourcesWithNonLibraryManga.subscribe()
|
getSourcesWithNonLibraryManga.subscribe()
|
||||||
.collectLatest { list ->
|
.collectLatest { list ->
|
||||||
mutableState.update { old ->
|
mutableState.update { old ->
|
||||||
|
@ -4,12 +4,8 @@ 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.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
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
|
||||||
@ -43,13 +39,9 @@ class BackupSchemaScreen : Screen() {
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = { Text(text = title) },
|
title = title,
|
||||||
navigationIcon = {
|
navigateUp = navigator::pop,
|
||||||
IconButton(onClick = navigator::pop) {
|
|
||||||
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.debug
|
package eu.kanade.presentation.more.settings.screen.debug
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.webkit.WebView
|
|
||||||
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
|
||||||
@ -16,6 +15,7 @@ import eu.kanade.presentation.more.settings.screen.about.AboutScreen
|
|||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import kotlinx.coroutines.guava.await
|
import kotlinx.coroutines.guava.await
|
||||||
|
|
||||||
class DebugInfoScreen : Screen() {
|
class DebugInfoScreen : Screen() {
|
||||||
@ -68,15 +68,7 @@ class DebugInfoScreen : Screen() {
|
|||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
private fun getWebViewVersion(): String {
|
private fun getWebViewVersion(): String {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
return WebViewUtil.getVersion(LocalContext.current)
|
||||||
val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?"
|
|
||||||
val pm = LocalContext.current.packageManager
|
|
||||||
val label = webView.applicationInfo.loadLabel(pm)
|
|
||||||
val version = webView.versionName
|
|
||||||
return "$label $version"
|
|
||||||
} else {
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -7,13 +7,9 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
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
|
||||||
@ -61,13 +57,9 @@ class WorkerInfoScreen : Screen() {
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = { Text(text = title) },
|
title = title,
|
||||||
navigationIcon = {
|
navigateUp = navigator::pop,
|
||||||
IconButton(onClick = navigator::pop) {
|
|
||||||
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -1,30 +1,20 @@
|
|||||||
package eu.kanade.presentation.more.settings.widget
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.selection.selectable
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
|
||||||
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
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.toMutableStateList
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MultiSelectListPreferenceWidget(
|
fun MultiSelectListPreferenceWidget(
|
||||||
@ -55,33 +45,17 @@ fun MultiSelectListPreferenceWidget(
|
|||||||
preference.entries.forEach { current ->
|
preference.entries.forEach { current ->
|
||||||
item {
|
item {
|
||||||
val isSelected = selected.contains(current.key)
|
val isSelected = selected.contains(current.key)
|
||||||
val onSelectionChanged = {
|
LabeledCheckbox(
|
||||||
when (!isSelected) {
|
label = current.value,
|
||||||
true -> selected.add(current.key)
|
checked = isSelected,
|
||||||
false -> selected.remove(current.key)
|
onCheckedChange = {
|
||||||
}
|
if (it) {
|
||||||
}
|
selected.add(current.key)
|
||||||
Row(
|
} else {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
selected.remove(current.key)
|
||||||
modifier = Modifier
|
}
|
||||||
.clip(MaterialTheme.shapes.small)
|
},
|
||||||
.selectable(
|
)
|
||||||
selected = isSelected,
|
|
||||||
onClick = { onSelectionChanged() },
|
|
||||||
)
|
|
||||||
.minimumInteractiveComponentSize()
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = isSelected,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = current.value,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(start = 24.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package eu.kanade.presentation.permissions
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches request for [Manifest.permission.WRITE_EXTERNAL_STORAGE] permission
|
||||||
|
*/
|
||||||
|
object PermissionRequestHelper {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun requestStoragePermission() {
|
||||||
|
val permissionState = rememberPermissionState(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
permissionState.launchPermissionRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.presentation.reader
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -10,11 +9,10 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
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.surfaceColorAtElevation
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -24,6 +22,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomReaderBar(
|
fun BottomReaderBar(
|
||||||
|
backgroundColor: Color,
|
||||||
readingMode: ReadingModeType,
|
readingMode: ReadingModeType,
|
||||||
onClickReadingMode: () -> Unit,
|
onClickReadingMode: () -> Unit,
|
||||||
orientationMode: OrientationType,
|
orientationMode: OrientationType,
|
||||||
@ -32,11 +31,6 @@ fun BottomReaderBar(
|
|||||||
onClickCropBorder: () -> Unit,
|
onClickCropBorder: () -> Unit,
|
||||||
onClickSettings: () -> Unit,
|
onClickSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
// Match with toolbar background color set in ReaderActivity
|
|
||||||
val backgroundColor = MaterialTheme.colorScheme
|
|
||||||
.surfaceColorAtElevation(3.dp)
|
|
||||||
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.presentation.reader
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
@ -0,0 +1,166 @@
|
|||||||
|
package eu.kanade.presentation.reader.appbars
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Bookmark
|
||||||
|
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
|
|
||||||
|
private val animationSpec = tween<IntOffset>(200)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReaderAppBars(
|
||||||
|
visible: Boolean,
|
||||||
|
fullscreen: Boolean,
|
||||||
|
|
||||||
|
mangaTitle: String?,
|
||||||
|
chapterTitle: String?,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onClickTopAppBar: () -> Unit,
|
||||||
|
bookmarked: Boolean,
|
||||||
|
onToggleBookmarked: () -> Unit,
|
||||||
|
onOpenInWebView: (() -> Unit)?,
|
||||||
|
onShare: (() -> Unit)?,
|
||||||
|
|
||||||
|
viewer: Viewer?,
|
||||||
|
onNextChapter: () -> Unit,
|
||||||
|
enabledNext: Boolean,
|
||||||
|
onPreviousChapter: () -> Unit,
|
||||||
|
enabledPrevious: Boolean,
|
||||||
|
currentPage: Int,
|
||||||
|
totalPages: Int,
|
||||||
|
onSliderValueChange: (Int) -> Unit,
|
||||||
|
|
||||||
|
readingMode: ReadingModeType,
|
||||||
|
onClickReadingMode: () -> Unit,
|
||||||
|
orientationMode: OrientationType,
|
||||||
|
onClickOrientationMode: () -> Unit,
|
||||||
|
cropEnabled: Boolean,
|
||||||
|
onClickCropBorder: () -> Unit,
|
||||||
|
onClickSettings: () -> Unit,
|
||||||
|
) {
|
||||||
|
val isRtl = viewer is R2LPagerViewer
|
||||||
|
val backgroundColor = MaterialTheme.colorScheme
|
||||||
|
.surfaceColorAtElevation(3.dp)
|
||||||
|
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
|
||||||
|
|
||||||
|
val appBarModifier = if (fullscreen) {
|
||||||
|
Modifier.windowInsetsPadding(WindowInsets.systemBars)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = slideInVertically(
|
||||||
|
initialOffsetY = { -it },
|
||||||
|
animationSpec = animationSpec,
|
||||||
|
),
|
||||||
|
exit = slideOutVertically(
|
||||||
|
targetOffsetY = { -it },
|
||||||
|
animationSpec = animationSpec,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
modifier = appBarModifier
|
||||||
|
.clickable(onClick = onClickTopAppBar),
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
title = mangaTitle,
|
||||||
|
subtitle = chapterTitle,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
listOfNotNull(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(if (bookmarked) R.string.action_remove_bookmark else R.string.action_bookmark),
|
||||||
|
icon = if (bookmarked) Icons.Outlined.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||||
|
onClick = onToggleBookmarked,
|
||||||
|
),
|
||||||
|
onOpenInWebView?.let {
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_open_in_web_view),
|
||||||
|
onClick = it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onShare?.let {
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_share),
|
||||||
|
onClick = it,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = slideInVertically(
|
||||||
|
initialOffsetY = { it },
|
||||||
|
animationSpec = animationSpec,
|
||||||
|
),
|
||||||
|
exit = slideOutVertically(
|
||||||
|
targetOffsetY = { it },
|
||||||
|
animationSpec = animationSpec,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
ChapterNavigator(
|
||||||
|
isRtl = isRtl,
|
||||||
|
onNextChapter = onNextChapter,
|
||||||
|
enabledNext = enabledNext,
|
||||||
|
onPreviousChapter = onPreviousChapter,
|
||||||
|
enabledPrevious = enabledPrevious,
|
||||||
|
currentPage = currentPage,
|
||||||
|
totalPages = totalPages,
|
||||||
|
onSliderValueChange = onSliderValueChange,
|
||||||
|
)
|
||||||
|
|
||||||
|
BottomReaderBar(
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
readingMode = readingMode,
|
||||||
|
onClickReadingMode = onClickReadingMode,
|
||||||
|
orientationMode = orientationMode,
|
||||||
|
onClickOrientationMode = onClickOrientationMode,
|
||||||
|
cropEnabled = cropEnabled,
|
||||||
|
onClickCropBorder = onClickCropBorder,
|
||||||
|
onClickSettings = onClickSettings,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,14 +44,17 @@ 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.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
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.presentation.track.components.TrackLogoIcon
|
import eu.kanade.presentation.track.components.TrackLogoIcon
|
||||||
import eu.kanade.tachiyomi.R
|
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
|
||||||
@ -168,6 +171,7 @@ private fun TrackInfoItem(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
VerticalDivider()
|
VerticalDivider()
|
||||||
@ -254,6 +258,7 @@ private fun TrackDetailsItem(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,3 +317,12 @@ private fun TrackInfoItemMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
private fun TrackInfoDialogHomePreviews(
|
||||||
|
@PreviewParameter(TrackInfoDialogHomePreviewProvider::class)
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
TachiyomiTheme { content() }
|
||||||
|
}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package eu.kanade.presentation.track
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import eu.kanade.tachiyomi.dev.preview.DummyTracker
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import java.text.DateFormat
|
||||||
|
|
||||||
|
internal class TrackInfoDialogHomePreviewProvider :
|
||||||
|
PreviewParameterProvider<@Composable () -> Unit> {
|
||||||
|
|
||||||
|
private val aTrack = Track(
|
||||||
|
id = 1L,
|
||||||
|
mangaId = 2L,
|
||||||
|
syncId = 3L,
|
||||||
|
remoteId = 4L,
|
||||||
|
libraryId = null,
|
||||||
|
title = "Manage Name On Tracker Site",
|
||||||
|
lastChapterRead = 2.0,
|
||||||
|
totalChapters = 12L,
|
||||||
|
status = 1L,
|
||||||
|
score = 2.0,
|
||||||
|
remoteUrl = "https://example.com",
|
||||||
|
startDate = 0L,
|
||||||
|
finishDate = 0L,
|
||||||
|
)
|
||||||
|
private val trackItemWithoutTrack = TrackItem(
|
||||||
|
track = null,
|
||||||
|
tracker = DummyTracker(
|
||||||
|
id = 1L,
|
||||||
|
name = "Example Tracker",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
private val trackItemWithTrack = TrackItem(
|
||||||
|
track = aTrack,
|
||||||
|
tracker = DummyTracker(
|
||||||
|
id = 2L,
|
||||||
|
name = "Example Tracker 2",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val trackersWithAndWithoutTrack = @Composable {
|
||||||
|
TrackInfoDialogHome(
|
||||||
|
trackItems = listOf(
|
||||||
|
trackItemWithoutTrack,
|
||||||
|
trackItemWithTrack,
|
||||||
|
),
|
||||||
|
dateFormat = DateFormat.getDateInstance(),
|
||||||
|
onStatusClick = {},
|
||||||
|
onChapterClick = {},
|
||||||
|
onScoreClick = {},
|
||||||
|
onStartDateEdit = {},
|
||||||
|
onEndDateEdit = {},
|
||||||
|
onNewSearch = {},
|
||||||
|
onOpenInBrowser = {},
|
||||||
|
onRemoved = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val noTrackers = @Composable {
|
||||||
|
TrackInfoDialogHome(
|
||||||
|
trackItems = listOf(),
|
||||||
|
dateFormat = DateFormat.getDateInstance(),
|
||||||
|
onStatusClick = {},
|
||||||
|
onChapterClick = {},
|
||||||
|
onScoreClick = {},
|
||||||
|
onStartDateEdit = {},
|
||||||
|
onEndDateEdit = {},
|
||||||
|
onNewSearch = {},
|
||||||
|
onOpenInBrowser = {},
|
||||||
|
onRemoved = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val values: Sequence<@Composable () -> Unit>
|
||||||
|
get() = sequenceOf(
|
||||||
|
trackersWithAndWithoutTrack,
|
||||||
|
noTrackers,
|
||||||
|
)
|
||||||
|
}
|
@ -30,12 +30,14 @@ 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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.WheelNumberPicker
|
import tachiyomi.presentation.core.components.WheelNumberPicker
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||||
import tachiyomi.presentation.core.components.material.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
|
||||||
|
|
||||||
@ -171,7 +173,7 @@ fun TrackDateSelector(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
TextButton(onClick = { onConfirm(pickerState.selectedDateMillis!!) }) {
|
TextButton(onClick = { onConfirm(pickerState.selectedDateMillis!!) }) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(R.string.action_ok))
|
||||||
@ -209,7 +211,7 @@ private fun BaseSelector(
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
Text(text = stringResource(R.string.action_cancel))
|
||||||
}
|
}
|
||||||
TextButton(onClick = onConfirm) {
|
TextButton(onClick = onConfirm) {
|
||||||
Text(text = stringResource(R.string.action_ok))
|
Text(text = stringResource(R.string.action_ok))
|
||||||
@ -218,3 +220,25 @@ private fun BaseSelector(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
private fun TrackStatusSelectorPreviews() {
|
||||||
|
TachiyomiTheme {
|
||||||
|
TrackStatusSelector(
|
||||||
|
selection = 1,
|
||||||
|
onSelectionChange = {},
|
||||||
|
selections = mapOf(
|
||||||
|
// Anilist values
|
||||||
|
1 to R.string.reading,
|
||||||
|
2 to R.string.plan_to_read,
|
||||||
|
3 to R.string.completed,
|
||||||
|
4 to R.string.on_hold,
|
||||||
|
5 to R.string.dropped,
|
||||||
|
6 to R.string.repeating,
|
||||||
|
),
|
||||||
|
onConfirm = {},
|
||||||
|
onDismissRequest = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@ 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
|
||||||
@ -56,8 +57,10 @@ 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.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
|
||||||
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
@ -65,6 +68,7 @@ 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
|
||||||
@ -94,7 +98,7 @@ fun TrackerSearch(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onDismissRequest) {
|
IconButton(onClick = onDismissRequest) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowBack,
|
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@ -315,3 +319,12 @@ private fun SearchResultItemDetails(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
private fun TrackerSearchPreviews(
|
||||||
|
@PreviewParameter(TrackerSearchPreviewProvider::class)
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
TachiyomiTheme { content() }
|
||||||
|
}
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.presentation.track
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composable () -> Unit> {
|
||||||
|
private val fullPageWithSecondSelected = @Composable {
|
||||||
|
val items = someTrackSearches().take(30).toList()
|
||||||
|
TrackerSearch(
|
||||||
|
query = TextFieldValue(text = "search text"),
|
||||||
|
onQueryChange = {},
|
||||||
|
onDispatchQuery = {},
|
||||||
|
queryResult = Result.success(items),
|
||||||
|
selected = items[1],
|
||||||
|
onSelectedChange = {},
|
||||||
|
onConfirmSelection = {},
|
||||||
|
onDismissRequest = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val fullPageWithoutSelected = @Composable {
|
||||||
|
TrackerSearch(
|
||||||
|
query = TextFieldValue(text = ""),
|
||||||
|
onQueryChange = {},
|
||||||
|
onDispatchQuery = {},
|
||||||
|
queryResult = Result.success(someTrackSearches().take(30).toList()),
|
||||||
|
selected = null,
|
||||||
|
onSelectedChange = {},
|
||||||
|
onConfirmSelection = {},
|
||||||
|
onDismissRequest = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val loading = @Composable {
|
||||||
|
TrackerSearch(
|
||||||
|
query = TextFieldValue(),
|
||||||
|
onQueryChange = {},
|
||||||
|
onDispatchQuery = {},
|
||||||
|
queryResult = null,
|
||||||
|
selected = null,
|
||||||
|
onSelectedChange = {},
|
||||||
|
onConfirmSelection = {},
|
||||||
|
onDismissRequest = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val values: Sequence<@Composable () -> Unit> = sequenceOf(
|
||||||
|
fullPageWithSecondSelected,
|
||||||
|
fullPageWithoutSelected,
|
||||||
|
loading,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun someTrackSearches(): Sequence<TrackSearch> = sequence {
|
||||||
|
while (true) {
|
||||||
|
yield(randTrackSearch())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun randTrackSearch() = TrackSearch().let {
|
||||||
|
it.id = Random.nextLong()
|
||||||
|
it.manga_id = Random.nextLong()
|
||||||
|
it.sync_id = Random.nextInt()
|
||||||
|
it.media_id = Random.nextLong()
|
||||||
|
it.library_id = Random.nextLong()
|
||||||
|
it.title = lorem((1..10).random()).joinToString()
|
||||||
|
it.last_chapter_read = (0..100).random().toFloat()
|
||||||
|
it.total_chapters = (100..1000).random()
|
||||||
|
it.score = (0..10).random().toFloat()
|
||||||
|
it.status = Random.nextInt()
|
||||||
|
it.started_reading_date = 0L
|
||||||
|
it.finished_reading_date = 0L
|
||||||
|
it.tracking_url = "https://example.com/tracker-example"
|
||||||
|
it.cover_url = "https://example.com/cover.png"
|
||||||
|
it.start_date = Instant.now().minus((1L..365).random(), ChronoUnit.DAYS).toString()
|
||||||
|
it.summary = lorem((0..40).random()).joinToString()
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lorem(words: Int): Sequence<String> =
|
||||||
|
LoremIpsum(words).values
|
||||||
|
}
|
@ -11,8 +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.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
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
|
||||||
@ -39,3 +42,17 @@ fun TrackLogoIcon(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThemePreviews
|
||||||
|
@Composable
|
||||||
|
private fun TrackLogoIconPreviews(
|
||||||
|
@PreviewParameter(TrackLogoIconPreviewProvider::class)
|
||||||
|
tracker: Tracker,
|
||||||
|
) {
|
||||||
|
TachiyomiTheme {
|
||||||
|
TrackLogoIcon(
|
||||||
|
tracker = tracker,
|
||||||
|
onClick = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package eu.kanade.presentation.track.components
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.dev.preview.DummyTracker
|
||||||
|
|
||||||
|
internal class TrackLogoIconPreviewProvider : PreviewParameterProvider<Tracker> {
|
||||||
|
|
||||||
|
override val values: Sequence<Tracker>
|
||||||
|
get() = sequenceOf(
|
||||||
|
DummyTracker(
|
||||||
|
id = 1L,
|
||||||
|
name = "Dummy Tracker",
|
||||||
|
valLogoColor = Color.rgb(18, 25, 35),
|
||||||
|
valLogo = R.drawable.ic_tracker_anilist,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
@ -7,13 +7,18 @@ import android.webkit.WebView
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.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
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
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
|
||||||
@ -22,8 +27,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.web.AccompanistWebViewClient
|
import com.google.accompanist.web.AccompanistWebViewClient
|
||||||
import com.google.accompanist.web.LoadingState
|
import com.google.accompanist.web.LoadingState
|
||||||
import com.google.accompanist.web.WebView
|
import com.google.accompanist.web.WebView
|
||||||
@ -72,7 +79,7 @@ fun WebViewScreenContent(
|
|||||||
super.onPageFinished(view, url)
|
super.onPageFinished(view, url)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val html = view.getHtml()
|
val html = view.getHtml()
|
||||||
showCloudflareHelp = "window._cf_chl_opt" in html
|
showCloudflareHelp = "window._cf_chl_opt" in html || "Ray ID is" in html
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,54 +110,71 @@ fun WebViewScreenContent(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Box {
|
Box {
|
||||||
AppBar(
|
Column {
|
||||||
title = state.pageTitle ?: initialTitle,
|
AppBar(
|
||||||
subtitle = currentUrl,
|
title = state.pageTitle ?: initialTitle,
|
||||||
navigateUp = onNavigateUp,
|
subtitle = currentUrl,
|
||||||
navigationIcon = Icons.Outlined.Close,
|
navigateUp = onNavigateUp,
|
||||||
actions = {
|
navigationIcon = Icons.Outlined.Close,
|
||||||
AppBarActions(
|
actions = {
|
||||||
listOf(
|
AppBarActions(
|
||||||
AppBar.Action(
|
listOf(
|
||||||
title = stringResource(R.string.action_webview_back),
|
AppBar.Action(
|
||||||
icon = Icons.Outlined.ArrowBack,
|
title = stringResource(R.string.action_webview_back),
|
||||||
onClick = {
|
icon = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||||
if (navigator.canGoBack) {
|
onClick = {
|
||||||
navigator.navigateBack()
|
if (navigator.canGoBack) {
|
||||||
}
|
navigator.navigateBack()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = navigator.canGoBack,
|
||||||
|
),
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_webview_forward),
|
||||||
|
icon = Icons.AutoMirrored.Outlined.ArrowForward,
|
||||||
|
onClick = {
|
||||||
|
if (navigator.canGoForward) {
|
||||||
|
navigator.navigateForward()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = navigator.canGoForward,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_webview_refresh),
|
||||||
|
onClick = { navigator.reload() },
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_share),
|
||||||
|
onClick = { onShare(currentUrl) },
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_open_in_browser),
|
||||||
|
onClick = { onOpenInBrowser(currentUrl) },
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.pref_clear_cookies),
|
||||||
|
onClick = { onClearCookies(currentUrl) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showCloudflareHelp) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
) {
|
||||||
|
WarningBanner(
|
||||||
|
textRes = R.string.information_cloudflare_help,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.clickable {
|
||||||
|
uriHandler.openUri("https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare")
|
||||||
},
|
},
|
||||||
enabled = navigator.canGoBack,
|
)
|
||||||
),
|
}
|
||||||
AppBar.Action(
|
}
|
||||||
title = stringResource(R.string.action_webview_forward),
|
}
|
||||||
icon = Icons.Outlined.ArrowForward,
|
|
||||||
onClick = {
|
|
||||||
if (navigator.canGoForward) {
|
|
||||||
navigator.navigateForward()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = navigator.canGoForward,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_webview_refresh),
|
|
||||||
onClick = { navigator.reload() },
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_share),
|
|
||||||
onClick = { onShare(currentUrl) },
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_open_in_browser),
|
|
||||||
onClick = { onOpenInBrowser(currentUrl) },
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
|
||||||
onClick = { onClearCookies(currentUrl) },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
when (val loadingState = state.loadingState) {
|
when (val loadingState = state.loadingState) {
|
||||||
is LoadingState.Initializing -> LinearProgressIndicator(
|
is LoadingState.Initializing -> LinearProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -158,7 +182,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),
|
||||||
@ -168,38 +192,27 @@ fun WebViewScreenContent(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
Column(
|
WebView(
|
||||||
modifier = Modifier.padding(contentPadding),
|
state = state,
|
||||||
) {
|
modifier = Modifier
|
||||||
if (showCloudflareHelp) {
|
.fillMaxSize()
|
||||||
WarningBanner(
|
.padding(contentPadding),
|
||||||
textRes = R.string.information_cloudflare_help,
|
navigator = navigator,
|
||||||
modifier = Modifier.clickable {
|
onCreated = { webView ->
|
||||||
uriHandler.openUri("https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare")
|
webView.setDefaultSettings()
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
WebView(
|
// Debug mode (chrome://inspect/#devices)
|
||||||
state = state,
|
if (BuildConfig.DEBUG &&
|
||||||
modifier = Modifier.weight(1f),
|
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
||||||
navigator = navigator,
|
) {
|
||||||
onCreated = { webView ->
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
webView.setDefaultSettings()
|
}
|
||||||
|
|
||||||
// Debug mode (chrome://inspect/#devices)
|
headers["user-agent"]?.let {
|
||||||
if (BuildConfig.DEBUG &&
|
webView.settings.userAgentString = it
|
||||||
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
|
}
|
||||||
) {
|
},
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
client = webClient,
|
||||||
}
|
)
|
||||||
|
|
||||||
headers["user-agent"]?.let {
|
|
||||||
webView.settings.userAgentString = it
|
|
||||||
}
|
|
||||||
},
|
|
||||||
client = webClient,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ class BackupCreator(
|
|||||||
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_MASK != BACKUP_SOURCE_PREFS) return emptyList()
|
||||||
|
|
||||||
return sourceManager.getOnlineSources()
|
return sourceManager.getCatalogueSources()
|
||||||
.filterIsInstance<ConfigurableSource>()
|
.filterIsInstance<ConfigurableSource>()
|
||||||
.map {
|
.map {
|
||||||
BackupSourcePreferences(
|
BackupSourcePreferences(
|
||||||
|
@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
|
|||||||
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
|
import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
|
||||||
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
|
import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
|
||||||
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
|
import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
|
||||||
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.source.model.copyFrom
|
import eu.kanade.tachiyomi.source.model.copyFrom
|
||||||
import eu.kanade.tachiyomi.source.sourcePreferences
|
import eu.kanade.tachiyomi.source.sourcePreferences
|
||||||
import eu.kanade.tachiyomi.util.BackupUtil
|
import eu.kanade.tachiyomi.util.BackupUtil
|
||||||
@ -589,6 +590,9 @@ class BackupRestorer(
|
|||||||
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
|
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
|
||||||
restorePreferences(preferences, preferenceStore)
|
restorePreferences(preferences, preferenceStore)
|
||||||
|
|
||||||
|
LibraryUpdateJob.setupTask(context)
|
||||||
|
BackupCreateJob.setupTask(context)
|
||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.app_settings), context.getString(R.string.restoring_backup))
|
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.app_settings), context.getString(R.string.restoring_backup))
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
|||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.manga.model.copyFrom
|
import eu.kanade.domain.manga.model.copyFrom
|
||||||
import eu.kanade.domain.manga.model.toSManga
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
@ -89,7 +88,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
private val updateManga: UpdateManga = Injekt.get()
|
private val updateManga: UpdateManga = Injekt.get()
|
||||||
private val getCategories: GetCategories = Injekt.get()
|
private val getCategories: GetCategories = Injekt.get()
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
||||||
private val refreshTracks: RefreshTracks = Injekt.get()
|
|
||||||
private val fetchInterval: FetchInterval = Injekt.get()
|
private val fetchInterval: FetchInterval = Injekt.get()
|
||||||
|
|
||||||
private val notifier = LibraryUpdateNotifier(context)
|
private val notifier = LibraryUpdateNotifier(context)
|
||||||
@ -131,7 +129,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
when (target) {
|
when (target) {
|
||||||
Target.CHAPTERS -> updateChapterList()
|
Target.CHAPTERS -> updateChapterList()
|
||||||
Target.COVERS -> updateCovers()
|
Target.COVERS -> updateCovers()
|
||||||
Target.TRACKING -> updateTrackings()
|
|
||||||
}
|
}
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -304,10 +301,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
}
|
}
|
||||||
failedUpdates.add(manga to errorMessage)
|
failedUpdates.add(manga to errorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (libraryPreferences.autoUpdateTrackers().get()) {
|
|
||||||
refreshTracks(manga.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,33 +402,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
notifier.cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method that updates the metadata of the connected tracking services. It's called in a
|
|
||||||
* background thread, so it's safe to do heavy operations or network calls here.
|
|
||||||
*/
|
|
||||||
private suspend fun updateTrackings() {
|
|
||||||
coroutineScope {
|
|
||||||
var progressCount = 0
|
|
||||||
|
|
||||||
mangaToUpdate.forEach { libraryManga ->
|
|
||||||
ensureActive()
|
|
||||||
|
|
||||||
val manga = libraryManga.manga
|
|
||||||
notifier.showProgressNotification(listOf(manga), progressCount++, mangaToUpdate.size)
|
|
||||||
refreshTracks(manga.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
notifier.cancelProgressNotification()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun refreshTracks(mangaId: Long) {
|
|
||||||
refreshTracks.await(mangaId).forEach { (_, e) ->
|
|
||||||
// Ignore errors and continue
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun withUpdateNotification(
|
private suspend fun withUpdateNotification(
|
||||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||||
completed: AtomicInteger,
|
completed: AtomicInteger,
|
||||||
@ -500,7 +466,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
enum class Target {
|
enum class Target {
|
||||||
CHAPTERS, // Manga chapters
|
CHAPTERS, // Manga chapters
|
||||||
COVERS, // Manga covers
|
COVERS, // Manga covers
|
||||||
TRACKING, // Tracking metadata
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
169
app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt
Normal file
169
app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||||
|
import eu.kanade.domain.track.model.toDbTrack
|
||||||
|
import eu.kanade.domain.track.model.toDomainTrack
|
||||||
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import logcat.LogPriority
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import tachiyomi.core.util.lang.withUIContext
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
||||||
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
|
abstract class BaseTracker(
|
||||||
|
override val id: Long,
|
||||||
|
override val name: String,
|
||||||
|
) : Tracker {
|
||||||
|
|
||||||
|
val trackPreferences: TrackPreferences by injectLazy()
|
||||||
|
val networkService: NetworkHelper by injectLazy()
|
||||||
|
private val insertTrack: InsertTrack by injectLazy()
|
||||||
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack by injectLazy()
|
||||||
|
|
||||||
|
override val client: OkHttpClient
|
||||||
|
get() = networkService.client
|
||||||
|
|
||||||
|
// Application and remote support for reading dates
|
||||||
|
override val supportsReadingDates: Boolean = false
|
||||||
|
|
||||||
|
// TODO: Store all scores as 10 point in the future maybe?
|
||||||
|
override fun get10PointScore(track: DomainTrack): Double {
|
||||||
|
return track.score
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun indexToScore(index: Int): Float {
|
||||||
|
return index.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun logout() {
|
||||||
|
trackPreferences.setCredentials(this, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isLoggedIn: Boolean
|
||||||
|
get() = getUsername().isNotEmpty() &&
|
||||||
|
getPassword().isNotEmpty()
|
||||||
|
|
||||||
|
override fun getUsername() = trackPreferences.trackUsername(this).get()
|
||||||
|
|
||||||
|
override fun getPassword() = trackPreferences.trackPassword(this).get()
|
||||||
|
|
||||||
|
override fun saveCredentials(username: String, password: String) {
|
||||||
|
trackPreferences.setCredentials(this, username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move this to an interactor, and update all trackers based on common data
|
||||||
|
override suspend fun register(item: Track, mangaId: Long) {
|
||||||
|
item.manga_id = mangaId
|
||||||
|
try {
|
||||||
|
withIOContext {
|
||||||
|
val allChapters = Injekt.get<GetChapterByMangaId>().await(mangaId)
|
||||||
|
val hasReadChapters = allChapters.any { it.read }
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track.startDate <= 0) {
|
||||||
|
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
|
||||||
|
.sortedBy { it.readAt }
|
||||||
|
.firstOrNull()
|
||||||
|
?.readAt
|
||||||
|
|
||||||
|
firstReadChapterDate?.let {
|
||||||
|
val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
||||||
|
track = track.copy(
|
||||||
|
startDate = startDate,
|
||||||
|
)
|
||||||
|
setRemoteStartDate(track.toDbTrack(), startDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncChapterProgressWithTrack.await(mangaId, track, this@BaseTracker)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
withUIContext { Injekt.get<Application>().toast(e.message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setRemoteStatus(track: Track, status: Int) {
|
||||||
|
track.status = status
|
||||||
|
if (track.status == getCompletionStatus() && track.total_chapters != 0) {
|
||||||
|
track.last_chapter_read = track.total_chapters.toFloat()
|
||||||
|
}
|
||||||
|
withIOContext { updateRemote(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
||||||
|
if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
|
||||||
|
track.status = getReadingStatus()
|
||||||
|
}
|
||||||
|
track.last_chapter_read = chapterNumber.toFloat()
|
||||||
|
if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) {
|
||||||
|
track.status = getCompletionStatus()
|
||||||
|
track.finished_reading_date = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
withIOContext { updateRemote(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setRemoteScore(track: Track, scoreString: String) {
|
||||||
|
track.score = indexToScore(getScoreList().indexOf(scoreString))
|
||||||
|
withIOContext { updateRemote(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setRemoteStartDate(track: Track, epochMillis: Long) {
|
||||||
|
track.started_reading_date = epochMillis
|
||||||
|
withIOContext { updateRemote(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setRemoteFinishDate(track: Track, epochMillis: Long) {
|
||||||
|
track.finished_reading_date = epochMillis
|
||||||
|
withIOContext { updateRemote(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateRemote(track: Track) {
|
||||||
|
withIOContext {
|
||||||
|
try {
|
||||||
|
update(track)
|
||||||
|
track.toDomainTrack(idRequired = false)?.let {
|
||||||
|
insertTrack.await(it)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=$id" }
|
||||||
|
withUIContext { Injekt.get<Application>().toast(e.message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,201 +1,81 @@
|
|||||||
package eu.kanade.tachiyomi.data.track
|
package eu.kanade.tachiyomi.data.track
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
|
||||||
import eu.kanade.domain.track.model.toDbTrack
|
|
||||||
import eu.kanade.domain.track.model.toDomainTrack
|
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import logcat.LogPriority
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
|
||||||
import tachiyomi.core.util.lang.withUIContext
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
|
|
||||||
import tachiyomi.domain.history.interactor.GetHistory
|
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
|
||||||
|
|
||||||
abstract class Tracker(val id: Long, val name: String) {
|
interface Tracker {
|
||||||
|
|
||||||
val trackPreferences: TrackPreferences by injectLazy()
|
val id: Long
|
||||||
val networkService: NetworkHelper by injectLazy()
|
|
||||||
private val insertTrack: InsertTrack by injectLazy()
|
|
||||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack by injectLazy()
|
|
||||||
|
|
||||||
open val client: OkHttpClient
|
val name: String
|
||||||
get() = networkService.client
|
|
||||||
|
val client: OkHttpClient
|
||||||
|
|
||||||
// Application and remote support for reading dates
|
// Application and remote support for reading dates
|
||||||
open val supportsReadingDates: Boolean = false
|
val supportsReadingDates: Boolean
|
||||||
|
|
||||||
@DrawableRes
|
|
||||||
abstract fun getLogo(): Int
|
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
abstract fun getLogoColor(): Int
|
fun getLogoColor(): Int
|
||||||
|
|
||||||
abstract fun getStatusList(): List<Int>
|
@DrawableRes
|
||||||
|
fun getLogo(): Int
|
||||||
|
|
||||||
|
fun getStatusList(): List<Int>
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
abstract fun getStatus(status: Int): Int?
|
fun getStatus(status: Int): Int?
|
||||||
|
|
||||||
abstract fun getReadingStatus(): Int
|
fun getReadingStatus(): Int
|
||||||
|
|
||||||
abstract fun getRereadingStatus(): Int
|
fun getRereadingStatus(): Int
|
||||||
|
|
||||||
abstract fun getCompletionStatus(): Int
|
fun getCompletionStatus(): Int
|
||||||
|
|
||||||
abstract fun getScoreList(): List<String>
|
fun getScoreList(): List<String>
|
||||||
|
|
||||||
// TODO: Store all scores as 10 point in the future maybe?
|
// TODO: Store all scores as 10 point in the future maybe?
|
||||||
open fun get10PointScore(track: DomainTrack): Double {
|
fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double
|
||||||
return track.score
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun indexToScore(index: Int): Float {
|
fun indexToScore(index: Int): Float
|
||||||
return index.toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun displayScore(track: Track): String
|
fun displayScore(track: Track): String
|
||||||
|
|
||||||
abstract suspend fun update(track: Track, didReadChapter: Boolean = false): Track
|
suspend fun update(track: Track, didReadChapter: Boolean = false): Track
|
||||||
|
|
||||||
abstract suspend fun bind(track: Track, hasReadChapters: Boolean = false): Track
|
suspend fun bind(track: Track, hasReadChapters: Boolean = false): Track
|
||||||
|
|
||||||
abstract suspend fun search(query: String): List<TrackSearch>
|
suspend fun search(query: String): List<TrackSearch>
|
||||||
|
|
||||||
abstract suspend fun refresh(track: Track): Track
|
suspend fun refresh(track: Track): Track
|
||||||
|
|
||||||
abstract suspend fun login(username: String, password: String)
|
suspend fun login(username: String, password: String)
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
open fun logout() {
|
fun logout()
|
||||||
trackPreferences.setCredentials(this, "", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
open val isLoggedIn: Boolean
|
val isLoggedIn: Boolean
|
||||||
get() = getUsername().isNotEmpty() &&
|
|
||||||
getPassword().isNotEmpty()
|
|
||||||
|
|
||||||
fun getUsername() = trackPreferences.trackUsername(this).get()
|
fun getUsername(): String
|
||||||
|
|
||||||
fun getPassword() = trackPreferences.trackPassword(this).get()
|
fun getPassword(): String
|
||||||
|
|
||||||
fun saveCredentials(username: String, password: String) {
|
fun saveCredentials(username: String, password: String)
|
||||||
trackPreferences.setCredentials(this, username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move this to an interactor, and update all trackers based on common data
|
// TODO: move this to an interactor, and update all trackers based on common data
|
||||||
suspend fun register(item: Track, mangaId: Long) {
|
suspend fun register(item: Track, mangaId: Long)
|
||||||
item.manga_id = mangaId
|
|
||||||
try {
|
|
||||||
withIOContext {
|
|
||||||
val allChapters = Injekt.get<GetChapterByMangaId>().await(mangaId)
|
|
||||||
val hasReadChapters = allChapters.any { it.read }
|
|
||||||
bind(item, hasReadChapters)
|
|
||||||
|
|
||||||
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
suspend fun setRemoteStatus(track: Track, status: Int)
|
||||||
|
|
||||||
insertTrack.await(track)
|
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int)
|
||||||
|
|
||||||
// TODO: merge into [SyncChapterProgressWithTrack]?
|
suspend fun setRemoteScore(track: Track, scoreString: String)
|
||||||
// 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) {
|
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
|
||||||
track = track.copy(
|
|
||||||
lastChapterRead = latestLocalReadChapterNumber,
|
|
||||||
)
|
|
||||||
setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track.startDate <= 0) {
|
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
|
||||||
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
|
|
||||||
.sortedBy { it.readAt }
|
|
||||||
.firstOrNull()
|
|
||||||
?.readAt
|
|
||||||
|
|
||||||
firstReadChapterDate?.let {
|
|
||||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
|
|
||||||
track = track.copy(
|
|
||||||
startDate = startDate,
|
|
||||||
)
|
|
||||||
setRemoteStartDate(track.toDbTrack(), startDate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
syncChapterProgressWithTrack.await(mangaId, track, this@Tracker)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
withUIContext { Injekt.get<Application>().toast(e.message) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setRemoteStatus(track: Track, status: Int) {
|
|
||||||
track.status = status
|
|
||||||
if (track.status == getCompletionStatus() && track.total_chapters != 0) {
|
|
||||||
track.last_chapter_read = track.total_chapters.toFloat()
|
|
||||||
}
|
|
||||||
withIOContext { updateRemote(track) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
|
||||||
if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
|
|
||||||
track.status = getReadingStatus()
|
|
||||||
}
|
|
||||||
track.last_chapter_read = chapterNumber.toFloat()
|
|
||||||
if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) {
|
|
||||||
track.status = getCompletionStatus()
|
|
||||||
track.finished_reading_date = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
withIOContext { updateRemote(track) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setRemoteScore(track: Track, scoreString: String) {
|
|
||||||
track.score = indexToScore(getScoreList().indexOf(scoreString))
|
|
||||||
withIOContext { updateRemote(track) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setRemoteStartDate(track: Track, epochMillis: Long) {
|
|
||||||
track.started_reading_date = epochMillis
|
|
||||||
withIOContext { updateRemote(track) }
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long) {
|
|
||||||
track.finished_reading_date = epochMillis
|
|
||||||
withIOContext { updateRemote(track) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateRemote(track: Track) {
|
|
||||||
withIOContext {
|
|
||||||
try {
|
|
||||||
update(track)
|
|
||||||
track.toDomainTrack(idRequired = false)?.let {
|
|
||||||
insertTrack.await(it)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=$id" }
|
|
||||||
withUIContext { Injekt.get<Application>().toast(e.message) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,15 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class Anilist(id: Long) : Tracker(id, "AniList"), DeletableTracker {
|
class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -4,13 +4,13 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class Bangumi(id: Long) : Tracker(id, "Bangumi") {
|
class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
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.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
@ -16,7 +16,7 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class Kavita(id: Long) : Tracker(id, "Kavita"), EnhancedTracker {
|
class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val UNREAD = 1
|
const val UNREAD = 1
|
||||||
|
@ -4,15 +4,15 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
class Kitsu(id: Long) : Tracker(id, "Kitsu"), DeletableTracker {
|
class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -4,8 +4,8 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
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.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import okhttp3.Dns
|
import okhttp3.Dns
|
||||||
@ -13,7 +13,7 @@ import okhttp3.OkHttpClient
|
|||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class Komga(id: Long) : Tracker(id, "Komga"), EnhancedTracker {
|
class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val UNREAD = 1
|
const val UNREAD = 1
|
||||||
|
@ -4,13 +4,13 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
|
||||||
class MangaUpdates(id: Long) : Tracker(id, "MangaUpdates"), DeletableTracker {
|
class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING_LIST = 0
|
const val READING_LIST = 0
|
||||||
|
@ -4,14 +4,14 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MyAnimeList(id: Long) : Tracker(id, "MyAnimeList"), DeletableTracker {
|
class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -4,14 +4,14 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class Shikimori(id: Long) : Tracker(id, "Shikimori"), DeletableTracker {
|
class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val READING = 1
|
const val READING = 1
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikimori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
@ -37,12 +38,12 @@ class ShikimoriApi(
|
|||||||
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track, user_id: String): Track {
|
suspend fun addLibManga(track: Track, userId: String): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
with(json) {
|
with(json) {
|
||||||
val payload = buildJsonObject {
|
val payload = buildJsonObject {
|
||||||
putJsonObject("user_rate") {
|
putJsonObject("user_rate") {
|
||||||
put("user_id", user_id)
|
put("user_id", userId)
|
||||||
put("target_id", track.media_id)
|
put("target_id", track.media_id)
|
||||||
put("target_type", "Manga")
|
put("target_type", "Manga")
|
||||||
put("chapters", track.last_chapter_read.toInt())
|
put("chapters", track.last_chapter_read.toInt())
|
||||||
@ -65,7 +66,7 @@ class ShikimoriApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
|
suspend fun updateLibManga(track: Track, userId: String): Track = addLibManga(track, userId)
|
||||||
|
|
||||||
suspend fun deleteLibManga(track: Track): Track {
|
suspend fun deleteLibManga(track: Track): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
@ -194,14 +195,14 @@ class ShikimoriApi(
|
|||||||
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
||||||
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
||||||
|
|
||||||
private const val baseUrl = "https://shikimori.me"
|
private const val baseUrl = "https://shikimori.one"
|
||||||
private const val apiUrl = "$baseUrl/api"
|
private const val apiUrl = "$baseUrl/api"
|
||||||
private const val oauthUrl = "$baseUrl/oauth/token"
|
private const val oauthUrl = "$baseUrl/oauth/token"
|
||||||
private const val loginUrl = "$baseUrl/oauth/authorize"
|
private const val loginUrl = "$baseUrl/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
||||||
|
|
||||||
fun authUrl() = loginUrl.toUri().buildUpon()
|
fun authUrl(): Uri = loginUrl.toUri().buildUpon()
|
||||||
.appendQueryParameter("client_id", clientId)
|
.appendQueryParameter("client_id", clientId)
|
||||||
.appendQueryParameter("redirect_uri", redirectUrl)
|
.appendQueryParameter("redirect_uri", redirectUrl)
|
||||||
.appendQueryParameter("response_type", "code")
|
.appendQueryParameter("response_type", "code")
|
||||||
|
@ -4,14 +4,14 @@ import android.graphics.Color
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||||
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.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import tachiyomi.domain.manga.model.Manga as DomainManga
|
import tachiyomi.domain.manga.model.Manga as DomainManga
|
||||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||||
|
|
||||||
class Suwayomi(id: Long) : Tracker(id, "Suwayomi"), EnhancedTracker {
|
class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
|
||||||
|
|
||||||
val api by lazy { SuwayomiApi(id) }
|
val api by lazy { SuwayomiApi(id) }
|
||||||
|
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
package eu.kanade.tachiyomi.dev.preview
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import tachiyomi.domain.track.model.Track
|
||||||
|
|
||||||
|
data class DummyTracker(
|
||||||
|
override val id: Long,
|
||||||
|
override val name: String,
|
||||||
|
override val supportsReadingDates: Boolean = false,
|
||||||
|
override val isLoggedIn: Boolean = false,
|
||||||
|
val valLogoColor: Int = Color.rgb(18, 25, 35),
|
||||||
|
val valLogo: Int = R.drawable.ic_tracker_anilist,
|
||||||
|
val valStatuses: List<Int> = (1..6).toList(),
|
||||||
|
val valReadingStatus: Int = 1,
|
||||||
|
val valRereadingStatus: Int = 1,
|
||||||
|
val valCompletionStatus: Int = 2,
|
||||||
|
val valScoreList: List<String> = (0..10).map(Int::toString),
|
||||||
|
val val10PointScore: Double = 5.4,
|
||||||
|
val valSearchResults: List<TrackSearch> = listOf(),
|
||||||
|
) : Tracker {
|
||||||
|
|
||||||
|
override val client: OkHttpClient
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
|
||||||
|
override fun getLogoColor(): Int = valLogoColor
|
||||||
|
|
||||||
|
override fun getLogo(): Int = valLogo
|
||||||
|
|
||||||
|
override fun getStatusList(): List<Int> = valStatuses
|
||||||
|
|
||||||
|
override fun getStatus(status: Int): Int? = when (status) {
|
||||||
|
1 -> R.string.reading
|
||||||
|
2 -> R.string.plan_to_read
|
||||||
|
3 -> R.string.completed
|
||||||
|
4 -> R.string.on_hold
|
||||||
|
5 -> R.string.dropped
|
||||||
|
6 -> R.string.repeating
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReadingStatus(): Int = valReadingStatus
|
||||||
|
|
||||||
|
override fun getRereadingStatus(): Int = valRereadingStatus
|
||||||
|
|
||||||
|
override fun getCompletionStatus(): Int = valCompletionStatus
|
||||||
|
|
||||||
|
override fun getScoreList(): List<String> = valScoreList
|
||||||
|
|
||||||
|
override fun get10PointScore(track: Track): Double = val10PointScore
|
||||||
|
|
||||||
|
override fun indexToScore(index: Int): Float = getScoreList()[index].toFloat()
|
||||||
|
|
||||||
|
override fun displayScore(track: eu.kanade.tachiyomi.data.database.models.Track): String =
|
||||||
|
track.score.toString()
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
didReadChapter: Boolean,
|
||||||
|
): eu.kanade.tachiyomi.data.database.models.Track = track
|
||||||
|
|
||||||
|
override suspend fun bind(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
hasReadChapters: Boolean,
|
||||||
|
): eu.kanade.tachiyomi.data.database.models.Track = track
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<TrackSearch> = valSearchResults
|
||||||
|
|
||||||
|
override suspend fun refresh(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
): eu.kanade.tachiyomi.data.database.models.Track = track
|
||||||
|
|
||||||
|
override suspend fun login(username: String, password: String) = Unit
|
||||||
|
|
||||||
|
override fun logout() = Unit
|
||||||
|
|
||||||
|
override fun getUsername(): String = "username"
|
||||||
|
|
||||||
|
override fun getPassword(): String = "passw0rd"
|
||||||
|
|
||||||
|
override fun saveCredentials(username: String, password: String) = Unit
|
||||||
|
|
||||||
|
override suspend fun register(
|
||||||
|
item: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
mangaId: Long,
|
||||||
|
) = Unit
|
||||||
|
|
||||||
|
override suspend fun setRemoteStatus(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
status: Int,
|
||||||
|
) = Unit
|
||||||
|
|
||||||
|
override suspend fun setRemoteLastChapterRead(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
chapterNumber: Int,
|
||||||
|
) = Unit
|
||||||
|
|
||||||
|
override suspend fun setRemoteScore(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
scoreString: String,
|
||||||
|
) = Unit
|
||||||
|
|
||||||
|
override suspend fun setRemoteStartDate(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
epochMillis: Long,
|
||||||
|
) = Unit
|
||||||
|
|
||||||
|
override suspend fun setRemoteFinishDate(
|
||||||
|
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||||
|
epochMillis: Long,
|
||||||
|
) = Unit
|
||||||
|
}
|
@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
|||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import eu.kanade.tachiyomi.util.lang.Hash
|
import eu.kanade.tachiyomi.util.lang.Hash
|
||||||
|
import eu.kanade.tachiyomi.util.storage.copyAndSetReadOnlyTo
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@ -97,7 +98,8 @@ internal object ExtensionLoader {
|
|||||||
|
|
||||||
val target = File(getPrivateExtensionDir(context), "${extension.packageName}.$PRIVATE_EXTENSION_EXTENSION")
|
val target = File(getPrivateExtensionDir(context), "${extension.packageName}.$PRIVATE_EXTENSION_EXTENSION")
|
||||||
return try {
|
return try {
|
||||||
file.copyTo(target, overwrite = true)
|
target.delete()
|
||||||
|
file.copyAndSetReadOnlyTo(target, overwrite = true)
|
||||||
if (currentExtension != null) {
|
if (currentExtension != null) {
|
||||||
ExtensionInstallReceiver.notifyReplaced(context, extension.packageName)
|
ExtensionInstallReceiver.notifyReplaced(context, extension.packageName)
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,7 +14,7 @@ import cafe.adriel.voyager.navigator.Navigator
|
|||||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.presentation.components.TabbedScreen
|
import eu.kanade.presentation.components.TabbedScreen
|
||||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
import eu.kanade.presentation.permissions.PermissionRequestHelper
|
||||||
import eu.kanade.presentation.util.Tab
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
@ -23,7 +23,6 @@ import eu.kanade.tachiyomi.ui.browse.migration.sources.migrateSourceTab
|
|||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|
||||||
|
|
||||||
data class BrowseTab(
|
data class BrowseTab(
|
||||||
private val toExtensions: Boolean = false,
|
private val toExtensions: Boolean = false,
|
||||||
@ -66,7 +65,7 @@ data class BrowseTab(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// For local source
|
// For local source
|
||||||
DiskUtil.RequestStoragePermission()
|
PermissionRequestHelper.requestStoragePermission()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
@ -29,7 +29,7 @@ class ExtensionFilterScreenModel(
|
|||||||
val events: Flow<ExtensionFilterEvent> = _events.receiveAsFlow()
|
val events: Flow<ExtensionFilterEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
combine(
|
combine(
|
||||||
getExtensionLanguages.subscribe(),
|
getExtensionLanguages.subscribe(),
|
||||||
preferences.enabledLanguages().changes(),
|
preferences.enabledLanguages().changes(),
|
||||||
|
@ -4,7 +4,7 @@ import android.app.Application
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS
|
import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS
|
||||||
@ -74,7 +74,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS),
|
state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS),
|
||||||
_currentDownloads,
|
_currentDownloads,
|
||||||
@ -118,11 +118,11 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launchIO { findAvailableExtensions() }
|
screenModelScope.launchIO { findAvailableExtensions() }
|
||||||
|
|
||||||
preferences.extensionUpdatesCount().changes()
|
preferences.extensionUpdatesCount().changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(updates = it) } }
|
.onEach { mutableState.update { state -> state.copy(updates = it) } }
|
||||||
.launchIn(coroutineScope)
|
.launchIn(screenModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String?) {
|
fun search(query: String?) {
|
||||||
@ -132,7 +132,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateAllExtensions() {
|
fun updateAllExtensions() {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
state.value.items.values.flatten()
|
state.value.items.values.flatten()
|
||||||
.map { it.extension }
|
.map { it.extension }
|
||||||
.filterIsInstance<Extension.Installed>()
|
.filterIsInstance<Extension.Installed>()
|
||||||
@ -142,13 +142,13 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun installExtension(extension: Extension.Available) {
|
fun installExtension(extension: Extension.Available) {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
extensionManager.installExtension(extension).collectToInstallUpdate(extension)
|
extensionManager.installExtension(extension).collectToInstallUpdate(extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateExtension(extension: Extension.Installed) {
|
fun updateExtension(extension: Extension.Installed) {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
extensionManager.updateExtension(extension).collectToInstallUpdate(extension)
|
extensionManager.updateExtension(extension).collectToInstallUpdate(extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,7 +176,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findAvailableExtensions() {
|
fun findAvailableExtensions() {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
mutableState.update { it.copy(isRefreshing = true) }
|
mutableState.update { it.copy(isRefreshing = true) }
|
||||||
|
|
||||||
extensionManager.findAvailableExtensions()
|
extensionManager.findAvailableExtensions()
|
||||||
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.extension.details
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
@ -44,7 +44,7 @@ class ExtensionDetailsScreenModel(
|
|||||||
val events: Flow<ExtensionDetailsEvent> = _events.receiveAsFlow()
|
val events: Flow<ExtensionDetailsEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
launch {
|
launch {
|
||||||
extensionManager.installedExtensionsFlow
|
extensionManager.installedExtensionsFlow
|
||||||
.map { it.firstOrNull { extension -> extension.pkgName == pkgName } }
|
.map { it.firstOrNull { extension -> extension.pkgName == pkgName } }
|
||||||
|
@ -7,9 +7,6 @@ import android.view.View
|
|||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
@ -34,7 +31,7 @@ import androidx.preference.forEach
|
|||||||
import androidx.preference.getOnBindEditTextListener
|
import androidx.preference.getOnBindEditTextListener
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.UpIcon
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
|
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
|
||||||
@ -55,13 +52,9 @@ class SourcePreferencesScreen(val sourceId: Long) : Screen() {
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
AppBar(
|
||||||
title = { Text(text = Injekt.get<SourceManager>().getOrStub(sourceId).toString()) },
|
title = Injekt.get<SourceManager>().getOrStub(sourceId).toString(),
|
||||||
navigationIcon = {
|
navigateUp = navigator::pop,
|
||||||
IconButton(onClick = navigator::pop) {
|
|
||||||
UpIcon()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = it,
|
scrollBehavior = it,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -30,7 +30,7 @@ class MigrateMangaScreenModel(
|
|||||||
val events: Flow<MigrationMangaEvent> = _events.receiveAsFlow()
|
val events: Flow<MigrationMangaEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
state.copy(source = sourceManager.getOrStub(sourceId))
|
state.copy(source = sourceManager.getOrStub(sourceId))
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.search
|
package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
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.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -22,9 +18,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
@ -55,6 +49,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
|
|||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
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 tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -69,7 +64,6 @@ internal fun MigrateDialog(
|
|||||||
onClickTitle: () -> Unit,
|
onClickTitle: () -> Unit,
|
||||||
onPopScreen: () -> Unit,
|
onPopScreen: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -92,16 +86,11 @@ internal fun MigrateDialog(
|
|||||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
flags.forEachIndexed { index, flag ->
|
flags.forEachIndexed { index, flag ->
|
||||||
val onChange = { selectedFlags[index] = !selectedFlags[index] }
|
LabeledCheckbox(
|
||||||
Row(
|
label = stringResource(flag.titleId),
|
||||||
modifier = Modifier
|
checked = selectedFlags[index],
|
||||||
.fillMaxWidth()
|
onCheckedChange = { selectedFlags[index] = it },
|
||||||
.clickable(onClick = onChange),
|
)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(checked = selectedFlags[index], onCheckedChange = { onChange() })
|
|
||||||
Text(text = context.getString(flag.titleId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
@ -16,7 +16,7 @@ class MigrateSearchScreenDialogScreenModel(
|
|||||||
) : StateScreenModel<MigrateSearchScreenDialogScreenModel.State>(State()) {
|
) : StateScreenModel<MigrateSearchScreenDialogScreenModel.State>(State()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
val manga = getManga.await(mangaId)!!
|
val manga = getManga.await(mangaId)!!
|
||||||
|
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.search
|
package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
@ -16,7 +16,7 @@ class MigrateSearchScreenModel(
|
|||||||
) : SearchScreenModel() {
|
) : SearchScreenModel() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
val manga = getManga.await(mangaId)!!
|
val manga = getManga.await(mangaId)!!
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
@ -30,7 +30,7 @@ class MigrateSourceScreenModel(
|
|||||||
val channel = _channel.receiveAsFlow()
|
val channel = _channel.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
getSourcesWithFavoriteCount.subscribe()
|
getSourcesWithFavoriteCount.subscribe()
|
||||||
.catch {
|
.catch {
|
||||||
logcat(LogPriority.ERROR, it)
|
logcat(LogPriority.ERROR, it)
|
||||||
@ -48,11 +48,11 @@ class MigrateSourceScreenModel(
|
|||||||
|
|
||||||
preferences.migrationSortingDirection().changes()
|
preferences.migrationSortingDirection().changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(sortingDirection = it) } }
|
.onEach { mutableState.update { state -> state.copy(sortingDirection = it) } }
|
||||||
.launchIn(coroutineScope)
|
.launchIn(screenModelScope)
|
||||||
|
|
||||||
preferences.migrationSortingMode().changes()
|
preferences.migrationSortingMode().changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(sortingMode = it) } }
|
.onEach { mutableState.update { state -> state.copy(sortingMode = it) } }
|
||||||
.launchIn(coroutineScope)
|
.launchIn(screenModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSortingMode() {
|
fun toggleSortingMode() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -29,7 +30,7 @@ fun Screen.migrateSourceTab(): TabContent {
|
|||||||
actions = listOf(
|
actions = listOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.migration_help_guide),
|
title = stringResource(R.string.migration_help_guide),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = {
|
onClick = {
|
||||||
uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration")
|
uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration")
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.source
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
@ -25,7 +25,7 @@ class SourcesFilterScreenModel(
|
|||||||
) : StateScreenModel<SourcesFilterScreenModel.State>(State.Loading) {
|
) : StateScreenModel<SourcesFilterScreenModel.State>(State.Loading) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
combine(
|
combine(
|
||||||
getLanguagesWithSources.subscribe(),
|
getLanguagesWithSources.subscribe(),
|
||||||
preferences.enabledLanguages().changes(),
|
preferences.enabledLanguages().changes(),
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.source
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
@ -31,7 +31,7 @@ class SourcesScreenModel(
|
|||||||
val events = _events.receiveAsFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
getEnabledSources.subscribe()
|
getEnabledSources.subscribe()
|
||||||
.catch {
|
.catch {
|
||||||
logcat(LogPriority.ERROR, it)
|
logcat(LogPriority.ERROR, it)
|
||||||
|
@ -12,7 +12,7 @@ import androidx.paging.cachedIn
|
|||||||
import androidx.paging.filter
|
import androidx.paging.filter
|
||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
@ -72,7 +72,7 @@ class BrowseSourceScreenModel(
|
|||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
|
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
|
||||||
|
|
||||||
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
|
var displayMode by sourcePreferences.sourceDisplayMode().asState(screenModelScope)
|
||||||
|
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ class BrowseSourceScreenModel(
|
|||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
*/
|
*/
|
||||||
fun changeMangaFavorite(manga: Manga) {
|
fun changeMangaFavorite(manga: Manga) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
var new = manga.copy(
|
var new = manga.copy(
|
||||||
favorite = !manga.favorite,
|
favorite = !manga.favorite,
|
||||||
dateAdded = when (manga.favorite) {
|
dateAdded = when (manga.favorite) {
|
||||||
@ -241,7 +241,7 @@ class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addFavorite(manga: Manga) {
|
fun addFavorite(manga: Manga) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||||
@ -291,7 +291,7 @@ class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
setMangaCategories.await(
|
setMangaCategories.await(
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
categoryIds = categoryIds.toList(),
|
categoryIds = categoryIds.toList(),
|
||||||
|
@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.category
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -31,7 +31,7 @@ class CategoryScreenModel(
|
|||||||
val events = _events.receiveAsFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
getCategories.subscribe()
|
getCategories.subscribe()
|
||||||
.collectLatest { categories ->
|
.collectLatest { categories ->
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
@ -44,7 +44,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createCategory(name: String) {
|
fun createCategory(name: String) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
when (createCategoryWithName.await(name)) {
|
when (createCategoryWithName.await(name)) {
|
||||||
is CreateCategoryWithName.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is CreateCategoryWithName.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -53,7 +53,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCategory(categoryId: Long) {
|
fun deleteCategory(categoryId: Long) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
when (deleteCategory.await(categoryId = categoryId)) {
|
when (deleteCategory.await(categoryId = categoryId)) {
|
||||||
is DeleteCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is DeleteCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -62,7 +62,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sortAlphabetically() {
|
fun sortAlphabetically() {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
when (reorderCategory.sortAlphabetically()) {
|
when (reorderCategory.sortAlphabetically()) {
|
||||||
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -71,7 +71,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun moveUp(category: Category) {
|
fun moveUp(category: Category) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
when (reorderCategory.moveUp(category)) {
|
when (reorderCategory.moveUp(category)) {
|
||||||
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -80,7 +80,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun moveDown(category: Category) {
|
fun moveDown(category: Category) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
when (reorderCategory.moveDown(category)) {
|
when (reorderCategory.moveDown(category)) {
|
||||||
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -89,7 +89,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun renameCategory(category: Category, name: String) {
|
fun renameCategory(category: Category, name: String) {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
when (renameCategory.await(category, name)) {
|
when (renameCategory.await(category, name)) {
|
||||||
is RenameCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is RenameCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
|
@ -5,6 +5,7 @@ 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.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
@ -14,6 +15,7 @@ import eu.kanade.presentation.util.Screen
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
|
||||||
@ -23,6 +25,7 @@ class DeepLinkScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
@ -46,12 +49,22 @@ class DeepLinkScreen(
|
|||||||
navigator.replace(GlobalSearchScreen(query))
|
navigator.replace(GlobalSearchScreen(query))
|
||||||
}
|
}
|
||||||
is DeepLinkScreenModel.State.Result -> {
|
is DeepLinkScreenModel.State.Result -> {
|
||||||
navigator.replace(
|
val resultState = state as DeepLinkScreenModel.State.Result
|
||||||
MangaScreen(
|
if (resultState.chapterId == null) {
|
||||||
(state as DeepLinkScreenModel.State.Result).manga.id,
|
navigator.replace(
|
||||||
true,
|
MangaScreen(
|
||||||
),
|
resultState.manga.id,
|
||||||
)
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
navigator.pop()
|
||||||
|
ReaderActivity.newIntent(
|
||||||
|
context,
|
||||||
|
resultState.manga.id,
|
||||||
|
resultState.chapterId,
|
||||||
|
).also(context::startActivity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,21 @@ package eu.kanade.tachiyomi.ui.deeplink
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.manga.model.toDomainManga
|
import eu.kanade.domain.manga.model.toDomainManga
|
||||||
|
import eu.kanade.domain.manga.model.toSManga
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.UriType
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import tachiyomi.core.util.lang.launchIO
|
import tachiyomi.core.util.lang.launchIO
|
||||||
|
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
||||||
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -15,25 +25,59 @@ import uy.kohesive.injekt.api.get
|
|||||||
class DeepLinkScreenModel(
|
class DeepLinkScreenModel(
|
||||||
query: String = "",
|
query: String = "",
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
|
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
||||||
|
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
|
||||||
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||||
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
|
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
val manga = sourceManager.getCatalogueSources()
|
val source = sourceManager.getCatalogueSources()
|
||||||
.filterIsInstance<ResolvableSource>()
|
.filterIsInstance<ResolvableSource>()
|
||||||
.filter { it.canResolveUri(query) }
|
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
||||||
.firstNotNullOfOrNull { it.getManga(query)?.toDomainManga(it.id) }
|
|
||||||
|
val manga = source?.getManga(query)?.let {
|
||||||
|
getMangaFromSManga(it, source.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
val chapter = if (source?.getUriType(query) == UriType.Chapter && manga != null) {
|
||||||
|
source.getChapter(query)?.let { getChapterFromSChapter(it, manga, source) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
if (manga == null) {
|
if (manga == null) {
|
||||||
State.NoResults
|
State.NoResults
|
||||||
} else {
|
} else {
|
||||||
State.Result(manga)
|
if (chapter == null) {
|
||||||
|
State.Result(manga)
|
||||||
|
} else {
|
||||||
|
State.Result(manga, chapter.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getChapterFromSChapter(sChapter: SChapter, manga: Manga, source: Source): Chapter? {
|
||||||
|
val localChapter = getChapterByUrlAndMangaId.await(sChapter.url, manga.id)
|
||||||
|
|
||||||
|
return if (localChapter == null) {
|
||||||
|
val sourceChapters = source.getChapterList(manga.toSManga())
|
||||||
|
val newChapters = syncChaptersWithSource.await(sourceChapters, manga, source, false)
|
||||||
|
newChapters.find { it.url == sChapter.url }
|
||||||
|
} else {
|
||||||
|
localChapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getMangaFromSManga(sManga: SManga, sourceId: Long): Manga {
|
||||||
|
return getMangaByUrlAndSourceId.awaitManga(sManga.url, sourceId)
|
||||||
|
?: networkToLocalManga.await(sManga.toDomainManga(sourceId))
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface State {
|
sealed interface State {
|
||||||
@Immutable
|
@Immutable
|
||||||
data object Loading : State
|
data object Loading : State
|
||||||
@ -42,6 +86,6 @@ class DeepLinkScreenModel(
|
|||||||
data object NoResults : State
|
data object NoResults : State
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Result(val manga: Manga) : State
|
data class Result(val manga: Manga, val chapterId: Long? = null) : State
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Sort
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.outlined.Pause
|
import androidx.compose.material.icons.outlined.Pause
|
||||||
import androidx.compose.material.icons.outlined.Sort
|
import androidx.compose.material.icons.outlined.Sort
|
||||||
@ -185,7 +186,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
listOf(
|
listOf(
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_sort),
|
title = stringResource(R.string.action_sort),
|
||||||
icon = Icons.Outlined.Sort,
|
icon = Icons.AutoMirrored.Outlined.Sort,
|
||||||
onClick = { sortExpanded = true },
|
onClick = { sortExpanded = true },
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.download
|
|||||||
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
@ -114,7 +114,7 @@ class DownloadQueueScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
downloadManager.queueState
|
downloadManager.queueState
|
||||||
.map { downloads ->
|
.map { downloads ->
|
||||||
downloads
|
downloads
|
||||||
@ -208,7 +208,7 @@ class DownloadQueueScreenModel(
|
|||||||
* @param download the download to observe its progress.
|
* @param download the download to observe its progress.
|
||||||
*/
|
*/
|
||||||
private fun launchProgressJob(download: Download) {
|
private fun launchProgressJob(download: Download) {
|
||||||
val job = coroutineScope.launch {
|
val job = screenModelScope.launch {
|
||||||
while (download.pages == null) {
|
while (download.pages == null) {
|
||||||
delay(50)
|
delay(50)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.history
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
import eu.kanade.presentation.history.HistoryUiModel
|
import eu.kanade.presentation.history.HistoryUiModel
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||||
@ -40,7 +40,7 @@ class HistoryScreenModel(
|
|||||||
val events: Flow<Event> = _events.receiveAsFlow()
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
screenModelScope.launch {
|
||||||
state.map { it.searchQuery }
|
state.map { it.searchQuery }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest { query ->
|
.flatMapLatest { query ->
|
||||||
@ -75,7 +75,7 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,19 +86,19 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeFromHistory(history: HistoryWithRelations) {
|
fun removeFromHistory(history: HistoryWithRelations) {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
removeHistory.await(history)
|
removeHistory.await(history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAllFromHistory(mangaId: Long) {
|
fun removeAllFromHistory(mangaId: Long) {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
removeHistory.await(mangaId)
|
removeHistory.await(mangaId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAllHistory() {
|
fun removeAllHistory() {
|
||||||
coroutineScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
val result = removeHistory.awaitAll()
|
val result = removeHistory.awaitAll()
|
||||||
if (!result) return@launchIO
|
if (!result) return@launchIO
|
||||||
_events.send(Event.HistoryCleared)
|
_events.send(Event.HistoryCleared)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user