diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index dc71584c..21c7c8c3 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -206,6 +206,7 @@ kotlin { implementation(libs.androidx.activity.compose) implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.work.runtime) + implementation(libs.coil.gif) implementation("androidx.recyclerview:recyclerview:1.4.0") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.11.0") diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.android.kt new file mode 100644 index 00000000..cda2e814 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.android.kt @@ -0,0 +1,15 @@ +package com.nuvio.app.core.ui + +import android.os.Build +import coil3.ImageLoader +import coil3.gif.AnimatedImageDecoder +import coil3.gif.GifDecoder + +internal actual fun ImageLoader.Builder.configurePlatformImageLoader(): ImageLoader.Builder = + components { + if (Build.VERSION.SDK_INT >= 28) { + add(AnimatedImageDecoder.Factory()) + } else { + add(GifDecoder.Factory()) + } + } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.android.kt new file mode 100644 index 00000000..0bd54dcb --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.android.kt @@ -0,0 +1,34 @@ +package com.nuvio.app.features.home.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import coil3.compose.AsyncImage +import coil3.request.ImageRequest + +@Composable +internal actual fun CollectionCardRemoteImage( + imageUrl: String, + contentDescription: String, + modifier: Modifier, + contentScale: ContentScale, + animateIfPossible: Boolean, +) { + val context = LocalContext.current + val request: ImageRequest = remember(context, imageUrl) { + ImageRequest.Builder(context) + .data(imageUrl) + .memoryCacheKey("home-collection:$imageUrl") + .diskCacheKey(imageUrl) + .build() + } + + AsyncImage( + model = request, + contentDescription = contentDescription, + modifier = modifier, + contentScale = contentScale, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 68414ba8..801435c8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -74,6 +74,7 @@ import com.nuvio.app.core.sync.SyncManager import com.nuvio.app.core.ui.NuvioNavigationBar import com.nuvio.app.core.ui.NuvioPosterActionSheet import com.nuvio.app.core.ui.PlatformBackHandler +import com.nuvio.app.core.ui.configurePlatformImageLoader import com.nuvio.app.core.ui.TraktListPickerDialog import com.nuvio.app.core.ui.NuvioTheme import com.nuvio.app.features.auth.AuthScreen @@ -248,6 +249,7 @@ fun App() { .crossfade(true) .diskCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED) + .configurePlatformImageLoader() .build() } val selectedTheme by remember { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.kt new file mode 100644 index 00000000..925ce3ba --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.kt @@ -0,0 +1,5 @@ +package com.nuvio.app.core.ui + +import coil3.ImageLoader + +internal expect fun ImageLoader.Builder.configurePlatformImageLoader(): ImageLoader.Builder \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt index 32297191..0da4e0c2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt @@ -14,6 +14,7 @@ data class CollectionEditorUiState( val title: String = "", val backdropImageUrl: String = "", val pinToTop: Boolean = false, + val focusGlowEnabled: Boolean = true, val viewMode: FolderViewMode = FolderViewMode.TABBED_GRID, val showAllTab: Boolean = true, val folders: List = emptyList(), @@ -43,6 +44,7 @@ object CollectionEditorRepository { title = existing.title, backdropImageUrl = existing.backdropImageUrl.orEmpty(), pinToTop = existing.pinToTop, + focusGlowEnabled = existing.focusGlowEnabled, viewMode = existing.folderViewMode, showAllTab = existing.showAllTab, folders = existing.folders, @@ -59,6 +61,7 @@ object CollectionEditorRepository { title = "", backdropImageUrl = "", pinToTop = false, + focusGlowEnabled = true, viewMode = FolderViewMode.TABBED_GRID, showAllTab = true, folders = emptyList(), @@ -83,6 +86,10 @@ object CollectionEditorRepository { _uiState.value = _uiState.value.copy(pinToTop = pinToTop) } + fun setFocusGlowEnabled(enabled: Boolean) { + _uiState.value = _uiState.value.copy(focusGlowEnabled = enabled) + } + fun setViewMode(viewMode: FolderViewMode) { _uiState.value = _uiState.value.copy(viewMode = viewMode) } @@ -145,6 +152,20 @@ object CollectionEditorRepository { ) } + fun updateFolderFocusGifUrl(url: String) { + val folder = _uiState.value.editingFolder ?: return + _uiState.value = _uiState.value.copy( + editingFolder = folder.copy(focusGifUrl = url.ifBlank { null }), + ) + } + + fun updateFolderFocusGifEnabled(enabled: Boolean) { + val folder = _uiState.value.editingFolder ?: return + _uiState.value = _uiState.value.copy( + editingFolder = folder.copy(focusGifEnabled = enabled), + ) + } + fun updateFolderCoverEmoji(emoji: String) { val folder = _uiState.value.editingFolder ?: return _uiState.value = _uiState.value.copy( @@ -256,6 +277,7 @@ object CollectionEditorRepository { title = state.title.trim(), backdropImageUrl = state.backdropImageUrl.ifBlank { null }, pinToTop = state.pinToTop, + focusGlowEnabled = state.focusGlowEnabled, viewMode = state.viewMode.name, showAllTab = state.showAllTab, folders = state.folders, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt index 71448945..ab98406f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt @@ -150,6 +150,43 @@ fun CollectionEditorScreen( } } + // Focus Glow + item { + NuvioSurfaceCard { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { CollectionEditorRepository.setFocusGlowEnabled(!state.focusGlowEnabled) }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) { + Text( + text = "Always-On Card Glow", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = "Show glow for home folder cards all the time on touch devices.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Switch( + checked = state.focusGlowEnabled, + onCheckedChange = { CollectionEditorRepository.setFocusGlowEnabled(it) }, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant, + uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant, + ), + ) + } + } + } + // View Mode item { NuvioSurfaceCard { @@ -479,6 +516,38 @@ private fun FolderEditorSheet( placeholder = "Image URL", ) } + Spacer(modifier = Modifier.height(12.dp)) + NuvioInputField( + value = folder.focusGifUrl.orEmpty(), + onValueChange = { CollectionEditorRepository.updateFolderFocusGifUrl(it) }, + placeholder = "Always-play GIF URL (optional)", + ) + Spacer(modifier = Modifier.height(12.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + CollectionEditorRepository.updateFolderFocusGifEnabled(!folder.focusGifEnabled) + } + .padding(vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Show GIF When Configured", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface, + ) + Switch( + checked = folder.focusGifEnabled, + onCheckedChange = { CollectionEditorRepository.updateFolderFocusGifEnabled(it) }, + colors = SwitchDefaults.colors( + checkedThumbColor = MaterialTheme.colorScheme.onPrimary, + checkedTrackColor = MaterialTheme.colorScheme.primary, + ), + ) + } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt index 96df5e78..1130c8e8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt @@ -35,6 +35,8 @@ data class CollectionFolder( val id: String, val title: String, val coverImageUrl: String? = null, + val focusGifUrl: String? = null, + val focusGifEnabled: Boolean = true, val coverEmoji: String? = null, val tileShape: String = "Poster", val hideTitle: Boolean = false, @@ -56,6 +58,7 @@ data class Collection( val title: String, val backdropImageUrl: String? = null, val pinToTop: Boolean = false, + val focusGlowEnabled: Boolean = true, val viewMode: String = "TABBED_GRID", val showAllTab: Boolean = true, val folders: List = emptyList(), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.kt new file mode 100644 index 00000000..14741764 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.kt @@ -0,0 +1,14 @@ +package com.nuvio.app.features.home.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale + +@Composable +internal expect fun CollectionCardRemoteImage( + imageUrl: String, + contentDescription: String, + modifier: Modifier = Modifier, + contentScale: ContentScale = ContentScale.Crop, + animateIfPossible: Boolean = false, +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt index 23f578bd..6b9b2d01 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt @@ -1,6 +1,5 @@ package com.nuvio.app.features.home.components -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -8,23 +7,25 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text 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.layout.ContentScale import androidx.compose.ui.text.style.TextAlign 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 coil3.compose.AsyncImage import com.nuvio.app.core.ui.NuvioShelfSection +import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.features.collection.Collection import com.nuvio.app.features.collection.CollectionFolder import com.nuvio.app.features.home.PosterShape @@ -84,40 +85,55 @@ private fun CollectionFolderCard( modifier = modifier.width(cardWidth), verticalArrangement = Arrangement.spacedBy(6.dp), ) { - Box( + val shapeCorner = RoundedCornerShape(16.dp) + val imageUrl = collectionFolderCardImageUrl(folder) + Card( modifier = Modifier .fillMaxWidth() - .aspectRatio(aspectRatio) - .clip(RoundedCornerShape(16.dp)) - .background(MaterialTheme.colorScheme.surface) - .then( - if (onClick != null) Modifier.clickable(onClick = onClick) - else Modifier - ), - contentAlignment = Alignment.Center, + .aspectRatio(aspectRatio), + shape = shapeCorner, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + elevation = CardDefaults.cardElevation( + defaultElevation = 2.dp, + ), ) { - when { - !folder.coverImageUrl.isNullOrBlank() -> { - AsyncImage( - model = folder.coverImageUrl, - contentDescription = folder.title, + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + when { + !imageUrl.isNullOrBlank() -> { + CollectionCardRemoteImage( + imageUrl = imageUrl, + contentDescription = folder.title, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + animateIfPossible = isAnimatedCollectionFolderImage(folder, imageUrl), + ) + } + !folder.coverEmoji.isNullOrBlank() -> { + Text( + text = folder.coverEmoji, + fontSize = 36.sp, + ) + } + else -> { + Text( + text = folder.title.take(2).uppercase(), + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + ) + } + } + + if (onClick != null) { + Box( modifier = Modifier - .matchParentSize(), - contentScale = ContentScale.Crop, - ) - } - !folder.coverEmoji.isNullOrBlank() -> { - Text( - text = folder.coverEmoji, - fontSize = 36.sp, - ) - } - else -> { - Text( - text = folder.title.take(2).uppercase(), - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, + .fillMaxSize() + .posterCardClickable(onClick = onClick, onLongClick = null), ) } } @@ -134,3 +150,23 @@ private fun CollectionFolderCard( } } } + +private fun collectionFolderCardImageUrl(folder: CollectionFolder): String? { + return if (folder.focusGifEnabled) { + firstNonBlank(folder.focusGifUrl, folder.coverImageUrl) + } else { + firstNonBlank(folder.coverImageUrl) + } +} + +private fun firstNonBlank(vararg candidates: String?): String? { + return candidates.firstOrNull { !it.isNullOrBlank() }?.trim() +} + +private fun isAnimatedCollectionFolderImage( + folder: CollectionFolder, + imageUrl: String, +): Boolean { + val gifUrl = firstNonBlank(folder.focusGifUrl) ?: return false + return folder.focusGifEnabled && imageUrl == gifUrl +} diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.ios.kt new file mode 100644 index 00000000..3240c6e3 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/ui/PlatformImageLoader.ios.kt @@ -0,0 +1,5 @@ +package com.nuvio.app.core.ui + +import coil3.ImageLoader + +internal actual fun ImageLoader.Builder.configurePlatformImageLoader(): ImageLoader.Builder = this \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.ios.kt new file mode 100644 index 00000000..368bc8b2 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/home/components/CollectionCardRemoteImage.ios.kt @@ -0,0 +1,166 @@ +package com.nuvio.app.features.home.components + +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.interop.UIKitView +import androidx.compose.ui.layout.ContentScale +import coil3.compose.AsyncImage +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.darwin.Darwin +import io.ktor.client.request.get +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.reinterpret +import platform.CoreGraphics.CGImageRef +import platform.CoreFoundation.CFDataCreate +import platform.ImageIO.CGImageSourceCreateImageAtIndex +import platform.ImageIO.CGImageSourceCreateWithData +import platform.ImageIO.CGImageSourceGetCount +import platform.UIKit.UIImage +import platform.UIKit.UIImageView +import platform.UIKit.UIViewContentMode +import platform.CoreGraphics.CGImageRelease +import kotlinx.cinterop.usePinned + +private val gifHttpClient = HttpClient(Darwin) +private val gifDecodeScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) +private const val MaxCachedGifImages = 12 +private val gifImageCache = mutableMapOf() +private val gifImageCacheOrder = mutableListOf() +private val gifImageInFlight = mutableMapOf>() + +@OptIn(ExperimentalForeignApi::class) +@Composable +internal actual fun CollectionCardRemoteImage( + imageUrl: String, + contentDescription: String, + modifier: Modifier, + contentScale: ContentScale, + animateIfPossible: Boolean, +) { + if (!animateIfPossible) { + AsyncImage( + model = imageUrl, + contentDescription = contentDescription, + modifier = modifier, + contentScale = contentScale, + ) + return + } + + var gifImage by remember(imageUrl) { mutableStateOf(cachedGifImage(imageUrl)) } + + LaunchedEffect(imageUrl) { + gifImage = loadGifImage(imageUrl) + } + + UIKitView( + modifier = modifier, + factory = { + UIImageView().apply { + contentMode = UIViewContentMode.UIViewContentModeScaleAspectFill + clipsToBounds = true + userInteractionEnabled = false + image = gifImage + tag = imageUrl.hashCode().toLong() + } + }, + update = { imageView -> + if (imageView.tag != imageUrl.hashCode().toLong()) { + imageView.tag = imageUrl.hashCode().toLong() + } + imageView.image = gifImage + }, + ) +} + +private fun cachedGifImage(imageUrl: String): UIImage? { + val image = gifImageCache[imageUrl] ?: return null + gifImageCacheOrder.remove(imageUrl) + gifImageCacheOrder.add(imageUrl) + return image +} + +private fun storeGifImage(imageUrl: String, image: UIImage) { + gifImageCache[imageUrl] = image + gifImageCacheOrder.remove(imageUrl) + gifImageCacheOrder.add(imageUrl) + + while (gifImageCacheOrder.size > MaxCachedGifImages) { + val eldestKey = gifImageCacheOrder.removeFirstOrNull() ?: break + gifImageCache.remove(eldestKey) + } +} + +@OptIn(ExperimentalForeignApi::class) +private suspend fun loadGifImage(imageUrl: String): UIImage? { + cachedGifImage(imageUrl)?.let { return it } + + val request = gifImageInFlight[imageUrl] ?: gifDecodeScope.async { + runCatching { + val bytes = gifHttpClient.get(imageUrl).body() + bytes + .takeIf { it.isNotEmpty() } + ?.toCFData() + ?.let { UIImage.gifImageWithData(it) } + }.getOrNull() + }.also { gifImageInFlight[imageUrl] = it } + + val image = try { + request.await() + } finally { + if (gifImageInFlight[imageUrl] === request) { + gifImageInFlight.remove(imageUrl) + } + } + + if (image != null) { + storeGifImage(imageUrl, image) + } + + return image +} + +@OptIn(ExperimentalForeignApi::class) +private fun ByteArray.toCFData() = + usePinned { pinned -> + CFDataCreate( + allocator = null, + bytes = pinned.addressOf(0).reinterpret(), + length = size.toLong(), + ) + } + +@OptIn(ExperimentalForeignApi::class) +private fun UIImage.Companion.gifImageWithData(data: kotlinx.cinterop.CPointer?): UIImage? { + return runCatching { + val source = data?.let { CGImageSourceCreateWithData(it, null) } ?: return null + val count = CGImageSourceGetCount(source).toInt() + val frames = mutableListOf() + + for (index in 0 until count) { + val imageRef: CGImageRef = CGImageSourceCreateImageAtIndex(source, index.toULong(), null) ?: continue + try { + frames.add(UIImage.imageWithCGImage(imageRef)) + } finally { + CGImageRelease(imageRef) + } + } + + if (frames.isEmpty()) return null + + val durationSeconds = (count * 0.1).coerceAtLeast(0.1) + UIImage.animatedImageWithImages(frames, durationSeconds) + }.getOrNull() +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e3594271..e8d79422 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,6 +48,7 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMul compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" } compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } diff --git a/iosApp/Configuration/Version.xcconfig b/iosApp/Configuration/Version.xcconfig index f9bf45cb..5f95b419 100644 --- a/iosApp/Configuration/Version.xcconfig +++ b/iosApp/Configuration/Version.xcconfig @@ -1,2 +1,2 @@ -CURRENT_PROJECT_VERSION=10 -MARKETING_VERSION=0.1.0-alpha10 \ No newline at end of file +CURRENT_PROJECT_VERSION=11 +MARKETING_VERSION=0.1.0-alpha11 \ No newline at end of file