mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Initial downloads menu implementation
This commit is contained in:
@@ -16,8 +16,10 @@ import ca.gosyer.data.server.Http
|
|||||||
import ca.gosyer.data.server.HttpProvider
|
import ca.gosyer.data.server.HttpProvider
|
||||||
import ca.gosyer.data.server.ServerPreferences
|
import ca.gosyer.data.server.ServerPreferences
|
||||||
import ca.gosyer.data.server.ServerService
|
import ca.gosyer.data.server.ServerService
|
||||||
|
import ca.gosyer.data.server.interactions.BackupInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
|
import ca.gosyer.data.server.interactions.DownloadInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.ExtensionInteractionHandler
|
import ca.gosyer.data.server.interactions.ExtensionInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.LibraryInteractionHandler
|
import ca.gosyer.data.server.interactions.LibraryInteractionHandler
|
||||||
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
import ca.gosyer.data.server.interactions.MangaInteractionHandler
|
||||||
@@ -58,10 +60,14 @@ val DataModule = module {
|
|||||||
.toProvider(HttpProvider::class)
|
.toProvider(HttpProvider::class)
|
||||||
.providesSingleton()
|
.providesSingleton()
|
||||||
|
|
||||||
|
bind<BackupInteractionHandler>()
|
||||||
|
.toClass<BackupInteractionHandler>()
|
||||||
bind<CategoryInteractionHandler>()
|
bind<CategoryInteractionHandler>()
|
||||||
.toClass<CategoryInteractionHandler>()
|
.toClass<CategoryInteractionHandler>()
|
||||||
bind<ChapterInteractionHandler>()
|
bind<ChapterInteractionHandler>()
|
||||||
.toClass<ChapterInteractionHandler>()
|
.toClass<ChapterInteractionHandler>()
|
||||||
|
bind<DownloadInteractionHandler>()
|
||||||
|
.toClass<DownloadInteractionHandler>()
|
||||||
bind<ExtensionInteractionHandler>()
|
bind<ExtensionInteractionHandler>()
|
||||||
.toClass<ExtensionInteractionHandler>()
|
.toClass<ExtensionInteractionHandler>()
|
||||||
bind<LibraryInteractionHandler>()
|
bind<LibraryInteractionHandler>()
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ class DownloadService @Inject constructor(
|
|||||||
private val serverUrl = serverPreferences.serverUrl().stateIn(GlobalScope)
|
private val serverUrl = serverPreferences.serverUrl().stateIn(GlobalScope)
|
||||||
private val _downloaderStatus = MutableStateFlow(DownloaderStatus.Stopped)
|
private val _downloaderStatus = MutableStateFlow(DownloaderStatus.Stopped)
|
||||||
val downloaderStatus = _downloaderStatus.asStateFlow()
|
val downloaderStatus = _downloaderStatus.asStateFlow()
|
||||||
|
|
||||||
|
private val _downloadQueue = MutableStateFlow(emptyList<DownloadChapter>())
|
||||||
|
val downloadQueue = _downloadQueue.asStateFlow()
|
||||||
|
|
||||||
private val watching = mutableMapOf<Long, MutableSharedFlow<List<DownloadChapter>>>()
|
private val watching = mutableMapOf<Long, MutableSharedFlow<List<DownloadChapter>>>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -58,6 +62,7 @@ class DownloadService @Inject constructor(
|
|||||||
frame as Frame.Text
|
frame as Frame.Text
|
||||||
val status = json.decodeFromString<DownloadStatus>(frame.readText())
|
val status = json.decodeFromString<DownloadStatus>(frame.readText())
|
||||||
_downloaderStatus.value = status.status
|
_downloaderStatus.value = status.status
|
||||||
|
_downloadQueue.value = status.queue
|
||||||
val queue = status.queue.groupBy { it.mangaId }
|
val queue = status.queue.groupBy { it.mangaId }
|
||||||
watching.forEach { (mangaId, flow) ->
|
watching.forEach { (mangaId, flow) ->
|
||||||
flow.emit(queue[mangaId].orEmpty())
|
flow.emit(queue[mangaId].orEmpty())
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import kotlinx.serialization.Serializable
|
|||||||
data class DownloadChapter(
|
data class DownloadChapter(
|
||||||
val chapterIndex: Int,
|
val chapterIndex: Int,
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
var state: DownloadState = DownloadState.Queued,
|
val state: DownloadState = DownloadState.Queued,
|
||||||
var progress: Float = 0f,
|
val progress: Float = 0f,
|
||||||
var tries: Int = 0,
|
val tries: Int = 0,
|
||||||
var chapter: Chapter? = null,
|
val chapter: Chapter? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BoxWithTooltip
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BoxWithTooltipSurface(
|
||||||
|
tooltip: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
contentAlignment: Alignment = Alignment.TopStart,
|
||||||
|
propagateMinConstraints: Boolean = false,
|
||||||
|
delay: Int = 500,
|
||||||
|
offset: DpOffset = DpOffset.Zero,
|
||||||
|
content: @Composable BoxScope.() -> Unit
|
||||||
|
) {
|
||||||
|
BoxWithTooltip(
|
||||||
|
{
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.shadow(4.dp),
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
elevation = 4.dp,
|
||||||
|
content = tooltip
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier,
|
||||||
|
contentAlignment,
|
||||||
|
propagateMinConstraints,
|
||||||
|
delay,
|
||||||
|
offset,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ import androidx.compose.ui.semantics.Role
|
|||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
|
|
||||||
suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
|
internal suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
|
||||||
var event: PointerEvent
|
var event: PointerEvent
|
||||||
do {
|
do {
|
||||||
event = awaitPointerEvent()
|
event = awaitPointerEvent()
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.base.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.forEachGesture
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.DropdownMenu
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DropdownIconButton(
|
||||||
|
key: Any? = Unit,
|
||||||
|
dropdownItems: @Composable ColumnScope.() -> Unit,
|
||||||
|
content: @Composable BoxScope.() -> Unit
|
||||||
|
) {
|
||||||
|
var showMenu by remember(key) { mutableStateOf(false) }
|
||||||
|
var offset by remember(key) { mutableStateOf(DpOffset(0.dp, 0.dp)) }
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMenu,
|
||||||
|
onDismissRequest = { showMenu = false },
|
||||||
|
offset = offset,
|
||||||
|
content = dropdownItems
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxHeight()
|
||||||
|
.size(48.dp)
|
||||||
|
.clickable(
|
||||||
|
remember { MutableInteractionSource() },
|
||||||
|
role = Role.Button,
|
||||||
|
indication = rememberRipple(bounded = false, radius = 24.dp)
|
||||||
|
) {
|
||||||
|
showMenu = true
|
||||||
|
}
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
forEachGesture {
|
||||||
|
awaitPointerEventScope {
|
||||||
|
awaitEventFirstDown().mouseEvent?.let {
|
||||||
|
offset = DpOffset(it.x.dp, it.y.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -94,13 +95,22 @@ fun Toolbar(
|
|||||||
Row {
|
Row {
|
||||||
actions()
|
actions()
|
||||||
if (closable) {
|
if (closable) {
|
||||||
IconButton(
|
ActionIcon(onClick = onClose, "Close", Icons.Default.Close)
|
||||||
onClick = onClose
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Close, "close", Modifier.size(52.dp))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ActionIcon(onClick: () -> Unit, contentDescription: String, icon: ImageVector) {
|
||||||
|
BoxWithTooltipSurface(
|
||||||
|
{
|
||||||
|
Text(contentDescription, modifier = Modifier.padding(10.dp))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
|
Icon(icon, contentDescription, Modifier.size(52.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
144
src/main/kotlin/ca/gosyer/ui/downloads/DownloadsMenu.kt
Normal file
144
src/main/kotlin/ca/gosyer/ui/downloads/DownloadsMenu.kt
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.downloads
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ClearAll
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.icons.filled.Pause
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.BuildConfig
|
||||||
|
import ca.gosyer.data.download.model.DownloadChapter
|
||||||
|
import ca.gosyer.data.download.model.DownloaderStatus
|
||||||
|
import ca.gosyer.data.models.Chapter
|
||||||
|
import ca.gosyer.ui.base.components.ActionIcon
|
||||||
|
import ca.gosyer.ui.base.components.DropdownIconButton
|
||||||
|
import ca.gosyer.ui.base.components.Toolbar
|
||||||
|
import ca.gosyer.ui.base.vm.viewModel
|
||||||
|
import ca.gosyer.util.compose.ThemedWindow
|
||||||
|
|
||||||
|
fun openDownloadsMenu() {
|
||||||
|
ThemedWindow(BuildConfig.NAME) {
|
||||||
|
DownloadsMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DownloadsMenu() {
|
||||||
|
val vm = viewModel<DownloadsMenuViewModel>()
|
||||||
|
val downloadQueue by vm.downloadQueue.collectAsState()
|
||||||
|
|
||||||
|
Surface {
|
||||||
|
Column {
|
||||||
|
Toolbar(
|
||||||
|
"Downloads",
|
||||||
|
closable = false,
|
||||||
|
actions = {
|
||||||
|
val downloadStatus by vm.downloaderStatus.collectAsState()
|
||||||
|
if (downloadStatus == DownloaderStatus.Started) {
|
||||||
|
ActionIcon(onClick = vm::pause, "Pause", Icons.Default.Pause)
|
||||||
|
} else {
|
||||||
|
ActionIcon(onClick = vm::start, "Continue", Icons.Default.PlayArrow)
|
||||||
|
}
|
||||||
|
ActionIcon(onClick = vm::clear, "Clear queue", Icons.Default.ClearAll)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
|
items(downloadQueue) {
|
||||||
|
downloadsItem(
|
||||||
|
it,
|
||||||
|
vm::stopDownload,
|
||||||
|
vm::moveToBottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun downloadsItem(
|
||||||
|
chapter: DownloadChapter,
|
||||||
|
onDownloadCancel: (Chapter?) -> Unit,
|
||||||
|
onMoveDownloadToBottom: (Chapter?) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.height(56.dp)
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceAround
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.SpaceAround) {
|
||||||
|
Row(Modifier.fillMaxWidth().padding(horizontal = 32.dp), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Text(chapter.chapter?.name.toString(), maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
|
// Spacer(Modifier.width(16.dp))
|
||||||
|
if (chapter.chapter?.pageCount != null && chapter.chapter.pageCount != -1) {
|
||||||
|
Text(
|
||||||
|
"${(chapter.chapter.pageCount * chapter.progress).toInt()}/${chapter.chapter.pageCount}",
|
||||||
|
Modifier.padding(start = 16.dp).requiredWidth(IntrinsicSize.Max),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Visible
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Spacer(Modifier.width(32.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
LinearProgressIndicator(
|
||||||
|
chapter.progress,
|
||||||
|
Modifier.fillMaxWidth()
|
||||||
|
.padding(start = 32.dp, end = 16.dp, bottom = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownIconButton(
|
||||||
|
chapter.mangaId to chapter.chapterIndex,
|
||||||
|
{
|
||||||
|
DropdownMenuItem(onClick = { onDownloadCancel(chapter.chapter) }) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
DropdownMenuItem(onClick = { onMoveDownloadToBottom(chapter.chapter) }) {
|
||||||
|
Text("Move to bottom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.MoreVert,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.ui.downloads
|
||||||
|
|
||||||
|
import ca.gosyer.data.download.DownloadService
|
||||||
|
import ca.gosyer.data.models.Chapter
|
||||||
|
import ca.gosyer.data.server.interactions.ChapterInteractionHandler
|
||||||
|
import ca.gosyer.data.server.interactions.DownloadInteractionHandler
|
||||||
|
import ca.gosyer.ui.base.vm.ViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DownloadsMenuViewModel @Inject constructor(
|
||||||
|
private val downloadService: DownloadService,
|
||||||
|
private val downloadsHandler: DownloadInteractionHandler,
|
||||||
|
private val chapterHandler: ChapterInteractionHandler
|
||||||
|
) : ViewModel() {
|
||||||
|
val downloaderStatus get() = downloadService.downloaderStatus
|
||||||
|
val downloadQueue get() = downloadService.downloadQueue
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
scope.launch {
|
||||||
|
downloadsHandler.startDownloading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
scope.launch {
|
||||||
|
downloadsHandler.stopDownloading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
scope.launch {
|
||||||
|
downloadsHandler.clearDownloadQueue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopDownload(chapter: Chapter?) {
|
||||||
|
chapter ?: return
|
||||||
|
scope.launch {
|
||||||
|
chapterHandler.deleteChapterDownload(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveToBottom(chapter: Chapter?) {
|
||||||
|
chapter ?: return
|
||||||
|
scope.launch {
|
||||||
|
chapterHandler.deleteChapterDownload(chapter)
|
||||||
|
chapterHandler.queueChapterDownload(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.extensions
|
package ca.gosyer.ui.extensions
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -26,6 +25,7 @@ import androidx.compose.foundation.rememberScrollbarAdapter
|
|||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -39,6 +39,7 @@ import androidx.compose.ui.text.withStyle
|
|||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
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 ca.gosyer.BuildConfig
|
||||||
import ca.gosyer.data.models.Extension
|
import ca.gosyer.data.models.Extension
|
||||||
import ca.gosyer.ui.base.components.KtorImage
|
import ca.gosyer.ui.base.components.KtorImage
|
||||||
import ca.gosyer.ui.base.components.LoadingScreen
|
import ca.gosyer.ui.base.components.LoadingScreen
|
||||||
@@ -49,12 +50,11 @@ import ca.gosyer.util.compose.persistentLazyListState
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
fun openExtensionsMenu() {
|
fun openExtensionsMenu() {
|
||||||
ThemedWindow(title = "TachideskJUI - Extensions", size = IntSize(550, 700)) {
|
ThemedWindow(BuildConfig.NAME, size = IntSize(550, 700)) {
|
||||||
ExtensionsMenu()
|
ExtensionsMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionsMenu() {
|
fun ExtensionsMenu() {
|
||||||
val vm = viewModel<ExtensionsMenuViewModel>()
|
val vm = viewModel<ExtensionsMenuViewModel>()
|
||||||
@@ -63,7 +63,7 @@ fun ExtensionsMenu() {
|
|||||||
val serverUrl by vm.serverUrl.collectAsState()
|
val serverUrl by vm.serverUrl.collectAsState()
|
||||||
val search by vm.searchQuery.collectAsState()
|
val search by vm.searchQuery.collectAsState()
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
|
Surface(Modifier.fillMaxSize()) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
LoadingScreen(isLoading)
|
LoadingScreen(isLoading)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material.ScrollableTabRow
|
import androidx.compose.material.ScrollableTabRow
|
||||||
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Tab
|
import androidx.compose.material.Tab
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -24,6 +25,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
|
import ca.gosyer.BuildConfig
|
||||||
import ca.gosyer.data.library.model.DisplayMode
|
import ca.gosyer.data.library.model.DisplayMode
|
||||||
import ca.gosyer.data.models.Category
|
import ca.gosyer.data.models.Category
|
||||||
import ca.gosyer.data.models.Manga
|
import ca.gosyer.data.models.Manga
|
||||||
@@ -35,7 +37,7 @@ import com.google.accompanist.pager.HorizontalPager
|
|||||||
import com.google.accompanist.pager.PagerState
|
import com.google.accompanist.pager.PagerState
|
||||||
|
|
||||||
fun openLibraryMenu() {
|
fun openLibraryMenu() {
|
||||||
ThemedWindow {
|
ThemedWindow(BuildConfig.NAME) {
|
||||||
LibraryScreen()
|
LibraryScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,46 +52,48 @@ fun LibraryScreen(onClickManga: (Long) -> Unit = { openMangaMenu(it) }) {
|
|||||||
val serverUrl by vm.serverUrl.collectAsState()
|
val serverUrl by vm.serverUrl.collectAsState()
|
||||||
// val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
|
// val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
|
||||||
|
|
||||||
if (categories.isEmpty()) {
|
Surface {
|
||||||
LoadingScreen(isLoading)
|
if (categories.isEmpty()) {
|
||||||
} else {
|
LoadingScreen(isLoading)
|
||||||
/*ModalBottomSheetLayout(
|
} else {
|
||||||
sheetState = sheetState,
|
/*ModalBottomSheetLayout(
|
||||||
sheetContent = { *//*LibrarySheet()*//* }
|
sheetState = sheetState,
|
||||||
) {*/
|
sheetContent = { *//*LibrarySheet()*//* }
|
||||||
Column(Modifier.fillMaxWidth()) {
|
) {*/
|
||||||
/*Toolbar(
|
Column(Modifier.fillMaxWidth()) {
|
||||||
title = {
|
/*Toolbar(
|
||||||
val text = if (vm.showCategoryTabs) {
|
title = {
|
||||||
stringResource(R.string.library_label)
|
val text = if (vm.showCategoryTabs) {
|
||||||
} else {
|
stringResource(R.string.library_label)
|
||||||
vm.selectedCategory?.visibleName.orEmpty()
|
} else {
|
||||||
|
vm.selectedCategory?.visibleName.orEmpty()
|
||||||
|
}
|
||||||
|
Text(text)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { scope.launch { sheetState.show() }}) {
|
||||||
|
Icon(Icons.Default.FilterList, contentDescription = null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Text(text)
|
)*/
|
||||||
},
|
LibraryTabs(
|
||||||
actions = {
|
visible = true, // vm.showCategoryTabs,
|
||||||
IconButton(onClick = { scope.launch { sheetState.show() }}) {
|
categories = categories,
|
||||||
Icon(Icons.Default.FilterList, contentDescription = null)
|
selectedPage = selectedCategoryIndex,
|
||||||
}
|
onPageChanged = vm::setSelectedPage
|
||||||
}
|
)
|
||||||
)*/
|
LibraryPager(
|
||||||
LibraryTabs(
|
categories = categories,
|
||||||
visible = true, // vm.showCategoryTabs,
|
displayMode = displayMode,
|
||||||
categories = categories,
|
selectedPage = selectedCategoryIndex,
|
||||||
selectedPage = selectedCategoryIndex,
|
serverUrl = serverUrl,
|
||||||
onPageChanged = vm::setSelectedPage
|
getLibraryForPage = { vm.getLibraryForCategoryIndex(it).collectAsState() },
|
||||||
)
|
onPageChanged = { vm.setSelectedPage(it) },
|
||||||
LibraryPager(
|
onClickManga = onClickManga
|
||||||
categories = categories,
|
)
|
||||||
displayMode = displayMode,
|
}
|
||||||
selectedPage = selectedCategoryIndex,
|
// }
|
||||||
serverUrl = serverUrl,
|
|
||||||
getLibraryForPage = { vm.getLibraryForCategoryIndex(it).collectAsState() },
|
|
||||||
onPageChanged = { vm.setSelectedPage(it) },
|
|
||||||
onClickManga = onClickManga
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,30 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Book
|
import androidx.compose.material.icons.filled.Book
|
||||||
|
import androidx.compose.material.icons.filled.Download
|
||||||
import androidx.compose.material.icons.filled.Explore
|
import androidx.compose.material.icons.filled.Explore
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.filled.Store
|
import androidx.compose.material.icons.filled.Store
|
||||||
import androidx.compose.material.icons.outlined.Book
|
import androidx.compose.material.icons.outlined.Book
|
||||||
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.Explore
|
import androidx.compose.material.icons.outlined.Explore
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.Store
|
import androidx.compose.material.icons.outlined.Store
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -42,9 +49,14 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ca.gosyer.BuildConfig
|
import ca.gosyer.BuildConfig
|
||||||
import ca.gosyer.data.ui.model.StartScreen
|
import ca.gosyer.data.ui.model.StartScreen
|
||||||
|
import ca.gosyer.ui.base.components.combinedMouseClickable
|
||||||
import ca.gosyer.ui.base.vm.viewModel
|
import ca.gosyer.ui.base.vm.viewModel
|
||||||
|
import ca.gosyer.ui.downloads.DownloadsMenu
|
||||||
|
import ca.gosyer.ui.downloads.DownloadsMenuViewModel
|
||||||
import ca.gosyer.ui.extensions.ExtensionsMenu
|
import ca.gosyer.ui.extensions.ExtensionsMenu
|
||||||
|
import ca.gosyer.ui.extensions.openExtensionsMenu
|
||||||
import ca.gosyer.ui.library.LibraryScreen
|
import ca.gosyer.ui.library.LibraryScreen
|
||||||
|
import ca.gosyer.ui.library.openLibraryMenu
|
||||||
import ca.gosyer.ui.manga.MangaMenu
|
import ca.gosyer.ui.manga.MangaMenu
|
||||||
import ca.gosyer.ui.settings.SettingsAdvancedScreen
|
import ca.gosyer.ui.settings.SettingsAdvancedScreen
|
||||||
import ca.gosyer.ui.settings.SettingsAppearance
|
import ca.gosyer.ui.settings.SettingsAppearance
|
||||||
@@ -56,6 +68,8 @@ import ca.gosyer.ui.settings.SettingsReaderScreen
|
|||||||
import ca.gosyer.ui.settings.SettingsScreen
|
import ca.gosyer.ui.settings.SettingsScreen
|
||||||
import ca.gosyer.ui.settings.SettingsServerScreen
|
import ca.gosyer.ui.settings.SettingsServerScreen
|
||||||
import ca.gosyer.ui.sources.SourcesMenu
|
import ca.gosyer.ui.sources.SourcesMenu
|
||||||
|
import ca.gosyer.ui.sources.openSourcesMenu
|
||||||
|
import com.github.zsoltk.compose.router.BackStack
|
||||||
import com.github.zsoltk.compose.router.Router
|
import com.github.zsoltk.compose.router.Router
|
||||||
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
import com.github.zsoltk.compose.savedinstancestate.Bundle
|
||||||
import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
||||||
@@ -66,51 +80,39 @@ fun MainMenu(rootBundle: Bundle) {
|
|||||||
Surface {
|
Surface {
|
||||||
Router("TopLevel", vm.startScreen.toRoute()) { backStack ->
|
Router("TopLevel", vm.startScreen.toRoute()) { backStack ->
|
||||||
Row {
|
Row {
|
||||||
Surface(elevation = 2.dp) {
|
SideMenu(backStack)
|
||||||
Column(Modifier.width(200.dp).fillMaxHeight(),) {
|
MainWindow(rootBundle, backStack)
|
||||||
Box(Modifier.fillMaxWidth().height(60.dp)) {
|
}
|
||||||
Text(
|
}
|
||||||
BuildConfig.NAME,
|
}
|
||||||
fontSize = 30.sp,
|
}
|
||||||
modifier = Modifier.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(20.dp))
|
|
||||||
remember { TopLevelMenus.values() }.forEach { topLevelMenu ->
|
|
||||||
MainMenuItem(
|
|
||||||
topLevelMenu,
|
|
||||||
backStack.elements.first() == topLevelMenu.menu
|
|
||||||
) {
|
|
||||||
backStack.newRoot(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(Modifier.fillMaxSize()) {
|
|
||||||
BundleScope("K${backStack.lastIndex}", rootBundle, false) {
|
|
||||||
when (val routing = backStack.last()) {
|
|
||||||
is Route.Library -> LibraryScreen {
|
|
||||||
backStack.push(Route.Manga(it))
|
|
||||||
}
|
|
||||||
is Route.Sources -> SourcesMenu {
|
|
||||||
backStack.push(Route.Manga(it))
|
|
||||||
}
|
|
||||||
is Route.Extensions -> ExtensionsMenu()
|
|
||||||
is Route.Manga -> MangaMenu(routing.mangaId, backStack)
|
|
||||||
|
|
||||||
is Route.Settings -> SettingsScreen(backStack)
|
@Composable
|
||||||
is Route.SettingsGeneral -> SettingsGeneralScreen(backStack)
|
fun SideMenu(backStack: BackStack<Route>) {
|
||||||
is Route.SettingsAppearance -> SettingsAppearance(backStack)
|
Surface(Modifier.width(200.dp).fillMaxHeight(), elevation = 2.dp) {
|
||||||
is Route.SettingsServer -> SettingsServerScreen(backStack)
|
Box(Modifier.fillMaxSize()) {
|
||||||
is Route.SettingsLibrary -> SettingsLibraryScreen(backStack)
|
Column(Modifier.fillMaxSize()) {
|
||||||
is Route.SettingsReader -> SettingsReaderScreen(backStack)
|
Box(Modifier.fillMaxWidth().height(60.dp)) {
|
||||||
/*is Route.SettingsDownloads -> SettingsDownloadsScreen(backStack)
|
Text(
|
||||||
is Route.SettingsTracking -> SettingsTrackingScreen(backStack)*/
|
BuildConfig.NAME,
|
||||||
is Route.SettingsBrowse -> SettingsBrowseScreen(backStack)
|
fontSize = 30.sp,
|
||||||
is Route.SettingsBackup -> SettingsBackupScreen(backStack)
|
modifier = Modifier.align(Alignment.Center)
|
||||||
/*is Route.SettingsSecurity -> SettingsSecurityScreen(backStack)
|
)
|
||||||
is Route.SettingsParentalControls -> SettingsParentalControlsScreen(backStack)*/
|
}
|
||||||
is Route.SettingsAdvanced -> SettingsAdvancedScreen(backStack)
|
Spacer(Modifier.height(20.dp))
|
||||||
|
remember { TopLevelMenus.values().filter { it.top } }.forEach { topLevelMenu ->
|
||||||
|
SideMenuItem(
|
||||||
|
topLevelMenu,
|
||||||
|
backStack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
Column(Modifier.align(Alignment.BottomStart).padding(bottom = 8.dp)) {
|
||||||
|
remember { TopLevelMenus.values().filterNot { it.top } }.forEach { topLevelMenu ->
|
||||||
|
SideMenuItem(
|
||||||
|
topLevelMenu,
|
||||||
|
backStack
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,33 +122,101 @@ fun MainMenu(rootBundle: Bundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainMenuItem(menu: TopLevelMenus, selected: Boolean, onClick: (Route) -> Unit) {
|
fun SideMenuItem(topLevelMenu: TopLevelMenus, backStack: BackStack<Route>) {
|
||||||
|
MainMenuItem(
|
||||||
|
backStack.elements.first() == topLevelMenu.menu,
|
||||||
|
topLevelMenu.text,
|
||||||
|
topLevelMenu.menu,
|
||||||
|
topLevelMenu.selectedIcon,
|
||||||
|
topLevelMenu.unselectedIcon,
|
||||||
|
topLevelMenu.openInNewWindow,
|
||||||
|
topLevelMenu.extraInfo
|
||||||
|
) {
|
||||||
|
backStack.newRoot(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainWindow(rootBundle: Bundle, backStack: BackStack<Route>) {
|
||||||
|
Column(Modifier.fillMaxSize()) {
|
||||||
|
BundleScope("K${backStack.lastIndex}", rootBundle, false) {
|
||||||
|
when (val routing = backStack.last()) {
|
||||||
|
is Route.Library -> LibraryScreen {
|
||||||
|
backStack.push(Route.Manga(it))
|
||||||
|
}
|
||||||
|
is Route.Sources -> SourcesMenu {
|
||||||
|
backStack.push(Route.Manga(it))
|
||||||
|
}
|
||||||
|
is Route.Extensions -> ExtensionsMenu()
|
||||||
|
is Route.Manga -> MangaMenu(routing.mangaId, backStack)
|
||||||
|
is Route.Downloads -> DownloadsMenu()
|
||||||
|
|
||||||
|
is Route.Settings -> SettingsScreen(backStack)
|
||||||
|
is Route.SettingsGeneral -> SettingsGeneralScreen(backStack)
|
||||||
|
is Route.SettingsAppearance -> SettingsAppearance(backStack)
|
||||||
|
is Route.SettingsServer -> SettingsServerScreen(backStack)
|
||||||
|
is Route.SettingsLibrary -> SettingsLibraryScreen(backStack)
|
||||||
|
is Route.SettingsReader -> SettingsReaderScreen(backStack)
|
||||||
|
/*is Route.SettingsDownloads -> SettingsDownloadsScreen(backStack)
|
||||||
|
is Route.SettingsTracking -> SettingsTrackingScreen(backStack)*/
|
||||||
|
is Route.SettingsBrowse -> SettingsBrowseScreen(backStack)
|
||||||
|
is Route.SettingsBackup -> SettingsBackupScreen(backStack)
|
||||||
|
/*is Route.SettingsSecurity -> SettingsSecurityScreen(backStack)
|
||||||
|
is Route.SettingsParentalControls -> SettingsParentalControlsScreen(backStack)*/
|
||||||
|
is Route.SettingsAdvanced -> SettingsAdvancedScreen(backStack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainMenuItem(
|
||||||
|
selected: Boolean,
|
||||||
|
text: String,
|
||||||
|
menu: Route,
|
||||||
|
selectedIcon: ImageVector,
|
||||||
|
unselectedIcon: ImageVector,
|
||||||
|
onMiddleClick: () -> Unit,
|
||||||
|
extraInfo: (@Composable () -> Unit)? = null,
|
||||||
|
onClick: (Route) -> Unit
|
||||||
|
) {
|
||||||
Card(
|
Card(
|
||||||
{ onClick(menu.menu) },
|
Modifier.fillMaxWidth(),
|
||||||
Modifier.fillMaxWidth().height(40.dp),
|
|
||||||
backgroundColor = if (!selected) {
|
backgroundColor = if (!selected) {
|
||||||
Color.Transparent
|
Color.Transparent
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colors.primary.copy(0.30F)
|
MaterialTheme.colors.primary.copy(0.30F)
|
||||||
},
|
},
|
||||||
contentColor = Color.Transparent,
|
|
||||||
elevation = 0.dp,
|
elevation = 0.dp,
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(8.dp)
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize()) {
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
.combinedMouseClickable(
|
||||||
|
onClick = { onClick(menu) },
|
||||||
|
onMiddleClick = { onMiddleClick() }
|
||||||
|
)
|
||||||
|
) {
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
Image(
|
Image(
|
||||||
if (selected) {
|
if (selected) {
|
||||||
menu.selectedIcon
|
selectedIcon
|
||||||
} else {
|
} else {
|
||||||
menu.unselectedIcon
|
unselectedIcon
|
||||||
},
|
},
|
||||||
menu.text,
|
text,
|
||||||
modifier = Modifier.size(20.dp),
|
Modifier.size(20.dp),
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface)
|
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface)
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(16.dp))
|
Spacer(Modifier.width(16.dp))
|
||||||
Text(menu.text, color = MaterialTheme.colors.onSurface)
|
Column {
|
||||||
|
Text(text, color = MaterialTheme.colors.onSurface)
|
||||||
|
if (extraInfo != null) {
|
||||||
|
extraInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,11 +227,33 @@ fun StartScreen.toRoute() = when (this) {
|
|||||||
StartScreen.Extensions -> Route.Extensions
|
StartScreen.Extensions -> Route.Extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class TopLevelMenus(val text: String, val unselectedIcon: ImageVector, val selectedIcon: ImageVector, val menu: Route) {
|
@Composable
|
||||||
Library("Library", Icons.Outlined.Book, Icons.Filled.Book, Route.Library),
|
fun DownloadsExtraInfo() {
|
||||||
Sources("Sources", Icons.Outlined.Explore, Icons.Filled.Explore, Route.Sources),
|
val vm = viewModel<DownloadsMenuViewModel>()
|
||||||
Extensions("Extensions", Icons.Outlined.Store, Icons.Filled.Store, Route.Extensions),
|
val list by vm.downloadQueue.collectAsState()
|
||||||
Settings("Settings", Icons.Outlined.Settings, Icons.Filled.Settings, Route.Settings)
|
if (list.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
"${list.size} remaining",
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TopLevelMenus(
|
||||||
|
val text: String,
|
||||||
|
val unselectedIcon: ImageVector,
|
||||||
|
val selectedIcon: ImageVector,
|
||||||
|
val menu: Route,
|
||||||
|
val top: Boolean,
|
||||||
|
val openInNewWindow: () -> Unit = {},
|
||||||
|
val extraInfo: (@Composable () -> Unit)? = null
|
||||||
|
) {
|
||||||
|
Library("Library", Icons.Outlined.Book, Icons.Filled.Book, Route.Library, true, ::openLibraryMenu),
|
||||||
|
Sources("Sources", Icons.Outlined.Explore, Icons.Filled.Explore, Route.Sources, true, ::openSourcesMenu),
|
||||||
|
Extensions("Extensions", Icons.Outlined.Store, Icons.Filled.Store, Route.Extensions, true, ::openExtensionsMenu),
|
||||||
|
Downloads("Downloads", Icons.Outlined.Download, Icons.Filled.Download, Route.Downloads, false, extraInfo = { DownloadsExtraInfo() }),
|
||||||
|
Settings("Settings", Icons.Outlined.Settings, Icons.Filled.Settings, Route.Settings, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Route {
|
sealed class Route {
|
||||||
@@ -169,6 +261,7 @@ sealed class Route {
|
|||||||
object Sources : Route()
|
object Sources : Route()
|
||||||
object Extensions : Route()
|
object Extensions : Route()
|
||||||
data class Manga(val mangaId: Long) : Route()
|
data class Manga(val mangaId: Long) : Route()
|
||||||
|
object Downloads : Route()
|
||||||
|
|
||||||
object Settings : Route()
|
object Settings : Route()
|
||||||
object SettingsGeneral : Route()
|
object SettingsGeneral : Route()
|
||||||
|
|||||||
@@ -7,26 +7,19 @@
|
|||||||
package ca.gosyer.ui.manga
|
package ca.gosyer.ui.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.gestures.forEachGesture
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.requiredSize
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.material.Card
|
||||||
import androidx.compose.material.CircularProgressIndicator
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.DropdownMenu
|
|
||||||
import androidx.compose.material.DropdownMenuItem
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
@@ -38,26 +31,18 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.ArrowDownward
|
import androidx.compose.material.icons.filled.ArrowDownward
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Error
|
import androidx.compose.material.icons.filled.Error
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
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.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.data.download.model.DownloadChapter
|
import ca.gosyer.data.download.model.DownloadChapter
|
||||||
import ca.gosyer.data.download.model.DownloadState
|
import ca.gosyer.data.download.model.DownloadState
|
||||||
import ca.gosyer.ui.base.components.awaitEventFirstDown
|
import ca.gosyer.ui.base.components.DropdownIconButton
|
||||||
import ca.gosyer.ui.base.components.combinedMouseClickable
|
import ca.gosyer.ui.base.components.combinedMouseClickable
|
||||||
import ca.gosyer.util.compose.contextMenu
|
import ca.gosyer.util.compose.contextMenu
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -140,7 +125,7 @@ fun ChapterItem(
|
|||||||
|
|
||||||
when (downloadState) {
|
when (downloadState) {
|
||||||
MangaMenuViewModel.DownloadState.Downloaded -> {
|
MangaMenuViewModel.DownloadState.Downloaded -> {
|
||||||
DownloadedIconButton(onClick = { deleteDownload(chapter.index) })
|
DownloadedIconButton(chapter.mangaId to chapter.index, onClick = { deleteDownload(chapter.index) })
|
||||||
}
|
}
|
||||||
MangaMenuViewModel.DownloadState.Downloading -> {
|
MangaMenuViewModel.DownloadState.Downloading -> {
|
||||||
DownloadingIconButton(downloadChapter, onClick = { stopDownload(chapter.index) })
|
DownloadingIconButton(downloadChapter, onClick = { stopDownload(chapter.index) })
|
||||||
@@ -167,7 +152,7 @@ private fun DownloadIconButton(onClick: () -> Unit) {
|
|||||||
Icons.Default.ArrowDownward,
|
Icons.Default.ArrowDownward,
|
||||||
null,
|
null,
|
||||||
Modifier
|
Modifier
|
||||||
.size(22.dp)
|
.requiredSize(22.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
|
LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
|
||||||
)
|
)
|
||||||
@@ -178,6 +163,7 @@ private fun DownloadIconButton(onClick: () -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: () -> Unit) {
|
private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: () -> Unit) {
|
||||||
DropdownIconButton(
|
DropdownIconButton(
|
||||||
|
downloadChapter?.mangaId to downloadChapter?.chapterIndex,
|
||||||
{
|
{
|
||||||
DropdownMenuItem(onClick = onClick) {
|
DropdownMenuItem(onClick = onClick) {
|
||||||
Text("Cancel")
|
Text("Cancel")
|
||||||
@@ -187,7 +173,7 @@ private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: ()
|
|||||||
when (downloadChapter?.state) {
|
when (downloadChapter?.state) {
|
||||||
null, DownloadState.Queued -> CircularProgressIndicator(
|
null, DownloadState.Queued -> CircularProgressIndicator(
|
||||||
Modifier
|
Modifier
|
||||||
.size(26.dp)
|
.requiredSize(26.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
||||||
2.dp
|
2.dp
|
||||||
@@ -196,7 +182,7 @@ private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: ()
|
|||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
downloadChapter.progress,
|
downloadChapter.progress,
|
||||||
Modifier
|
Modifier
|
||||||
.size(26.dp)
|
.requiredSize(26.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
||||||
2.dp
|
2.dp
|
||||||
@@ -205,14 +191,14 @@ private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: ()
|
|||||||
Icons.Default.ArrowDownward,
|
Icons.Default.ArrowDownward,
|
||||||
null,
|
null,
|
||||||
Modifier
|
Modifier
|
||||||
.size(22.dp)
|
.requiredSize(22.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
|
LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
Modifier
|
Modifier
|
||||||
.size(26.dp)
|
.requiredSize(26.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
LocalContentColor.current.copy(alpha = ContentAlpha.disabled),
|
||||||
2.dp
|
2.dp
|
||||||
@@ -223,7 +209,7 @@ private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: ()
|
|||||||
Icons.Default.Error,
|
Icons.Default.Error,
|
||||||
null,
|
null,
|
||||||
Modifier
|
Modifier
|
||||||
.size(22.dp)
|
.requiredSize(22.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
Color.Red
|
Color.Red
|
||||||
)
|
)
|
||||||
@@ -233,7 +219,7 @@ private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: ()
|
|||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
null,
|
null,
|
||||||
Modifier
|
Modifier
|
||||||
.size(22.dp)
|
.requiredSize(22.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
MaterialTheme.colors.surface
|
MaterialTheme.colors.surface
|
||||||
)
|
)
|
||||||
@@ -243,8 +229,9 @@ private fun DownloadingIconButton(downloadChapter: DownloadChapter?, onClick: ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DownloadedIconButton(onClick: () -> Unit) {
|
private fun DownloadedIconButton(chapter: Pair<Long, Int?>, onClick: () -> Unit) {
|
||||||
DropdownIconButton(
|
DropdownIconButton(
|
||||||
|
chapter,
|
||||||
{
|
{
|
||||||
DropdownMenuItem(onClick = onClick) {
|
DropdownMenuItem(onClick = onClick) {
|
||||||
Text("Delete")
|
Text("Delete")
|
||||||
@@ -256,47 +243,10 @@ private fun DownloadedIconButton(onClick: () -> Unit) {
|
|||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
null,
|
null,
|
||||||
Modifier
|
Modifier
|
||||||
.size(22.dp)
|
.requiredSize(22.dp)
|
||||||
.padding(2.dp),
|
.padding(2.dp),
|
||||||
MaterialTheme.colors.surface
|
MaterialTheme.colors.surface
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropdownIconButton(
|
|
||||||
dropdownItems: @Composable ColumnScope.() -> Unit,
|
|
||||||
content: @Composable BoxScope.() -> Unit
|
|
||||||
) {
|
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
|
||||||
var offset by remember { mutableStateOf(DpOffset(0.dp, 0.dp)) }
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = showMenu,
|
|
||||||
onDismissRequest = { showMenu = false },
|
|
||||||
offset = offset,
|
|
||||||
content = dropdownItems
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxHeight()
|
|
||||||
.size(48.dp)
|
|
||||||
.clickable(
|
|
||||||
remember { MutableInteractionSource() },
|
|
||||||
role = Role.Button,
|
|
||||||
indication = rememberRipple(bounded = false, radius = 24.dp)
|
|
||||||
) {
|
|
||||||
showMenu = true
|
|
||||||
}
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
forEachGesture {
|
|
||||||
awaitPointerEventScope {
|
|
||||||
awaitEventFirstDown().mouseEvent?.let {
|
|
||||||
offset = DpOffset(it.x.dp, it.y.dp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
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.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ca.gosyer.BuildConfig
|
||||||
import ca.gosyer.data.models.Source
|
import ca.gosyer.data.models.Source
|
||||||
import ca.gosyer.ui.base.components.KtorImage
|
import ca.gosyer.ui.base.components.KtorImage
|
||||||
import ca.gosyer.ui.base.components.Toolbar
|
import ca.gosyer.ui.base.components.Toolbar
|
||||||
@@ -41,9 +43,13 @@ import com.github.zsoltk.compose.savedinstancestate.BundleScope
|
|||||||
import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState
|
import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState
|
||||||
|
|
||||||
fun openSourcesMenu() {
|
fun openSourcesMenu() {
|
||||||
ThemedWindow(title = "TachideskJUI - Sources") {
|
ThemedWindow(BuildConfig.NAME) {
|
||||||
SourcesMenu {
|
CompositionLocalProvider(
|
||||||
openMangaMenu(it)
|
LocalSavedInstanceState provides Bundle()
|
||||||
|
) {
|
||||||
|
SourcesMenu {
|
||||||
|
openMangaMenu(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user