Switch from Kamel to Compose-ImageLoader

This commit is contained in:
Syer10
2022-08-24 20:03:16 -04:00
parent 7a68f9acfe
commit ddbf9d16b8
27 changed files with 384 additions and 89 deletions

View File

@@ -27,6 +27,7 @@ dependencies {
implementation(libs.accompanist.pagerIndicators)
implementation(libs.accompanist.flowLayout)
implementation(libs.kamel)
implementation(libs.imageloader)
implementation(libs.materialDialogs.core)
// Android

View File

@@ -37,6 +37,7 @@ dependencies {
implementation(libs.accompanist.pagerIndicators)
implementation(libs.accompanist.flowLayout)
implementation(libs.kamel)
implementation(libs.imageloader)
implementation(libs.materialDialogs.core)
// UI (Swing)

View File

@@ -13,6 +13,7 @@ composeAndroid = "1.2.1"
voyager = "1.0.0-beta16"
accompanist = "0.25.1"
kamel = "0.4.1"
imageloader = "1.1.3"
materialDialogs = "0.8.0"
# Android
@@ -101,6 +102,7 @@ accompanist-pager = { module = "ca.gosyer:accompanist-pager", version.ref = "acc
accompanist-pagerIndicators = { module = "ca.gosyer:accompanist-pager-indicators", version.ref = "accompanist" }
accompanist-flowLayout = { module = "ca.gosyer:accompanist-flowlayout", version.ref = "accompanist" }
kamel = { module = "com.alialbaali.kamel:kamel-image", version.ref = "kamel" }
imageloader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "imageloader" }
materialDialogs-core = { module = "ca.gosyer:compose-material-dialogs-core", version.ref = "materialDialogs" }
# Android

View File

@@ -43,6 +43,7 @@ kotlin {
api(kotlin("stdlib-common"))
api(libs.coroutines.core)
api(libs.kamel)
api(libs.imageloader)
api(libs.voyager.core)
api(libs.voyager.navigation)
api(libs.voyager.transitions)

View File

@@ -0,0 +1,31 @@
/*
* 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.jui.ui.base.image
import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.ImageLoaderBuilder
import com.seiko.imageloader.cache.disk.DiskCache
import com.seiko.imageloader.cache.disk.DiskCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryCache
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
import okio.Path.Companion.toOkioPath
actual fun imageLoaderBuilder(contextWrapper: ContextWrapper): ImageLoaderBuilder {
return ImageLoaderBuilder(contextWrapper)
}
actual fun diskCache(contextWrapper: ContextWrapper): DiskCache {
return DiskCacheBuilder()
.directory(contextWrapper.cacheDir.toOkioPath() / "image_cache")
.maxSizeBytes(1024 * 1024 * 150) // 150 MB
.build()
}
actual fun memoryCache(contextWrapper: ContextWrapper): MemoryCache {
return MemoryCacheBuilder(contextWrapper)
.build()
}

View File

@@ -10,8 +10,11 @@ import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.compositionLocalOf
import ca.gosyer.jui.core.di.AppScope
import ca.gosyer.jui.ui.ViewModelComponent
import ca.gosyer.jui.ui.base.image.ImageLoaderProvider
import ca.gosyer.jui.ui.base.image.KamelConfigProvider
import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.ImageLoader
import com.seiko.imageloader.LocalImageLoader
import io.kamel.core.config.KamelConfig
import io.kamel.core.config.KamelConfigBuilder
import io.kamel.image.config.LocalKamelConfig
@@ -22,6 +25,8 @@ interface UiComponent {
val kamelConfig: KamelConfig
val imageLoader: ImageLoader
val contextWrapper: ContextWrapper
val hooks: Array<ProvidedValue<out Any>>
@@ -30,10 +35,15 @@ interface UiComponent {
@Provides
fun kamelConfigFactory(contextWrapper: ContextWrapper): KamelConfig = kamelConfigProvider.get { kamelPlatformHandler(contextWrapper) }
@AppScope
@Provides
fun imageLoaderFactory(imageLoaderProvider: ImageLoaderProvider): ImageLoader = imageLoaderProvider.get()
@Provides
fun getHooks(viewModelComponent: ViewModelComponent) = arrayOf(
LocalViewModels provides viewModelComponent,
LocalKamelConfig provides kamelConfig
LocalKamelConfig provides kamelConfig,
LocalImageLoader provides imageLoader
)
}

View File

@@ -0,0 +1,84 @@
/*
* 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.jui.ui.base.image
import ca.gosyer.jui.domain.extension.model.Extension
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.server.service.ServerPreferences
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.ImageLoader
import com.seiko.imageloader.ImageLoaderBuilder
import com.seiko.imageloader.cache.disk.DiskCache
import com.seiko.imageloader.cache.memory.MemoryCache
import com.seiko.imageloader.component.mapper.Mapper
import com.seiko.imageloader.request.Options
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import me.tatarka.inject.annotations.Inject
class ImageLoaderProvider @Inject constructor(
private val http: Http,
serverPreferences: ServerPreferences,
private val context: ContextWrapper
) {
@OptIn(DelicateCoroutinesApi::class)
val serverUrl = serverPreferences.serverUrl().stateIn(GlobalScope)
fun get(): ImageLoader {
return imageLoaderBuilder(context).apply {
httpClient { http }
components {
add(MangaCoverMapper())
add(ExtensionIconMapper())
add(SourceIconMapper())
}
options(
Options(
config = Options.ImageConfig.HARDWARE
)
)
diskCache {
diskCache(context)
}
memoryCache {
memoryCache(context)
}
}.build()
}
inner class MangaCoverMapper : Mapper<String> {
override fun map(data: Any, options: Options): String? {
if (data !is Manga) return null
if (data.thumbnailUrl.isNullOrBlank()) return null
return serverUrl.value.toString() + data.thumbnailUrl
}
}
inner class ExtensionIconMapper : Mapper<String> {
override fun map(data: Any, options: Options): String? {
if (data !is Extension) return null
if (data.iconUrl.isBlank()) return null
return serverUrl.value.toString() + data.iconUrl
}
}
inner class SourceIconMapper : Mapper<String> {
override fun map(data: Any, options: Options): String? {
if (data !is Source) return null
if (data.iconUrl.isBlank()) return null
return serverUrl.value.toString() + data.iconUrl
}
}
}
expect fun imageLoaderBuilder(contextWrapper: ContextWrapper): ImageLoaderBuilder
expect fun diskCache(contextWrapper: ContextWrapper): DiskCache
expect fun memoryCache(contextWrapper: ContextWrapper): MemoryCache

View File

@@ -37,7 +37,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.domain.chapter.model.Chapter
@@ -58,7 +57,6 @@ import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.resources.stringResource
import io.kamel.image.lazyPainterResource
@Composable
fun DownloadsScreenContent(
@@ -128,7 +126,7 @@ fun DownloadsItem(
.padding(start = 16.dp, top = 8.dp, bottom = 8.dp)
.clip(MaterialTheme.shapes.medium)
.clickable { onClickCover() },
cover = lazyPainterResource(item.manga, filterQuality = FilterQuality.Medium),
data = item.manga,
contentDescription = item.manga.title
)
MangaListItemColumn(

View File

@@ -54,14 +54,13 @@ import ca.gosyer.jui.uicore.components.LoadingScreen
import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import ca.gosyer.jui.uicore.image.ImageLoaderImage
import ca.gosyer.jui.uicore.resources.stringResource
import com.vanpra.composematerialdialogs.MaterialDialog
import com.vanpra.composematerialdialogs.MaterialDialogState
import com.vanpra.composematerialdialogs.listItemsMultiChoice
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
import com.vanpra.composematerialdialogs.title
import io.kamel.image.lazyPainterResource
@Composable
fun ExtensionsScreenContent(
@@ -153,7 +152,12 @@ fun ExtensionItem(
Box(modifier = Modifier.fillMaxWidth().padding(end = 12.dp).height(50.dp).background(MaterialTheme.colors.background)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(4.dp))
KamelImage(lazyPainterResource(extension, filterQuality = FilterQuality.Medium), extension.name, Modifier.size(50.dp))
ImageLoaderImage(
data = extension,
contentDescription = extension.name,
modifier = Modifier.size(50.dp),
filterQuality = FilterQuality.Medium
)
Spacer(Modifier.width(8.dp))
Column {
val title = buildAnnotatedString {

View File

@@ -22,7 +22,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.uicore.components.MangaListItem
@@ -31,7 +30,6 @@ import ca.gosyer.jui.uicore.components.MangaListItemTitle
import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import io.kamel.image.lazyPainterResource
@Composable
fun LibraryMangaList(
@@ -81,7 +79,6 @@ private fun LibraryMangaListItem(
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
MangaListItem(
modifier = modifier then Modifier
.requiredHeight(56.dp)
@@ -91,7 +88,7 @@ private fun LibraryMangaListItem(
modifier = Modifier
.size(40.dp)
.clip(MaterialTheme.shapes.medium),
cover = cover,
data = manga,
contentDescription = manga.title
)
MangaListItemTitle(

View File

@@ -35,8 +35,7 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberVerticalScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun LibraryMangaComfortableGrid(
@@ -94,7 +93,6 @@ private fun LibraryMangaComfortableGridItem(
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
@@ -106,14 +104,15 @@ private fun LibraryMangaComfortableGridItem(
.clip(MaterialTheme.shapes.medium) then modifier
) {
Column {
KamelImage(
cover,
ImageLoaderImage(
manga,
contentDescription = manga.title,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(mangaAspectRatio)
.clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
Text(
text = manga.title,

View File

@@ -38,8 +38,7 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberVerticalScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
expect fun Modifier.libraryMangaModifier(
onClickManga: () -> Unit,
@@ -102,7 +101,6 @@ private fun LibraryMangaCompactGridItem(
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
@@ -113,11 +111,12 @@ private fun LibraryMangaCompactGridItem(
.aspectRatio(mangaAspectRatio)
.clip(MaterialTheme.shapes.medium) then modifier
) {
KamelImage(
cover,
ImageLoaderImage(
manga,
manga.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
Box(modifier = Modifier.fillMaxSize() then shadowGradient)
Text(

View File

@@ -29,8 +29,7 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberVerticalScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun LibraryMangaCoverOnlyGrid(
@@ -88,19 +87,18 @@ private fun LibraryMangaCoverOnlyGridItem(
showLanguage: Boolean,
showLocal: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
Box(
modifier = Modifier.padding(4.dp)
.fillMaxWidth()
.aspectRatio(mangaAspectRatio)
.clip(MaterialTheme.shapes.medium) then modifier
) {
KamelImage(
cover,
ImageLoaderImage(
manga,
contentDescription = manga.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
LibraryMangaBadges(
modifier = Modifier.padding(4.dp),

View File

@@ -41,14 +41,13 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import ca.gosyer.jui.uicore.image.ImageLoaderImage
import ca.gosyer.jui.uicore.resources.stringResource
import com.google.accompanist.flowlayout.FlowRow
import com.vanpra.composematerialdialogs.MaterialDialog
import com.vanpra.composematerialdialogs.MaterialDialogState
import com.vanpra.composematerialdialogs.listItemsMultiChoice
import com.vanpra.composematerialdialogs.title
import io.kamel.image.lazyPainterResource
@Composable
fun MangaItem(manga: Manga) {
@@ -74,15 +73,16 @@ fun MangaItem(manga: Manga) {
@Composable
private fun Cover(manga: Manga, modifier: Modifier = Modifier) {
KamelImage(
resource = lazyPainterResource(manga, filterQuality = FilterQuality.Medium),
ImageLoaderImage(
data = manga,
contentDescription = manga.title,
modifier = modifier,
errorModifier = modifier then Modifier
.aspectRatio(
ratio = mangaAspectRatio,
matchHeightConstraintsFirst = true
)
),
filterQuality = FilterQuality.Medium
)
}

View File

@@ -37,8 +37,7 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberVerticalScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun SourceMangaComfortableGrid(
@@ -89,7 +88,6 @@ private fun SourceMangaComfortableGridItem(
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
@@ -101,14 +99,15 @@ private fun SourceMangaComfortableGridItem(
.clip(MaterialTheme.shapes.medium) then modifier
) {
Column {
KamelImage(
cover,
ImageLoaderImage(
manga,
contentDescription = manga.title,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(mangaAspectRatio)
.clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
Text(
text = manga.title,

View File

@@ -40,8 +40,7 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberVerticalScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun SourceMangaCompactGrid(
@@ -92,7 +91,6 @@ private fun SourceMangaCompactGridItem(
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
@@ -103,11 +101,12 @@ private fun SourceMangaCompactGridItem(
.aspectRatio(mangaAspectRatio)
.clip(MaterialTheme.shapes.medium) then modifier
) {
KamelImage(
cover,
ImageLoaderImage(
manga,
manga.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
Box(modifier = Modifier.fillMaxSize().then(shadowGradient))
Text(

View File

@@ -22,7 +22,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.uicore.components.MangaListItem
@@ -31,7 +30,6 @@ import ca.gosyer.jui.uicore.components.MangaListItemTitle
import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import io.kamel.image.lazyPainterResource
@Composable
fun SourceMangaList(
@@ -74,7 +72,6 @@ private fun MangaListItem(
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
MangaListItem(
modifier = modifier then Modifier
.requiredHeight(56.dp)
@@ -84,7 +81,7 @@ private fun MangaListItem(
modifier = Modifier
.size(40.dp)
.clip(MaterialTheme.shapes.medium),
cover = cover,
data = manga,
contentDescription = manga.title
)
MangaListItemTitle(

View File

@@ -40,9 +40,8 @@ import ca.gosyer.jui.ui.sources.home.SourceHomeScreen
import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import ca.gosyer.jui.uicore.image.ImageLoaderImage
import ca.gosyer.jui.uicore.resources.stringResource
import io.kamel.image.lazyPainterResource
expect fun Modifier.sourceSideMenuItem(
onSourceTabClick: () -> Unit,
@@ -140,10 +139,11 @@ fun SourcesSideMenu(
modifier = modifier
)
is SourceNavigatorScreen.SourceScreen -> Box(Modifier.align(Alignment.Center)) {
KamelImage(
resource = lazyPainterResource(screen.source, filterQuality = FilterQuality.Medium),
ImageLoaderImage(
data = screen.source,
contentDescription = screen.source.displayName,
modifier = modifier
modifier = modifier,
filterQuality = FilterQuality.Medium
)
}
}

View File

@@ -28,8 +28,7 @@ import androidx.compose.ui.unit.times
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.ui.sources.browse.components.SourceMangaBadges
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun GlobalSearchMangaComfortableGridItem(
@@ -37,7 +36,6 @@ fun GlobalSearchMangaComfortableGridItem(
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
@@ -49,14 +47,15 @@ fun GlobalSearchMangaComfortableGridItem(
.clip(MaterialTheme.shapes.medium) then modifier
) {
Column {
KamelImage(
cover,
ImageLoaderImage(
manga,
contentDescription = manga.title,
modifier = Modifier
.height(200.dp)
.aspectRatio(mangaAspectRatio, true)
.clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
Text(
text = manga.title,

View File

@@ -31,8 +31,7 @@ import androidx.compose.ui.unit.sp
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.ui.sources.browse.components.SourceMangaBadges
import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun GlobalSearchMangaCompactGridItem(
@@ -40,7 +39,6 @@ fun GlobalSearchMangaCompactGridItem(
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
@@ -51,11 +49,12 @@ fun GlobalSearchMangaCompactGridItem(
.aspectRatio(mangaAspectRatio, true)
.clip(MaterialTheme.shapes.medium) then modifier
) {
KamelImage(
cover,
ImageLoaderImage(
manga,
manga.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
Box(modifier = Modifier.fillMaxSize().then(shadowGradient))
Text(

View File

@@ -56,10 +56,9 @@ import ca.gosyer.jui.uicore.components.VerticalScrollbar
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.rememberVerticalScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.image.KamelImage
import ca.gosyer.jui.uicore.image.ImageLoaderImage
import ca.gosyer.jui.uicore.resources.stringResource
import com.vanpra.composematerialdialogs.rememberMaterialDialogState
import io.kamel.image.lazyPainterResource
@Composable
fun SourceHomeScreenContent(
@@ -175,7 +174,12 @@ fun WideSourceItem(
},
horizontalAlignment = Alignment.CenterHorizontally
) {
KamelImage(lazyPainterResource(source, filterQuality = FilterQuality.Medium), source.displayName, Modifier.size(96.dp))
ImageLoaderImage(
data = source,
contentDescription = source.displayName,
modifier = Modifier.size(96.dp),
filterQuality = FilterQuality.Medium
)
Spacer(Modifier.height(4.dp))
Text(
"${source.name} (${source.displayLang.toUpperCase(Locale.current)})",
@@ -199,11 +203,12 @@ fun ThinSourceItem(
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
KamelImage(
lazyPainterResource(source, filterQuality = FilterQuality.Medium),
ImageLoaderImage(
source,
source.displayName,
Modifier.fillMaxHeight()
.aspectRatio(1F, true)
.aspectRatio(1F, true),
filterQuality = FilterQuality.Medium
)
Spacer(Modifier.width(8.dp))
Column {

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.domain.chapter.model.Chapter
@@ -45,7 +44,6 @@ import ca.gosyer.jui.uicore.components.mangaAspectRatio
import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter
import ca.gosyer.jui.uicore.components.scrollbarPadding
import ca.gosyer.jui.uicore.resources.stringResource
import io.kamel.image.lazyPainterResource
import kotlinx.datetime.LocalDate
@Composable
@@ -137,7 +135,7 @@ fun UpdatesItem(
.padding(start = 16.dp, top = 8.dp, bottom = 8.dp)
.clip(MaterialTheme.shapes.medium)
.clickable { onClickCover() },
cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium),
data = manga,
contentDescription = manga.title
)
MangaListItemColumn(

View File

@@ -0,0 +1,31 @@
/*
* 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.jui.ui.base.image
import ca.gosyer.jui.core.io.userDataDir
import ca.gosyer.jui.uicore.vm.ContextWrapper
import com.seiko.imageloader.ImageLoaderBuilder
import com.seiko.imageloader.cache.disk.DiskCache
import com.seiko.imageloader.cache.disk.DiskCacheBuilder
import com.seiko.imageloader.cache.memory.MemoryCache
import com.seiko.imageloader.cache.memory.MemoryCacheBuilder
actual fun imageLoaderBuilder(contextWrapper: ContextWrapper): ImageLoaderBuilder {
return ImageLoaderBuilder()
}
actual fun diskCache(contextWrapper: ContextWrapper): DiskCache {
return DiskCacheBuilder()
.directory(userDataDir / "image_cache")
.maxSizeBytes(1024 * 1024 * 150) // 150 MB
.build()
}
actual fun memoryCache(contextWrapper: ContextWrapper): MemoryCache {
return MemoryCacheBuilder()
.build()
}

View File

@@ -39,6 +39,7 @@ kotlin {
api(kotlin("stdlib-common"))
api(libs.coroutines.core)
api(libs.kamel)
api(libs.imageloader)
api(libs.voyager.core)
api(libs.dateTime)
api(projects.core)

View File

@@ -23,20 +23,19 @@ import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.core.Resource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun MangaGridItem(
title: String,
cover: Resource<Painter>,
data: Any,
onClick: () -> Unit = {}
) {
val fontStyle = LocalTextStyle.current.merge(
@@ -53,7 +52,7 @@ fun MangaGridItem(
shape = RoundedCornerShape(4.dp)
) {
Box(modifier = Modifier.fillMaxSize()) {
KamelImage(cover, title, contentScale = ContentScale.Crop)
ImageLoaderImage(data, title, contentScale = ContentScale.Crop, filterQuality = FilterQuality.Medium)
Box(modifier = Modifier.fillMaxSize().then(shadowGradient))
Text(
text = title,

View File

@@ -15,12 +15,11 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import ca.gosyer.jui.uicore.image.KamelImage
import io.kamel.core.Resource
import ca.gosyer.jui.uicore.image.ImageLoaderImage
@Composable
fun MangaListItem(
@@ -38,14 +37,15 @@ fun MangaListItem(
@Composable
fun MangaListItemImage(
modifier: Modifier = Modifier,
cover: Resource<Painter>,
data: Any,
contentDescription: String
) {
KamelImage(
cover,
ImageLoaderImage(
data,
contentDescription = contentDescription,
modifier = modifier,
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
filterQuality = FilterQuality.Medium
)
}

View File

@@ -0,0 +1,143 @@
/*
* 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.jui.uicore.image
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.BrokenImage
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
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.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultFilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import ca.gosyer.jui.uicore.components.LoadingScreen
import com.seiko.imageloader.ImageRequestState
import com.seiko.imageloader.rememberAsyncImagePainter
import com.seiko.imageloader.request.ImageRequestBuilder
import org.lighthousegames.logging.logging
private val log = logging()
private enum class ImageLoaderImageState {
Loading,
Success,
Failure,
}
@Composable
fun ImageLoaderImage(
data: Any,
contentDescription: String?,
modifier: Modifier = Modifier,
errorModifier: Modifier = modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DefaultFilterQuality,
onLoading: (@Composable BoxScope.(Float) -> Unit)? = {
LoadingScreen(progress = it.coerceIn(0.0F, 1.0F), modifier = modifier then Modifier.fillMaxSize())
},
onFailure: (@Composable BoxScope.(Throwable) -> Unit)? = {
LaunchedEffect(it) {
log.warn(it) { "Error loading image" }
}
Box(
modifier = errorModifier then Modifier.fillMaxSize()
.background(Color(0x1F888888)),
contentAlignment = Alignment.Center
) {
Icon(
Icons.Rounded.BrokenImage,
contentDescription = null,
tint = Color(0x1F888888),
modifier = Modifier.size(24.dp)
)
}
},
contentAlignment: Alignment = Alignment.Center,
animationSpec: FiniteAnimationSpec<Float>? = tween()
) {
val request = remember(data) { ImageRequestBuilder().data(data).build() }
val painter = rememberAsyncImagePainter(request, contentScale = contentScale)
val progress = remember { mutableStateOf(-1F) }
val error = remember { mutableStateOf<Throwable?>(null) }
val state by derivedStateOf {
when (val state = painter.requestState) {
is ImageRequestState.Failure -> {
progress.value = 0.0F
error.value = state.error
ImageLoaderImageState.Failure
}
ImageRequestState.Loading -> {
progress.value = 0.0F
ImageLoaderImageState.Loading
}
ImageRequestState.Success -> {
progress.value = 1.0F
ImageLoaderImageState.Success
}
}
}
if (animationSpec != null) {
Crossfade(state, animationSpec = animationSpec, modifier = modifier) {
Box(Modifier.fillMaxSize(), contentAlignment) {
when (it) {
ImageLoaderImageState.Loading -> if (onLoading != null) {
onLoading(progress.value)
}
ImageLoaderImageState.Success -> Image(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier.fillMaxSize(),
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter
)
ImageLoaderImageState.Failure -> {
if (onFailure != null) {
onFailure(error.value ?: return@Crossfade)
}
}
}
}
}
} else {
Box(modifier, contentAlignment) {
Image(
painter = painter,
contentDescription = contentDescription,
modifier = Modifier.fillMaxSize(),
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter
)
}
}
}