Initial downloads menu implementation

This commit is contained in:
Syer10
2021-06-15 16:54:13 -04:00
parent 28c8de99bc
commit 51e8718c29
14 changed files with 567 additions and 179 deletions

View File

@@ -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>()

View File

@@ -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())

View File

@@ -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,
) )

View File

@@ -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
)
}

View File

@@ -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()

View File

@@ -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
)
}

View File

@@ -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))
}
}
}

View 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
)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -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
)
} }
// }
} }
} }

View File

@@ -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()

View File

@@ -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
)
}

View File

@@ -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)
}
} }
} }
} }