diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 7556ec11..5fe112d3 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { implementation(libs.accompanist.pager) implementation(libs.accompanist.pagerIndicators) implementation(libs.accompanist.flowLayout) - implementation(libs.kamel) implementation(libs.imageloader) implementation(libs.materialDialogs.core) diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 5f8c3fcf..a7b211e8 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -36,7 +36,6 @@ dependencies { implementation(libs.accompanist.pager) implementation(libs.accompanist.pagerIndicators) implementation(libs.accompanist.flowLayout) - implementation(libs.kamel) implementation(libs.imageloader) implementation(libs.materialDialogs.core) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9f0ccdc..a342f049 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,6 @@ composeCompiler = "1.3.0" 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" @@ -101,7 +100,6 @@ voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", vers accompanist-pager = { module = "ca.gosyer:accompanist-pager", version.ref = "accompanist" } 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" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 313ffd14..16ffa0c4 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -42,7 +42,6 @@ kotlin { dependencies { api(kotlin("stdlib-common")) api(libs.coroutines.core) - api(libs.kamel) api(libs.imageloader) api(libs.voyager.core) api(libs.voyager.navigation) diff --git a/presentation/src/androidMain/kotlin/ca/gosyer/jui/ui/base/KamelAndroidHandler.kt b/presentation/src/androidMain/kotlin/ca/gosyer/jui/ui/base/KamelAndroidHandler.kt deleted file mode 100644 index df08f98d..00000000 --- a/presentation/src/androidMain/kotlin/ca/gosyer/jui/ui/base/KamelAndroidHandler.kt +++ /dev/null @@ -1,17 +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.jui.ui.base - -import ca.gosyer.jui.uicore.vm.ContextWrapper -import io.kamel.core.config.KamelConfigBuilder -import io.kamel.image.config.resourcesFetcher -import io.kamel.image.config.resourcesIdMapper - -actual fun KamelConfigBuilder.kamelPlatformHandler(contextWrapper: ContextWrapper) { - resourcesIdMapper(contextWrapper) - resourcesFetcher(contextWrapper) -} diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/UiComponent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/UiComponent.kt index 0d282c2b..044974a7 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/UiComponent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/UiComponent.kt @@ -11,30 +11,18 @@ 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 import me.tatarka.inject.annotations.Provides interface UiComponent { - val kamelConfigProvider: KamelConfigProvider - - val kamelConfig: KamelConfig - val imageLoader: ImageLoader val contextWrapper: ContextWrapper val hooks: Array> - @AppScope - @Provides - fun kamelConfigFactory(contextWrapper: ContextWrapper): KamelConfig = kamelConfigProvider.get { kamelPlatformHandler(contextWrapper) } - @AppScope @Provides fun imageLoaderFactory(imageLoaderProvider: ImageLoaderProvider): ImageLoader = imageLoaderProvider.get() @@ -42,12 +30,9 @@ interface UiComponent { @Provides fun getHooks(viewModelComponent: ViewModelComponent) = arrayOf( LocalViewModels provides viewModelComponent, - LocalKamelConfig provides kamelConfig, LocalImageLoader provides imageLoader ) } -expect fun KamelConfigBuilder.kamelPlatformHandler(contextWrapper: ContextWrapper) - val LocalViewModels = compositionLocalOf { throw IllegalArgumentException("ViewModelComponent not found") } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/image/KamelConfigProvider.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/image/KamelConfigProvider.kt deleted file mode 100644 index d2f58751..00000000 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/image/KamelConfigProvider.kt +++ /dev/null @@ -1,112 +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.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 io.kamel.core.DataSource -import io.kamel.core.Resource -import io.kamel.core.config.DefaultCacheSize -import io.kamel.core.config.KamelConfig -import io.kamel.core.config.KamelConfigBuilder -import io.kamel.core.config.ResourceConfig -import io.kamel.core.config.fileFetcher -import io.kamel.core.config.stringMapper -import io.kamel.core.config.uriMapper -import io.kamel.core.config.urlMapper -import io.kamel.core.fetcher.Fetcher -import io.kamel.core.mapper.Mapper -import io.kamel.image.config.imageBitmapDecoder -import io.ktor.client.HttpClient -import io.ktor.client.plugins.expectSuccess -import io.ktor.client.plugins.onDownload -import io.ktor.client.request.request -import io.ktor.client.request.takeFrom -import io.ktor.client.request.url -import io.ktor.client.statement.bodyAsChannel -import io.ktor.http.Url -import io.ktor.utils.io.ByteReadChannel -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.channelFlow -import me.tatarka.inject.annotations.Inject - -class KamelConfigProvider @Inject constructor( - private val http: Http, - serverPreferences: ServerPreferences -) { - @OptIn(DelicateCoroutinesApi::class) - val serverUrl = serverPreferences.serverUrl().stateIn(GlobalScope) - - fun get(kamelPlatformHandler: KamelConfigBuilder.() -> Unit): KamelConfig { - return KamelConfig { - // Default config - imageBitmapCacheSize = DefaultCacheSize - imageVectorCacheSize = DefaultCacheSize - imageBitmapDecoder() - stringMapper() - urlMapper() - uriMapper() - fileFetcher() - - // JUI config - kamelPlatformHandler() - fetcher(HttpFetcher(http)) - mapper(MangaCoverMapper(serverUrl)) - mapper(ExtensionIconMapper(serverUrl)) - mapper(SourceIconMapper(serverUrl)) - } - } - - class MangaCoverMapper(private val serverUrlStateFlow: StateFlow) : Mapper { - override fun map(input: Manga): Url { - return Url(serverUrlStateFlow.value.toString() + input.thumbnailUrl.orEmpty()) - } - } - - class ExtensionIconMapper(private val serverUrlStateFlow: StateFlow) : Mapper { - override fun map(input: Extension): Url { - return Url(serverUrlStateFlow.value.toString() + input.iconUrl) - } - } - - class SourceIconMapper(private val serverUrlStateFlow: StateFlow) : Mapper { - override fun map(input: Source): Url { - return Url(serverUrlStateFlow.value.toString() + input.iconUrl) - } - } - - private class HttpFetcher(private val client: HttpClient) : Fetcher { - - override val source: DataSource = DataSource.Network - - override val Url.isSupported: Boolean - get() = protocol.name == "https" || protocol.name == "http" - - override fun fetch( - data: Url, - resourceConfig: ResourceConfig - ): Flow> = channelFlow { - val response = client.request { - onDownload { bytesSentTotal, contentLength -> - val progress = (bytesSentTotal.toFloat() / contentLength).coerceAtMost(1.0F) - send(Resource.Loading(progress)) - } - takeFrom(resourceConfig.requestData) - url(data) - expectSuccess = true - } - val bytes = response.bodyAsChannel() - send(Resource.Success(bytes)) - } - } -} diff --git a/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/base/KamelDesktopHandler.kt b/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/base/KamelDesktopHandler.kt deleted file mode 100644 index 65bcb2ca..00000000 --- a/presentation/src/desktopMain/kotlin/ca/gosyer/jui/ui/base/KamelDesktopHandler.kt +++ /dev/null @@ -1,19 +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.jui.ui.base - -import ca.gosyer.jui.uicore.vm.ContextWrapper -import io.kamel.core.config.KamelConfigBuilder -import io.kamel.image.config.imageVectorDecoder -import io.kamel.image.config.resourcesFetcher -import io.kamel.image.config.svgDecoder - -actual fun KamelConfigBuilder.kamelPlatformHandler(contextWrapper: ContextWrapper) { - resourcesFetcher() - imageVectorDecoder() - svgDecoder() -} diff --git a/ui-core/build.gradle.kts b/ui-core/build.gradle.kts index a2ecf6df..f8b1be6e 100644 --- a/ui-core/build.gradle.kts +++ b/ui-core/build.gradle.kts @@ -38,7 +38,6 @@ kotlin { dependencies { api(kotlin("stdlib-common")) api(libs.coroutines.core) - api(libs.kamel) api(libs.imageloader) api(libs.voyager.core) api(libs.dateTime) diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/image/KamelImage.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/image/KamelImage.kt deleted file mode 100644 index ecb6cd7f..00000000 --- a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/image/KamelImage.kt +++ /dev/null @@ -1,148 +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.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.painter.Painter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.dp -import ca.gosyer.jui.uicore.components.LoadingScreen -import io.kamel.core.Resource -import org.lighthousegames.logging.logging -import io.kamel.image.KamelImage as BaseKamelImage - -private val log = logging() - -private enum class KamelImageState { - Loading, - Success, - Failure, -} - -@Composable -fun KamelImage( - resource: Resource, - contentDescription: String?, - modifier: Modifier = Modifier, - errorModifier: Modifier = modifier, - alignment: Alignment = Alignment.Center, - contentScale: ContentScale = ContentScale.Fit, - alpha: Float = DefaultAlpha, - colorFilter: ColorFilter? = null, - 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? = tween() -) { - if (animationSpec != null) { - val progress = remember { mutableStateOf(-1F) } - val image = remember { mutableStateOf(null) } - val error = remember { mutableStateOf(null) } - val state by derivedStateOf { - when (resource) { - is Resource.Failure -> { - progress.value = -1F - error.value = resource.exception - KamelImageState.Failure - } - is Resource.Loading -> { - progress.value = resource.progress - KamelImageState.Loading - } - is Resource.Success -> { - progress.value = 1.0F - image.value = resource.value - KamelImageState.Success - } - } - } - Crossfade(state, animationSpec = animationSpec, modifier = modifier) { - Box(Modifier.fillMaxSize(), contentAlignment) { - when (it) { - KamelImageState.Loading -> if (onLoading != null) { - onLoading(progress.value) - } - KamelImageState.Success -> Box { - val value = image.value - if (value != null) { - Image( - value, - contentDescription, - modifier, - alignment, - contentScale, - alpha, - colorFilter - ) - } else if (onLoading != null) { - onLoading(1.0F) - } - } - KamelImageState.Failure -> { - if (onFailure != null) { - onFailure(error.value ?: return@Crossfade) - } - } - } - } - } - } else { - BaseKamelImage( - resource, - contentDescription, - modifier, - alignment, - contentScale, - alpha, - colorFilter, - onLoading, - onFailure, - contentAlignment, - null - ) - } -}