Migrate image loading and display to Kamel

This commit is contained in:
Syer10
2021-10-24 17:44:20 -04:00
parent fdc72c5fbb
commit 1731e7c001
27 changed files with 221 additions and 203 deletions

View File

@@ -35,6 +35,7 @@ dependencies {
implementation("ca.gosyer:compose-router:0.24.2-jetbrains-2") implementation("ca.gosyer:compose-router:0.24.2-jetbrains-2")
implementation("ca.gosyer:accompanist-pager:0.18.1") implementation("ca.gosyer:accompanist-pager:0.18.1")
implementation("ca.gosyer:accompanist-flowlayout:0.18.1") implementation("ca.gosyer:accompanist-flowlayout:0.18.1")
implementation("com.alialbaali.kamel:kamel-image:0.3.0")
// UI (Swing) // UI (Swing)
implementation("com.github.weisj:darklaf-core:2.7.3") implementation("com.github.weisj:darklaf-core:2.7.3")

View File

@@ -14,6 +14,7 @@ import ca.gosyer.data.library.LibraryPreferences
import ca.gosyer.data.reader.ReaderPreferences import ca.gosyer.data.reader.ReaderPreferences
import ca.gosyer.data.server.Http import ca.gosyer.data.server.Http
import ca.gosyer.data.server.HttpProvider import ca.gosyer.data.server.HttpProvider
import ca.gosyer.data.server.KamelConfigProvider
import ca.gosyer.data.server.ServerHostPreferences import ca.gosyer.data.server.ServerHostPreferences
import ca.gosyer.data.server.ServerPreferences import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.ServerService import ca.gosyer.data.server.ServerService
@@ -28,6 +29,7 @@ import ca.gosyer.data.server.interactions.SourceInteractionHandler
import ca.gosyer.data.translation.ResourceProvider import ca.gosyer.data.translation.ResourceProvider
import ca.gosyer.data.translation.XmlResourceBundle import ca.gosyer.data.translation.XmlResourceBundle
import ca.gosyer.data.ui.UiPreferences import ca.gosyer.data.ui.UiPreferences
import io.kamel.core.config.KamelConfig
import toothpick.ktp.binding.bind import toothpick.ktp.binding.bind
import toothpick.ktp.binding.module import toothpick.ktp.binding.module
@@ -66,6 +68,10 @@ val DataModule = module {
.toProvider(HttpProvider::class) .toProvider(HttpProvider::class)
.providesSingleton() .providesSingleton()
bind<KamelConfig>()
.toProvider(KamelConfigProvider::class)
.providesSingleton()
bind<XmlResourceBundle>() bind<XmlResourceBundle>()
.toProvider(ResourceProvider::class) .toProvider(ResourceProvider::class)
.providesSingleton() .providesSingleton()

View File

@@ -21,6 +21,4 @@ data class Extension(
val hasUpdate: Boolean, val hasUpdate: Boolean,
val obsolete: Boolean, val obsolete: Boolean,
val isNsfw: Boolean val isNsfw: Boolean
) { )
fun iconUrl(serverUrl: String) = serverUrl + iconUrl
}

View File

@@ -27,9 +27,7 @@ data class Manga(
val meta: MangaMeta, val meta: MangaMeta,
val realUrl: String?, val realUrl: String?,
val inLibraryAt: Long val inLibraryAt: Long
) { )
fun cover(serverUrl: String) = thumbnailUrl?.let { serverUrl + it }
}
@Serializable @Serializable
data class MangaMeta( data class MangaMeta(

View File

@@ -19,7 +19,6 @@ data class Source(
val isNsfw: Boolean, val isNsfw: Boolean,
val displayName: String val displayName: String
) { ) {
fun iconUrl(serverUrl: String) = serverUrl + iconUrl
companion object { companion object {
const val LOCAL_SOURCE_LANG = "localsourcelang" const val LOCAL_SOURCE_LANG = "localsourcelang"
} }

View File

@@ -0,0 +1,78 @@
/*
* 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.data.server
import ca.gosyer.data.models.Extension
import ca.gosyer.data.models.Manga
import ca.gosyer.data.models.Source
import ca.gosyer.ui.base.prefs.asStateIn
import io.kamel.core.config.DefaultCacheSize
import io.kamel.core.config.KamelConfig
import io.kamel.core.config.fileFetcher
import io.kamel.core.config.httpFetcher
import io.kamel.core.config.stringMapper
import io.kamel.core.config.uriMapper
import io.kamel.core.config.urlMapper
import io.kamel.core.mapper.Mapper
import io.kamel.image.config.imageBitmapDecoder
import io.kamel.image.config.resourcesFetcher
import io.ktor.http.Url
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Provider
class KamelConfigProvider @Inject constructor(
private val http: Http,
serverPreferences: ServerPreferences
) : Provider<KamelConfig> {
@OptIn(DelicateCoroutinesApi::class)
val serverUrl = serverPreferences.serverUrl().asStateIn(GlobalScope)
override fun get(): KamelConfig {
return KamelConfig {
// Default config
imageBitmapCacheSize = DefaultCacheSize
imageVectorCacheSize = DefaultCacheSize
imageBitmapDecoder()
stringMapper()
urlMapper()
uriMapper()
fileFetcher()
// JUI config
httpFetcher(http.engine) {
install(http)
}
resourcesFetcher()
val serverUrl = serverUrl.asStateFlow()
mapper(MangaCoverMapper(serverUrl))
mapper(ExtensionIconMapper(serverUrl))
mapper(SourceIconMapper(serverUrl))
}
}
class MangaCoverMapper(private val serverUrlStateFlow: StateFlow<String>) : Mapper<Manga, Url> {
override fun map(input: Manga): Url {
return Url(serverUrlStateFlow.value + input.thumbnailUrl)
}
}
class ExtensionIconMapper(private val serverUrlStateFlow: StateFlow<String>) : Mapper<Extension, Url> {
override fun map(input: Extension): Url {
return Url(serverUrlStateFlow.value + input.iconUrl)
}
}
class SourceIconMapper(private val serverUrlStateFlow: StateFlow<String>) : Mapper<Source, Url> {
override fun map(input: Source): Url {
return Url(serverUrlStateFlow.value + input.iconUrl)
}
}
}

View File

@@ -40,6 +40,8 @@ import ca.gosyer.ui.base.components.setIcon
import ca.gosyer.ui.base.resources.LocalResources import ca.gosyer.ui.base.resources.LocalResources
import ca.gosyer.ui.base.theme.AppTheme import ca.gosyer.ui.base.theme.AppTheme
import ca.gosyer.util.lang.launchApplication import ca.gosyer.util.lang.launchApplication
import io.kamel.core.config.KamelConfig
import io.kamel.image.config.LocalKamelConfig
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@@ -69,6 +71,7 @@ fun WindowDialog(
} }
val resources = remember { AppScope.getInstance<XmlResourceBundle>() } val resources = remember { AppScope.getInstance<XmlResourceBundle>() }
val kamelConfig = remember { AppScope.getInstance<KamelConfig>() }
val windowState = rememberWindowState(size = size, position = WindowPosition(Alignment.Center)) val windowState = rememberWindowState(size = size, position = WindowPosition(Alignment.Center))
Window( Window(
@@ -95,7 +98,8 @@ fun WindowDialog(
) { ) {
setIcon() setIcon()
CompositionLocalProvider( CompositionLocalProvider(
LocalResources provides resources LocalResources provides resources,
LocalKamelConfig provides kamelConfig
) { ) {
AppTheme { AppTheme {
Surface { Surface {
@@ -144,6 +148,7 @@ fun WindowDialog(
} }
val resources = remember { AppScope.getInstance<XmlResourceBundle>() } val resources = remember { AppScope.getInstance<XmlResourceBundle>() }
val kamelConfig = remember { AppScope.getInstance<KamelConfig>() }
val windowState = rememberWindowState(size = size, position = WindowPosition.Aligned(Alignment.Center)) val windowState = rememberWindowState(size = size, position = WindowPosition.Aligned(Alignment.Center))
Window( Window(
@@ -162,7 +167,8 @@ fun WindowDialog(
) { ) {
setIcon() setIcon()
CompositionLocalProvider( CompositionLocalProvider(
LocalResources provides resources LocalResources provides resources,
LocalKamelConfig provides kamelConfig
) { ) {
AppTheme { AppTheme {
Surface { Surface {

View File

@@ -0,0 +1,53 @@
/*
* 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.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import io.kamel.core.Resource
import io.kamel.image.KamelImage as BaseKamelImage
@Composable
fun KamelImage(
resource: Resource<Painter>,
contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
onLoading: @Composable (Float) -> Unit = {
LoadingScreen(progress = it, modifier = modifier then Modifier.fillMaxSize())
},
onFailure: @Composable (Throwable) -> Unit = {
ErrorScreen(it.localizedMessage, modifier = modifier then Modifier.fillMaxSize())
},
crossfade: Boolean = true,
animationSpec: FiniteAnimationSpec<Float> = tween()
) {
BaseKamelImage(
resource,
contentDescription,
modifier,
alignment,
contentScale,
alpha,
colorFilter,
onLoading,
onFailure,
crossfade,
animationSpec
)
}

View File

@@ -1,100 +0,0 @@
/*
* 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.animation.Crossfade
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import ca.gosyer.common.di.AppScope
import ca.gosyer.data.server.Http
import ca.gosyer.util.compose.imageFromUrl
import ca.gosyer.util.system.kLogger
import io.ktor.client.features.onDownload
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
private val logger = kLogger {}
private val semaphore = Semaphore(5)
@OptIn(DelicateCoroutinesApi::class)
@Composable
fun KtorImage(
imageUrl: String,
modifier: Modifier = Modifier.fillMaxSize(),
loadingModifier: Modifier = modifier,
contentDescription: String? = null,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = FilterQuality.Medium,
client: Http = remember { AppScope.getInstance() }
) {
BoxWithConstraints(modifier) {
val drawable = remember { mutableStateOf<ImageBitmap?>(null) }
val loading = remember { mutableStateOf(true) }
val progress = remember { mutableStateOf(0.0F) }
val error = remember { mutableStateOf<String?>(null) }
DisposableEffect(imageUrl) {
val handler = CoroutineExceptionHandler { _, throwable ->
logger.error(throwable) { "Error loading image $imageUrl" }
loading.value = false
error.value = throwable.message
}
val job = GlobalScope.launch(handler) {
if (drawable.value == null) {
semaphore.withPermit {
drawable.value = imageFromUrl(client, imageUrl) {
onDownload { bytesSentTotal, contentLength ->
progress.value = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F)
}
}
}
}
loading.value = false
}
onDispose {
job.cancel()
drawable.value = null
}
}
Crossfade(drawable.value to loading.value) { (value, loading) ->
if (value != null) {
Image(
value,
modifier = Modifier.fillMaxSize(),
contentDescription = contentDescription,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
filterQuality = filterQuality
)
} else {
LoadingScreen(loading, loadingModifier, progress.value, error.value)
}
}
}
}

View File

@@ -23,17 +23,19 @@ import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
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 io.kamel.core.Resource
@Composable @Composable
fun MangaGridItem( fun MangaGridItem(
title: String, title: String,
cover: String?, cover: Resource<Painter>,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
) { ) {
val fontStyle = LocalTextStyle.current.merge( val fontStyle = LocalTextStyle.current.merge(
@@ -50,9 +52,7 @@ fun MangaGridItem(
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) { ) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
if (cover != null) { KamelImage(cover, title, contentScale = ContentScale.Crop)
KtorImage(cover, contentScale = ContentScale.Crop)
}
Box(modifier = Modifier.fillMaxSize().then(shadowGradient)) Box(modifier = Modifier.fillMaxSize().then(shadowGradient))
Text( Text(
text = title, text = title,

View File

@@ -15,9 +15,11 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import io.kamel.core.Resource
@Composable @Composable
fun MangaListItem( fun MangaListItem(
@@ -35,18 +37,15 @@ fun MangaListItem(
@Composable @Composable
fun MangaListItemImage( fun MangaListItemImage(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageUrl: String? cover: Resource<Painter>,
contentDescription: String
) { ) {
if (imageUrl != null) { KamelImage(
KtorImage( cover,
imageUrl = imageUrl, contentDescription = contentDescription,
contentDescription = null, modifier = modifier,
modifier = modifier, contentScale = ContentScale.Crop
contentScale = ContentScale.Crop )
)
} else {
ErrorScreen(modifier = modifier)
}
} }
@Composable @Composable

View File

@@ -39,6 +39,7 @@ 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
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality
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.text.withStyle import androidx.compose.ui.text.withStyle
@@ -50,7 +51,7 @@ import ca.gosyer.build.BuildConfig
import ca.gosyer.data.models.Extension import ca.gosyer.data.models.Extension
import ca.gosyer.ui.base.WindowDialog import ca.gosyer.ui.base.WindowDialog
import ca.gosyer.ui.base.components.ActionIcon import ca.gosyer.ui.base.components.ActionIcon
import ca.gosyer.ui.base.components.KtorImage import ca.gosyer.ui.base.components.KamelImage
import ca.gosyer.ui.base.components.LoadingScreen import ca.gosyer.ui.base.components.LoadingScreen
import ca.gosyer.ui.base.components.Toolbar import ca.gosyer.ui.base.components.Toolbar
import ca.gosyer.ui.base.resources.stringResource import ca.gosyer.ui.base.resources.stringResource
@@ -58,6 +59,7 @@ import ca.gosyer.ui.base.vm.viewModel
import ca.gosyer.util.compose.ThemedWindow import ca.gosyer.util.compose.ThemedWindow
import ca.gosyer.util.compose.persistentLazyListState import ca.gosyer.util.compose.persistentLazyListState
import ca.gosyer.util.lang.launchApplication import ca.gosyer.util.lang.launchApplication
import io.kamel.image.lazyPainterResource
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -80,7 +82,6 @@ fun ExtensionsMenu() {
val vm = viewModel<ExtensionsMenuViewModel>() val vm = viewModel<ExtensionsMenuViewModel>()
val extensions by vm.extensions.collectAsState() val extensions by vm.extensions.collectAsState()
val isLoading by vm.isLoading.collectAsState() val isLoading by vm.isLoading.collectAsState()
val serverUrl by vm.serverUrl.collectAsState()
val search by vm.searchQuery.collectAsState() val search by vm.searchQuery.collectAsState()
if (isLoading) { if (isLoading) {
@@ -119,7 +120,6 @@ fun ExtensionsMenu() {
items(items) { extension -> items(items) { extension ->
ExtensionItem( ExtensionItem(
extension, extension,
serverUrl,
onInstallClicked = vm::install, onInstallClicked = vm::install,
onUpdateClicked = vm::update, onUpdateClicked = vm::update,
onUninstallClicked = vm::uninstall onUninstallClicked = vm::uninstall
@@ -167,7 +167,6 @@ fun ExtensionsToolbar(
@Composable @Composable
fun ExtensionItem( fun ExtensionItem(
extension: Extension, extension: Extension,
serverUrl: String,
onInstallClicked: (Extension) -> Unit, onInstallClicked: (Extension) -> Unit,
onUpdateClicked: (Extension) -> Unit, onUpdateClicked: (Extension) -> Unit,
onUninstallClicked: (Extension) -> Unit onUninstallClicked: (Extension) -> Unit
@@ -175,7 +174,7 @@ fun ExtensionItem(
Box(modifier = Modifier.fillMaxWidth().padding(end = 12.dp).height(50.dp).background(MaterialTheme.colors.background)) { Box(modifier = Modifier.fillMaxWidth().padding(end = 12.dp).height(50.dp).background(MaterialTheme.colors.background)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(4.dp)) Spacer(Modifier.width(4.dp))
KtorImage(extension.iconUrl(serverUrl), Modifier.size(50.dp)) KamelImage(lazyPainterResource(extension, filterQuality = FilterQuality.Medium), extension.name, Modifier.size(50.dp))
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Column { Column {
val title = buildAnnotatedString { val title = buildAnnotatedString {

View File

@@ -8,7 +8,6 @@ package ca.gosyer.ui.extensions
import ca.gosyer.data.extension.ExtensionPreferences import ca.gosyer.data.extension.ExtensionPreferences
import ca.gosyer.data.models.Extension import ca.gosyer.data.models.Extension
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.interactions.ExtensionInteractionHandler import ca.gosyer.data.server.interactions.ExtensionInteractionHandler
import ca.gosyer.data.translation.XmlResourceBundle import ca.gosyer.data.translation.XmlResourceBundle
import ca.gosyer.ui.base.vm.ViewModel import ca.gosyer.ui.base.vm.ViewModel
@@ -26,10 +25,8 @@ import javax.inject.Inject
class ExtensionsMenuViewModel @Inject constructor( class ExtensionsMenuViewModel @Inject constructor(
private val extensionHandler: ExtensionInteractionHandler, private val extensionHandler: ExtensionInteractionHandler,
private val resources: XmlResourceBundle, private val resources: XmlResourceBundle,
serverPreferences: ServerPreferences,
extensionPreferences: ExtensionPreferences extensionPreferences: ExtensionPreferences
) : ViewModel() { ) : ViewModel() {
val serverUrl = serverPreferences.serverUrl().stateIn(scope)
private val _enabledLangs = extensionPreferences.languages().asStateFlow() private val _enabledLangs = extensionPreferences.languages().asStateFlow()
val enabledLangs = _enabledLangs.asStateFlow() val enabledLangs = _enabledLangs.asStateFlow()

View File

@@ -73,7 +73,6 @@ fun LibraryScreen(bundle: Bundle, onClickManga: (Long) -> Unit = { openMangaMenu
val displayMode by vm.displayMode.collectAsState() val displayMode by vm.displayMode.collectAsState()
val isLoading by vm.isLoading.collectAsState() val isLoading by vm.isLoading.collectAsState()
val error by vm.error.collectAsState() val error by vm.error.collectAsState()
val serverUrl by vm.serverUrl.collectAsState()
val query by vm.query.collectAsState() val query by vm.query.collectAsState()
// val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden) // val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
@@ -116,7 +115,6 @@ fun LibraryScreen(bundle: Bundle, onClickManga: (Long) -> Unit = { openMangaMenu
categories = categories, categories = categories,
displayMode = displayMode, displayMode = displayMode,
selectedPage = selectedCategoryIndex, selectedPage = selectedCategoryIndex,
serverUrl = serverUrl,
getLibraryForPage = { vm.getLibraryForCategoryIndex(it).collectAsState() }, getLibraryForPage = { vm.getLibraryForCategoryIndex(it).collectAsState() },
onPageChanged = vm::setSelectedPage, onPageChanged = vm::setSelectedPage,
onClickManga = onClickManga, onClickManga = onClickManga,
@@ -163,7 +161,6 @@ private fun LibraryPager(
categories: List<Category>, categories: List<Category>,
displayMode: DisplayMode, displayMode: DisplayMode,
selectedPage: Int, selectedPage: Int,
serverUrl: String,
getLibraryForPage: @Composable (Int) -> State<List<Manga>>, getLibraryForPage: @Composable (Int) -> State<List<Manga>>,
onPageChanged: (Int) -> Unit, onPageChanged: (Int) -> Unit,
onClickManga: (Long) -> Unit, onClickManga: (Long) -> Unit,
@@ -187,7 +184,6 @@ private fun LibraryPager(
when (displayMode) { when (displayMode) {
DisplayMode.CompactGrid -> LibraryMangaCompactGrid( DisplayMode.CompactGrid -> LibraryMangaCompactGrid(
library = library, library = library,
serverUrl = serverUrl,
onClickManga = onClickManga, onClickManga = onClickManga,
onRemoveMangaClicked = onRemoveMangaClicked onRemoveMangaClicked = onRemoveMangaClicked
) )

View File

@@ -9,7 +9,6 @@ package ca.gosyer.ui.library
import ca.gosyer.data.library.LibraryPreferences import ca.gosyer.data.library.LibraryPreferences
import ca.gosyer.data.models.Category import ca.gosyer.data.models.Category
import ca.gosyer.data.models.Manga import ca.gosyer.data.models.Manga
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.interactions.CategoryInteractionHandler import ca.gosyer.data.server.interactions.CategoryInteractionHandler
import ca.gosyer.data.server.interactions.LibraryInteractionHandler import ca.gosyer.data.server.interactions.LibraryInteractionHandler
import ca.gosyer.ui.base.vm.ViewModel import ca.gosyer.ui.base.vm.ViewModel
@@ -73,11 +72,8 @@ class LibraryScreenViewModel @Inject constructor(
private val bundle: Bundle, private val bundle: Bundle,
private val categoryHandler: CategoryInteractionHandler, private val categoryHandler: CategoryInteractionHandler,
private val libraryHandler: LibraryInteractionHandler, private val libraryHandler: LibraryInteractionHandler,
libraryPreferences: LibraryPreferences, libraryPreferences: LibraryPreferences
serverPreferences: ServerPreferences,
) : ViewModel() { ) : ViewModel() {
val serverUrl = serverPreferences.serverUrl().stateIn(scope)
private val library = Library(MutableStateFlow(emptyList()), mutableMapOf()) private val library = Library(MutableStateFlow(emptyList()), mutableMapOf())
val categories = library.categories.asStateFlow() val categories = library.categories.asStateFlow()

View File

@@ -19,7 +19,6 @@ import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -27,19 +26,20 @@ import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
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.data.models.Manga import ca.gosyer.data.models.Manga
import ca.gosyer.ui.base.components.KtorImage import ca.gosyer.ui.base.components.KamelImage
import ca.gosyer.ui.base.components.contextMenuClickable import ca.gosyer.ui.base.components.contextMenuClickable
import io.kamel.image.lazyPainterResource
@Composable @Composable
fun LibraryMangaCompactGrid( fun LibraryMangaCompactGrid(
library: List<Manga>, library: List<Manga>,
serverUrl: String,
onClickManga: (Long) -> Unit = {}, onClickManga: (Long) -> Unit = {},
onRemoveMangaClicked: (Long) -> Unit = {} onRemoveMangaClicked: (Long) -> Unit = {}
) { ) {
@@ -52,7 +52,6 @@ fun LibraryMangaCompactGrid(
manga = manga, manga = manga,
unread = null, // TODO unread = null, // TODO
downloaded = null, // TODO downloaded = null, // TODO
serverUrl = serverUrl,
onClick = { onClickManga(manga.id) } onClick = { onClickManga(manga.id) }
) { ) {
listOf( listOf(
@@ -68,11 +67,10 @@ private fun LibraryMangaCompactGridItem(
manga: Manga, manga: Manga,
unread: Int?, unread: Int?,
downloaded: Int?, downloaded: Int?,
serverUrl: String,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
contextMenuItems: () -> List<ContextMenuItem> = { emptyList() } contextMenuItems: () -> List<ContextMenuItem> = { emptyList() }
) { ) {
val cover = remember(manga.id, serverUrl) { manga.cover(serverUrl) } val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge( val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp) TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
) )
@@ -87,9 +85,7 @@ private fun LibraryMangaCompactGridItem(
items = contextMenuItems items = contextMenuItems
) )
) { ) {
if (cover != null) { KamelImage(cover, manga.title, contentScale = ContentScale.Crop)
KtorImage(cover, contentScale = ContentScale.Crop)
}
Box(modifier = Modifier.fillMaxSize().then(shadowGradient)) Box(modifier = Modifier.fillMaxSize().then(shadowGradient))
Text( Text(
text = manga.title, text = manga.title,

View File

@@ -48,6 +48,8 @@ import com.github.weisj.darklaf.theme.IntelliJTheme
import com.github.zsoltk.compose.backpress.BackPressHandler import com.github.zsoltk.compose.backpress.BackPressHandler
import com.github.zsoltk.compose.backpress.LocalBackPressHandler import com.github.zsoltk.compose.backpress.LocalBackPressHandler
import com.github.zsoltk.compose.savedinstancestate.Bundle import com.github.zsoltk.compose.savedinstancestate.Bundle
import io.kamel.core.config.KamelConfig
import io.kamel.image.config.LocalKamelConfig
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -94,6 +96,7 @@ suspend fun main() {
} }
val resources = scope.getInstance<XmlResourceBundle>() val resources = scope.getInstance<XmlResourceBundle>()
val kamelConfig = scope.getInstance<KamelConfig>()
// Set the Compose constants before any // Set the Compose constants before any
// Swing functions are called // Swing functions are called
@@ -181,7 +184,8 @@ suspend fun main() {
CompositionLocalProvider( CompositionLocalProvider(
LocalComposeWindow provides window, LocalComposeWindow provides window,
LocalBackPressHandler provides backPressHandler, LocalBackPressHandler provides backPressHandler,
LocalResources provides resources LocalResources provides resources,
LocalKamelConfig provides kamelConfig
) { ) {
Crossfade(serverService.initialized.collectAsState().value) { initialized -> Crossfade(serverService.initialized.collectAsState().value) { initialized ->
when (initialized) { when (initialized) {

View File

@@ -40,6 +40,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -50,7 +51,7 @@ import ca.gosyer.data.models.Manga
import ca.gosyer.ui.base.WindowDialog import ca.gosyer.ui.base.WindowDialog
import ca.gosyer.ui.base.components.ActionIcon import ca.gosyer.ui.base.components.ActionIcon
import ca.gosyer.ui.base.components.ErrorScreen import ca.gosyer.ui.base.components.ErrorScreen
import ca.gosyer.ui.base.components.KtorImage import ca.gosyer.ui.base.components.KamelImage
import ca.gosyer.ui.base.components.LoadingScreen import ca.gosyer.ui.base.components.LoadingScreen
import ca.gosyer.ui.base.components.LocalMenuController import ca.gosyer.ui.base.components.LocalMenuController
import ca.gosyer.ui.base.components.MenuController import ca.gosyer.ui.base.components.MenuController
@@ -61,6 +62,7 @@ import ca.gosyer.ui.reader.openReaderMenu
import ca.gosyer.util.compose.ThemedWindow import ca.gosyer.util.compose.ThemedWindow
import ca.gosyer.util.lang.launchApplication import ca.gosyer.util.lang.launchApplication
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import io.kamel.image.lazyPainterResource
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@@ -84,7 +86,6 @@ fun MangaMenu(mangaId: Long, menuController: MenuController? = LocalMenuControll
val manga by vm.manga.collectAsState() val manga by vm.manga.collectAsState()
val chapters by vm.chapters.collectAsState() val chapters by vm.chapters.collectAsState()
val isLoading by vm.isLoading.collectAsState() val isLoading by vm.isLoading.collectAsState()
val serverUrl by vm.serverUrl.collectAsState()
val dateTimeFormatter by vm.dateTimeFormatter.collectAsState() val dateTimeFormatter by vm.dateTimeFormatter.collectAsState()
val categoriesExist by vm.categoriesExist.collectAsState() val categoriesExist by vm.categoriesExist.collectAsState()
@@ -128,7 +129,7 @@ fun MangaMenu(mangaId: Long, menuController: MenuController? = LocalMenuControll
val state = rememberLazyListState() val state = rememberLazyListState()
LazyColumn(state = state) { LazyColumn(state = state) {
item { item {
MangaItem(manga, serverUrl) MangaItem(manga)
} }
if (chapters.isNotEmpty()) { if (chapters.isNotEmpty()) {
items(chapters) { chapter -> items(chapters) { chapter ->
@@ -171,17 +172,17 @@ fun MangaMenu(mangaId: Long, menuController: MenuController? = LocalMenuControll
} }
@Composable @Composable
fun MangaItem(manga: Manga, serverUrl: String) { fun MangaItem(manga: Manga) {
BoxWithConstraints(Modifier.padding(8.dp)) { BoxWithConstraints(Modifier.padding(8.dp)) {
if (maxWidth > 600.dp) { if (maxWidth > 600.dp) {
Row { Row {
Cover(manga, serverUrl, Modifier.width(300.dp)) Cover(manga, Modifier.width(300.dp))
Spacer(Modifier.width(16.dp)) Spacer(Modifier.width(16.dp))
MangaInfo(manga) MangaInfo(manga)
} }
} else { } else {
Column { Column {
Cover(manga, serverUrl, Modifier.align(Alignment.CenterHorizontally)) Cover(manga, Modifier.align(Alignment.CenterHorizontally))
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
MangaInfo(manga) MangaInfo(manga)
} }
@@ -190,17 +191,17 @@ fun MangaItem(manga: Manga, serverUrl: String) {
} }
@Composable @Composable
private fun Cover(manga: Manga, serverUrl: String, modifier: Modifier = Modifier) { private fun Cover(manga: Manga, modifier: Modifier = Modifier) {
Surface( Surface(
modifier = modifier then Modifier modifier = modifier then Modifier
.padding(4.dp), .padding(4.dp),
shape = RoundedCornerShape(4.dp) shape = RoundedCornerShape(4.dp)
) { ) {
Box(modifier = Modifier.fillMaxSize()) { KamelImage(
manga.cover(serverUrl)?.let { lazyPainterResource(manga, filterQuality = FilterQuality.Medium),
KtorImage(it) manga.title,
} Modifier.fillMaxSize()
} )
} }
} }

View File

@@ -10,7 +10,6 @@ import ca.gosyer.data.download.DownloadService
import ca.gosyer.data.models.Category import ca.gosyer.data.models.Category
import ca.gosyer.data.models.Chapter import ca.gosyer.data.models.Chapter
import ca.gosyer.data.models.Manga import ca.gosyer.data.models.Manga
import ca.gosyer.data.server.ServerPreferences
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.LibraryInteractionHandler import ca.gosyer.data.server.interactions.LibraryInteractionHandler
@@ -41,11 +40,8 @@ class MangaMenuViewModel @Inject constructor(
private val categoryHandler: CategoryInteractionHandler, private val categoryHandler: CategoryInteractionHandler,
private val libraryHandler: LibraryInteractionHandler, private val libraryHandler: LibraryInteractionHandler,
private val downloadService: DownloadService, private val downloadService: DownloadService,
serverPreferences: ServerPreferences,
uiPreferences: UiPreferences, uiPreferences: UiPreferences,
) : ViewModel() { ) : ViewModel() {
val serverUrl = serverPreferences.serverUrl().stateIn(scope)
private val downloadingChapters = downloadService.registerWatch(params.mangaId) private val downloadingChapters = downloadService.registerWatch(params.mangaId)
private val _manga = MutableStateFlow<Manga?>(null) private val _manga = MutableStateFlow<Manga?>(null)

View File

@@ -66,6 +66,8 @@ import ca.gosyer.ui.reader.navigation.navigationClickable
import ca.gosyer.ui.reader.viewer.ContinuousReader import ca.gosyer.ui.reader.viewer.ContinuousReader
import ca.gosyer.ui.reader.viewer.PagerReader import ca.gosyer.ui.reader.viewer.PagerReader
import ca.gosyer.util.lang.launchApplication import ca.gosyer.util.lang.launchApplication
import io.kamel.core.config.KamelConfig
import io.kamel.image.config.LocalKamelConfig
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
@@ -79,6 +81,7 @@ fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
) = windowSettings.get().get() ) = windowSettings.get().get()
val resources = AppScope.getInstance<XmlResourceBundle>() val resources = AppScope.getInstance<XmlResourceBundle>()
val kamelConfig = AppScope.getInstance<KamelConfig>()
launchApplication { launchApplication {
var shortcuts by remember { var shortcuts by remember {
@@ -110,7 +113,8 @@ fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
setIcon() setIcon()
CompositionLocalProvider( CompositionLocalProvider(
LocalComposeWindow provides window, LocalComposeWindow provides window,
LocalResources provides resources LocalResources provides resources,
LocalKamelConfig provides kamelConfig
) { ) {
AppTheme { AppTheme {
ReaderMenu(chapterIndex, mangaId) { shortcuts = it } ReaderMenu(chapterIndex, mangaId) { shortcuts = it }

View File

@@ -35,11 +35,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ca.gosyer.build.BuildConfig import ca.gosyer.build.BuildConfig
import ca.gosyer.ui.base.components.ActionIcon import ca.gosyer.ui.base.components.ActionIcon
import ca.gosyer.ui.base.components.KtorImage import ca.gosyer.ui.base.components.KamelImage
import ca.gosyer.ui.base.components.Toolbar import ca.gosyer.ui.base.components.Toolbar
import ca.gosyer.ui.base.components.combinedMouseClickable import ca.gosyer.ui.base.components.combinedMouseClickable
import ca.gosyer.ui.base.resources.stringResource import ca.gosyer.ui.base.resources.stringResource
@@ -54,6 +55,7 @@ import ca.gosyer.util.lang.launchApplication
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
import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState import com.github.zsoltk.compose.savedinstancestate.LocalSavedInstanceState
import io.kamel.image.lazyPainterResource
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -91,7 +93,6 @@ fun SourcesMenu(bundle: Bundle, onSourceSettingsClick: (Long) -> Unit, onMangaCl
val selectedSourceTab by vm.selectedSourceTab.collectAsState() val selectedSourceTab by vm.selectedSourceTab.collectAsState()
val sourceSearchEnabled by vm.sourceSearchEnabled.collectAsState() val sourceSearchEnabled by vm.sourceSearchEnabled.collectAsState()
val sourceSearchQuery by vm.sourceSearchQuery.collectAsState() val sourceSearchQuery by vm.sourceSearchQuery.collectAsState()
val serverUrl by vm.serverUrl.collectAsState()
Column { Column {
Toolbar( Toolbar(
selectedSourceTab?.name ?: stringResource("location_sources"), selectedSourceTab?.name ?: stringResource("location_sources"),
@@ -165,7 +166,13 @@ fun SourcesMenu(bundle: Bundle, onSourceSettingsClick: (Long) -> Unit, onMangaCl
.requiredSize(50.dp) .requiredSize(50.dp)
.align(Alignment.Center) .align(Alignment.Center)
if (source != null) { if (source != null) {
KtorImage(source.iconUrl(serverUrl), modifier = modifier) Box(Modifier.align(Alignment.Center)) {
KamelImage(
lazyPainterResource(source, filterQuality = FilterQuality.Medium),
source.displayName,
modifier
)
}
} else { } else {
Icon(Icons.Rounded.Home, stringResource("sources_home"), modifier = modifier) Icon(Icons.Rounded.Home, stringResource("sources_home"), modifier = modifier)
} }
@@ -180,7 +187,7 @@ fun SourcesMenu(bundle: Bundle, onSourceSettingsClick: (Long) -> Unit, onMangaCl
if (selectedSource != null) { if (selectedSource != null) {
SourceScreen(it, selectedSource, onMangaClick, vm::enableSearch, vm::setSearch) SourceScreen(it, selectedSource, onMangaClick, vm::enableSearch, vm::setSearch)
} else { } else {
SourceHomeScreen(isLoading, sources, serverUrl, vm::addTab) SourceHomeScreen(isLoading, sources, vm::addTab)
} }
} }
} }

View File

@@ -8,7 +8,6 @@ package ca.gosyer.ui.sources
import ca.gosyer.data.catalog.CatalogPreferences import ca.gosyer.data.catalog.CatalogPreferences
import ca.gosyer.data.models.Source import ca.gosyer.data.models.Source
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.interactions.SourceInteractionHandler import ca.gosyer.data.server.interactions.SourceInteractionHandler
import ca.gosyer.ui.base.vm.ViewModel import ca.gosyer.ui.base.vm.ViewModel
import ca.gosyer.util.lang.throwIfCancellation import ca.gosyer.util.lang.throwIfCancellation
@@ -25,11 +24,8 @@ import javax.inject.Inject
class SourcesMenuViewModel @Inject constructor( class SourcesMenuViewModel @Inject constructor(
private val bundle: Bundle, private val bundle: Bundle,
private val sourceHandler: SourceInteractionHandler, private val sourceHandler: SourceInteractionHandler,
serverPreferences: ServerPreferences,
catalogPreferences: CatalogPreferences catalogPreferences: CatalogPreferences
) : ViewModel() { ) : ViewModel() {
val serverUrl = serverPreferences.serverUrl().stateIn(scope)
private val _languages = catalogPreferences.languages().asStateFlow() private val _languages = catalogPreferences.languages().asStateFlow()
val languages = _languages.asStateFlow() val languages = _languages.asStateFlow()

View File

@@ -33,17 +33,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.KamelImage
import ca.gosyer.ui.base.components.LoadingScreen import ca.gosyer.ui.base.components.LoadingScreen
import io.kamel.image.lazyPainterResource
@Composable @Composable
fun SourceHomeScreen( fun SourceHomeScreen(
isLoading: Boolean, isLoading: Boolean,
sources: List<Source>, sources: List<Source>,
serverUrl: String,
onSourceClicked: (Source) -> Unit onSourceClicked: (Source) -> Unit
) { ) {
if (sources.isEmpty()) { if (sources.isEmpty()) {
@@ -51,7 +52,7 @@ fun SourceHomeScreen(
} else { } else {
Box(Modifier.fillMaxSize(), Alignment.TopCenter) { Box(Modifier.fillMaxSize(), Alignment.TopCenter) {
val state = rememberLazyListState() val state = rememberLazyListState()
SourceCategory(sources, serverUrl, onSourceClicked, state) SourceCategory(sources, onSourceClicked, state)
/*val sourcesByLang = sources.groupBy { it.lang.toLowerCase() }.toList() /*val sourcesByLang = sources.groupBy { it.lang.toLowerCase() }.toList()
LazyColumn(state = state) { LazyColumn(state = state) {
items(sourcesByLang) { (lang, sources) -> items(sourcesByLang) { (lang, sources) ->
@@ -75,7 +76,6 @@ fun SourceHomeScreen(
@Composable @Composable
fun SourceCategory( fun SourceCategory(
sources: List<Source>, sources: List<Source>,
serverUrl: String,
onSourceClicked: (Source) -> Unit, onSourceClicked: (Source) -> Unit,
state: LazyListState state: LazyListState
) { ) {
@@ -83,7 +83,6 @@ fun SourceCategory(
items(sources) { source -> items(sources) { source ->
SourceItem( SourceItem(
source, source,
serverUrl,
onSourceClicked = onSourceClicked onSourceClicked = onSourceClicked
) )
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
@@ -94,7 +93,6 @@ fun SourceCategory(
@Composable @Composable
fun SourceItem( fun SourceItem(
source: Source, source: Source,
serverUrl: String,
onSourceClicked: (Source) -> Unit onSourceClicked: (Source) -> Unit
) { ) {
TooltipArea( TooltipArea(
@@ -117,7 +115,7 @@ fun SourceItem(
}, },
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
KtorImage(source.iconUrl(serverUrl), Modifier.size(96.dp)) KamelImage(lazyPainterResource(source, filterQuality = FilterQuality.Medium), source.displayName, Modifier.size(96.dp))
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
"${source.name} (${source.lang.uppercase()})", "${source.name} (${source.lang.uppercase()})",

View File

@@ -21,6 +21,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ca.gosyer.data.models.Manga import ca.gosyer.data.models.Manga
import ca.gosyer.data.models.Source import ca.gosyer.data.models.Source
@@ -30,6 +31,7 @@ import ca.gosyer.ui.base.resources.stringResource
import ca.gosyer.ui.base.vm.viewModel import ca.gosyer.ui.base.vm.viewModel
import ca.gosyer.util.compose.persistentLazyListState import ca.gosyer.util.compose.persistentLazyListState
import com.github.zsoltk.compose.savedinstancestate.Bundle import com.github.zsoltk.compose.savedinstancestate.Bundle
import io.kamel.image.lazyPainterResource
@Composable @Composable
fun SourceScreen( fun SourceScreen(
@@ -46,7 +48,6 @@ fun SourceScreen(
val hasNextPage by vm.hasNextPage.collectAsState() val hasNextPage by vm.hasNextPage.collectAsState()
val loading by vm.loading.collectAsState() val loading by vm.loading.collectAsState()
val isLatest by vm.isLatest.collectAsState() val isLatest by vm.isLatest.collectAsState()
val serverUrl by vm.serverUrl.collectAsState()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
setSearch(vm::search) setSearch(vm::search)
@@ -67,7 +68,6 @@ fun SourceScreen(
hasNextPage, hasNextPage,
source.supportsLatest, source.supportsLatest,
isLatest, isLatest,
serverUrl,
onLoadNextPage = vm::loadNextPage, onLoadNextPage = vm::loadNextPage,
onMangaClick = onMangaClick, onMangaClick = onMangaClick,
onClickMode = vm::setMode onClickMode = vm::setMode
@@ -82,7 +82,6 @@ private fun MangaTable(
hasNextPage: Boolean = false, hasNextPage: Boolean = false,
supportsLatest: Boolean, supportsLatest: Boolean,
isLatest: Boolean, isLatest: Boolean,
serverUrl: String,
onLoadNextPage: () -> Unit, onLoadNextPage: () -> Unit,
onMangaClick: (Long) -> Unit, onMangaClick: (Long) -> Unit,
onClickMode: (Boolean) -> Unit onClickMode: (Boolean) -> Unit
@@ -111,7 +110,7 @@ private fun MangaTable(
} }
MangaGridItem( MangaGridItem(
title = manga.title, title = manga.title,
cover = manga.cover(serverUrl), cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium),
onClick = { onClick = {
onMangaClick(manga.id) onMangaClick(manga.id)
} }

View File

@@ -9,7 +9,6 @@ package ca.gosyer.ui.sources.components
import ca.gosyer.data.models.Manga import ca.gosyer.data.models.Manga
import ca.gosyer.data.models.MangaPage import ca.gosyer.data.models.MangaPage
import ca.gosyer.data.models.Source import ca.gosyer.data.models.Source
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.interactions.SourceInteractionHandler import ca.gosyer.data.server.interactions.SourceInteractionHandler
import ca.gosyer.ui.base.vm.ViewModel import ca.gosyer.ui.base.vm.ViewModel
import ca.gosyer.util.compose.saveBooleanInBundle import ca.gosyer.util.compose.saveBooleanInBundle
@@ -26,21 +25,17 @@ import javax.inject.Inject
class SourceScreenViewModel( class SourceScreenViewModel(
private val source: Source, private val source: Source,
private val bundle: Bundle, private val bundle: Bundle,
private val sourceHandler: SourceInteractionHandler, private val sourceHandler: SourceInteractionHandler
serverPreferences: ServerPreferences
) : ViewModel() { ) : ViewModel() {
@Inject constructor( @Inject constructor(
params: Params, params: Params,
sourceHandler: SourceInteractionHandler, sourceHandler: SourceInteractionHandler
serverPreferences: ServerPreferences
) : this( ) : this(
params.source, params.source,
params.bundle, params.bundle,
sourceHandler, sourceHandler
serverPreferences
) )
val serverUrl = serverPreferences.serverUrl().stateIn(scope)
private val _mangas = saveObjectInBundle(scope, bundle, MANGAS_KEY) { emptyList<Manga>() } private val _mangas = saveObjectInBundle(scope, bundle, MANGAS_KEY) { emptyList<Manga>() }
val mangas = _mangas.asStateFlow() val mangas = _mangas.asStateFlow()

View File

@@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ca.gosyer.data.models.Chapter import ca.gosyer.data.models.Chapter
@@ -37,6 +38,7 @@ import ca.gosyer.ui.base.components.Toolbar
import ca.gosyer.ui.base.components.mangaAspectRatio import ca.gosyer.ui.base.components.mangaAspectRatio
import ca.gosyer.ui.base.resources.stringResource import ca.gosyer.ui.base.resources.stringResource
import ca.gosyer.ui.base.vm.viewModel import ca.gosyer.ui.base.vm.viewModel
import io.kamel.image.lazyPainterResource
@Composable @Composable
fun UpdatesMenu( fun UpdatesMenu(
@@ -44,7 +46,6 @@ fun UpdatesMenu(
openManga: (Long) -> Unit openManga: (Long) -> Unit
) { ) {
val vm = viewModel<UpdatesMenuViewModel>() val vm = viewModel<UpdatesMenuViewModel>()
val serverUrl by vm.serverUrl.collectAsState()
val isLoading by vm.isLoading.collectAsState() val isLoading by vm.isLoading.collectAsState()
val updates by vm.updates.collectAsState() val updates by vm.updates.collectAsState()
Column { Column {
@@ -57,7 +58,6 @@ fun UpdatesMenu(
val manga = it.manga!! val manga = it.manga!!
val chapter = it.chapter val chapter = it.chapter
UpdatesItem( UpdatesItem(
serverUrl,
it, it,
onClickItem = { openChapter(chapter.index, chapter.mangaId) }, onClickItem = { openChapter(chapter.index, chapter.mangaId) },
onClickCover = { openManga(manga.id) }, onClickCover = { openManga(manga.id) },
@@ -73,7 +73,6 @@ fun UpdatesMenu(
@Composable @Composable
fun UpdatesItem( fun UpdatesItem(
serverUrl: String,
chapterDownloadItem: ChapterDownloadItem, chapterDownloadItem: ChapterDownloadItem,
onClickItem: () -> Unit, onClickItem: () -> Unit,
onClickCover: () -> Unit, onClickCover: () -> Unit,
@@ -101,7 +100,8 @@ fun UpdatesItem(
.padding(start = 16.dp, top = 8.dp, bottom = 8.dp) .padding(start = 16.dp, top = 8.dp, bottom = 8.dp)
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.clickable { onClickCover() }, .clickable { onClickCover() },
imageUrl = manga.cover(serverUrl) cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium),
contentDescription = manga.title
) )
MangaListItemColumn( MangaListItemColumn(
modifier = Modifier modifier = Modifier

View File

@@ -8,11 +8,9 @@ package ca.gosyer.ui.updates
import ca.gosyer.data.download.DownloadService import ca.gosyer.data.download.DownloadService
import ca.gosyer.data.models.Chapter import ca.gosyer.data.models.Chapter
import ca.gosyer.data.server.ServerPreferences
import ca.gosyer.data.server.interactions.ChapterInteractionHandler import ca.gosyer.data.server.interactions.ChapterInteractionHandler
import ca.gosyer.data.server.interactions.UpdatesInteractionHandler import ca.gosyer.data.server.interactions.UpdatesInteractionHandler
import ca.gosyer.ui.base.components.ChapterDownloadItem import ca.gosyer.ui.base.components.ChapterDownloadItem
import ca.gosyer.ui.base.prefs.asStateIn
import ca.gosyer.ui.base.vm.ViewModel import ca.gosyer.ui.base.vm.ViewModel
import ca.gosyer.util.lang.throwIfCancellation import ca.gosyer.util.lang.throwIfCancellation
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,10 +24,8 @@ import javax.inject.Inject
class UpdatesMenuViewModel @Inject constructor( class UpdatesMenuViewModel @Inject constructor(
private val chapterHandler: ChapterInteractionHandler, private val chapterHandler: ChapterInteractionHandler,
private val updatesHandler: UpdatesInteractionHandler, private val updatesHandler: UpdatesInteractionHandler,
private val serverPreferences: ServerPreferences,
private val downloadService: DownloadService private val downloadService: DownloadService
) : ViewModel() { ) : ViewModel() {
val serverUrl = serverPreferences.serverUrl().asStateIn(scope).asStateFlow()
private val _isLoading = MutableStateFlow(true) private val _isLoading = MutableStateFlow(true)
val isLoading = _isLoading.asStateFlow() val isLoading = _isLoading.asStateFlow()