From 0faa35a07bfc1f07aa49c993534df60eb5f1d186 Mon Sep 17 00:00:00 2001 From: t-regbs Date: Wed, 6 May 2026 16:49:03 +0100 Subject: [PATCH 1/3] feat: implemented error banner handling for web import action failures --- .../valkyrie/jewel/banner/BannerHost.kt | 22 +++++++++--- .../valkyrie/jewel/banner/BannerMessage.kt | 9 +++++ .../webimport/common/StandardIconViewModel.kt | 32 +++++++++++++++++ .../common/StandardImportScreenUI.kt | 35 +++++++++++++++++++ .../webimport/common/WebFailureReason.kt | 15 ++++++++ .../webimport/common/WebIconViewModel.kt | 19 ++++++++++ .../webimport/svg/common/SvgImportScreenUI.kt | 13 +++++++ .../resources/messages/Valkyrie.properties | 6 ++++ 8 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebFailureReason.kt diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerHost.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerHost.kt index 5b4f5b36a..b4e15b015 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerHost.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerHost.kt @@ -30,6 +30,7 @@ import org.jetbrains.jewel.ui.component.InlineErrorBanner import org.jetbrains.jewel.ui.component.InlineSuccessBanner import org.jetbrains.jewel.ui.component.InlineWarningBanner import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.banner.BannerLinkActionScope @Composable fun BannerHost( @@ -73,10 +74,23 @@ fun BannerHost( @Composable private fun BannerRender(bannerData: BannerData) { - when (val message = bannerData.message) { - is SuccessBanner -> InlineSuccessBanner(text = message.text) - is WarningBanner -> InlineWarningBanner(text = message.text) - is ErrorBanner -> InlineErrorBanner(text = message.text) + val message = bannerData.message + val linkActions: (BannerLinkActionScope.() -> Unit)? = if (message.actions.isEmpty()) { + null + } else { + { + message.actions.forEach { entry -> + action(entry.label) { + entry.onClick() + bannerData.dismiss() + } + } + } + } + when (message) { + is SuccessBanner -> InlineSuccessBanner(text = message.text, linkActions = linkActions) + is WarningBanner -> InlineWarningBanner(text = message.text, linkActions = linkActions) + is ErrorBanner -> InlineErrorBanner(text = message.text, linkActions = linkActions) } } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerMessage.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerMessage.kt index c60bb8ac2..98a1a48c6 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerMessage.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/banner/BannerMessage.kt @@ -3,23 +3,32 @@ package io.github.composegears.valkyrie.jewel.banner sealed interface BannerMessage { val text: String val duration: BannerDuration + val actions: List data class SuccessBanner( override val text: String, override val duration: BannerDuration = BannerDuration.Short, + override val actions: List = emptyList(), ) : BannerMessage data class WarningBanner( override val text: String, override val duration: BannerDuration = BannerDuration.Short, + override val actions: List = emptyList(), ) : BannerMessage data class ErrorBanner( override val text: String, override val duration: BannerDuration = BannerDuration.Short, + override val actions: List = emptyList(), ) : BannerMessage } +data class BannerAction( + val label: String, + val onClick: () -> Unit, +) + enum class BannerDuration { Short, Long, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardIconViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardIconViewModel.kt index 0850c0079..20016757b 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardIconViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardIconViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import com.composegears.tiamat.navigation.MutableSavedState import com.composegears.tiamat.navigation.asStateFlow import com.composegears.tiamat.navigation.recordOf +import com.intellij.openapi.diagnostic.Logger import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter import io.github.composegears.valkyrie.sdk.core.extensions.safeAs import io.github.composegears.valkyrie.ui.screen.webimport.common.domain.StandardIconProvider @@ -42,6 +43,8 @@ class StandardIconViewModel( private val _events = Channel() val events = _events.receiveAsFlow() + private val logger = Logger.getInstance(StandardIconViewModel::class.java) + private var downloadJob: Job? = null private var fontLoadJob: Job? = null private var prefetchJob: Job? = null @@ -116,6 +119,7 @@ class StandardIconViewModel( selectedStyleId = selectedStyle?.id, ) }.onFailure { error -> + if (error is CancellationException) throw error stateRecord.value = StandardState.Error( "Error loading ${provider.providerName} icons: ${error.message}", ) @@ -144,7 +148,14 @@ class StandardIconViewModel( updateSuccess { it.copy(fontByteArray = bytes) } }.onFailure { error -> if (error is CancellationException) throw error + logger.warn("Failed to load ${provider.providerName} font", error) updateSuccess { it.copy(fontByteArray = null) } + _events.send( + StandardIconEvent.FontLoadFailed( + providerName = provider.providerName, + reason = error.toWebFailureReason(), + ), + ) } } else { updateSuccess { it.copy(fontByteArray = cachedFont) } @@ -166,6 +177,15 @@ class StandardIconViewModel( name = IconNameFormatter.format(icon.exportName), ), ) + }.onFailure { error -> + if (error is CancellationException) throw error + logger.warn("Failed to download ${provider.providerName} icon", error) + _events.send( + StandardIconEvent.IconDownloadFailed( + providerName = provider.providerName, + reason = error.toWebFailureReason(), + ), + ) } } } @@ -243,6 +263,8 @@ class StandardIconViewModel( if (fontCache[style.id] == null) { runCatching { provider.loadFontBytes(style) + }.onFailure { error -> + if (error is CancellationException) throw error }.onSuccess { bytes -> fontCache[style.id] = bytes } @@ -268,6 +290,16 @@ sealed interface StandardIconEvent { val svgContent: String, val name: String, ) : StandardIconEvent + + data class IconDownloadFailed( + val providerName: String, + val reason: WebFailureReason, + ) : StandardIconEvent + + data class FontLoadFailed( + val providerName: String, + val reason: WebFailureReason, + ) : StandardIconEvent } @Stable diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt index b6bd15fca..1e39aee3e 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt @@ -34,6 +34,10 @@ import io.github.composegears.valkyrie.jewel.BackAction import io.github.composegears.valkyrie.jewel.HorizontalDivider import io.github.composegears.valkyrie.jewel.Title import io.github.composegears.valkyrie.jewel.Toolbar +import io.github.composegears.valkyrie.jewel.banner.BannerAction +import io.github.composegears.valkyrie.jewel.banner.BannerDuration +import io.github.composegears.valkyrie.jewel.banner.BannerMessage.ErrorBanner +import io.github.composegears.valkyrie.jewel.banner.rememberBannerManager import io.github.composegears.valkyrie.jewel.ui.placeholder.EmptyPlaceholder import io.github.composegears.valkyrie.jewel.ui.placeholder.ErrorPlaceholder import io.github.composegears.valkyrie.jewel.ui.placeholder.LoadingPlaceholder @@ -60,6 +64,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.SidePanel import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.WebIconTopActions import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.ZOOM_DEFAULT_SCALE import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.ZoomFloatingBar +import io.github.composegears.valkyrie.util.ValkyrieBundle.message import io.github.composegears.valkyrie.util.stringResource import kotlinx.coroutines.launch import org.jetbrains.jewel.foundation.theme.LocalContentColor @@ -78,6 +83,7 @@ internal fun StandardImportScreen( } val state by viewModel.state.collectAsState() val variableFontConfig by provider.variableFontConfig.collectAsState() + val bannerManager = rememberBannerManager() val currentOnIconDownloaded by rememberUpdatedState(onIconDownload) ObserveEvent(viewModel.events) { event -> @@ -85,6 +91,35 @@ internal fun StandardImportScreen( is StandardIconEvent.IconDownloaded -> { currentOnIconDownloaded(event) } + is StandardIconEvent.IconDownloadFailed -> { + bannerManager.show( + message = ErrorBanner( + text = message( + "web.import.error.icon.download", + event.providerName, + message(event.reason.bundleKey), + ), + ), + ) + } + is StandardIconEvent.FontLoadFailed -> { + bannerManager.show( + message = ErrorBanner( + text = message( + "web.import.error.font.load", + event.providerName, + message(event.reason.bundleKey), + ), + duration = BannerDuration.Indefinite, + actions = listOf( + BannerAction( + label = message("web.import.error.retry"), + onClick = { viewModel.downloadFont() }, + ), + ), + ), + ) + } } } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebFailureReason.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebFailureReason.kt new file mode 100644 index 000000000..f152f3f07 --- /dev/null +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebFailureReason.kt @@ -0,0 +1,15 @@ +package io.github.composegears.valkyrie.ui.screen.webimport.common + +import java.io.IOException + +enum class WebFailureReason(val bundleKey: String) { + Network("web.import.error.reason.network"), + Server("web.import.error.reason.server"), + Unknown("web.import.error.reason.unknown"), +} + +fun Throwable.toWebFailureReason(): WebFailureReason = when (this) { + is IOException -> WebFailureReason.Network + is IllegalStateException -> WebFailureReason.Server + else -> WebFailureReason.Unknown +} diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebIconViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebIconViewModel.kt index f45ebf00b..91b29e386 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebIconViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/WebIconViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import com.composegears.tiamat.navigation.MutableSavedState import com.composegears.tiamat.navigation.asStateFlow import com.composegears.tiamat.navigation.recordOf +import com.intellij.openapi.diagnostic.Logger import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter import io.github.composegears.valkyrie.sdk.core.extensions.safeAs import io.github.composegears.valkyrie.ui.screen.webimport.common.domain.WebIconProvider @@ -16,6 +17,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.domain.icon.St import io.github.composegears.valkyrie.ui.screen.webimport.common.domain.icon.WebIconConfig import io.github.composegears.valkyrie.ui.screen.webimport.common.domain.settings.SizeSettings import io.github.composegears.valkyrie.ui.screen.webimport.common.util.filterByCategory +import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel @@ -36,6 +38,8 @@ class WebIconViewModel>( private val _events = Channel() val events = _events.receiveAsFlow() + private val logger = Logger.getInstance(WebIconViewModel::class.java) + private var downloadJob: Job? = null init { @@ -81,6 +85,7 @@ class WebIconViewModel>( selectedStyle = selectedStyle, ) }.onFailure { error -> + if (error is CancellationException) throw error stateRecord.value = WebIconState.Error( "Error loading ${provider.providerName} icons: ${error.message}", ) @@ -100,6 +105,15 @@ class WebIconViewModel>( name = IconNameFormatter.format(icon.exportName), ), ) + }.onFailure { error -> + if (error is CancellationException) throw error + logger.warn("Failed to download ${provider.providerName} icon", error) + _events.send( + WebIconEvent.IconDownloadFailed( + providerName = provider.providerName, + reason = error.toWebFailureReason(), + ), + ) } } } @@ -157,6 +171,11 @@ sealed interface WebIconEvent { val svgContent: String, val name: String, ) : WebIconEvent + + data class IconDownloadFailed( + val providerName: String, + val reason: WebFailureReason, + ) : WebIconEvent } @Stable diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt index 00d82fed6..4507e0d66 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt @@ -34,6 +34,8 @@ import io.github.composegears.valkyrie.jewel.BackAction import io.github.composegears.valkyrie.jewel.HorizontalDivider import io.github.composegears.valkyrie.jewel.Title import io.github.composegears.valkyrie.jewel.Toolbar +import io.github.composegears.valkyrie.jewel.banner.BannerMessage.ErrorBanner +import io.github.composegears.valkyrie.jewel.banner.rememberBannerManager import io.github.composegears.valkyrie.jewel.ui.placeholder.EmptyPlaceholder import io.github.composegears.valkyrie.jewel.ui.placeholder.ErrorPlaceholder import io.github.composegears.valkyrie.jewel.ui.placeholder.LoadingPlaceholder @@ -65,6 +67,7 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.ZoomFloatin import io.github.composegears.valkyrie.ui.screen.webimport.svg.common.data.SvgPreviewCache import io.github.composegears.valkyrie.ui.screen.webimport.svg.common.domain.SvgIconProvider import io.github.composegears.valkyrie.ui.screen.webimport.svg.common.model.SvgIcon +import io.github.composegears.valkyrie.util.ValkyrieBundle.message import io.github.composegears.valkyrie.util.stringResource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.currentCoroutineContext @@ -87,11 +90,21 @@ internal fun SvgImportScreen( WebIconViewModel(savedState = it, provider = provider) } val state by viewModel.state.collectAsState() + val bannerManager = rememberBannerManager() val currentOnIconDownloaded by rememberUpdatedState(onIconDownload) ObserveEvent(viewModel.events) { event -> when (event) { is WebIconEvent.IconDownloaded -> currentOnIconDownloaded(event) + is WebIconEvent.IconDownloadFailed -> bannerManager.show( + message = ErrorBanner( + text = message( + "web.import.error.icon.download", + event.providerName, + message(event.reason.bundleKey), + ), + ), + ) } } diff --git a/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties b/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties index e519cd22f..ff016b474 100644 --- a/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties +++ b/tools/idea-plugin/src/main/resources/messages/Valkyrie.properties @@ -149,6 +149,12 @@ web.import.selector.simpleicons.title=Simple Icons web.import.selector.simpleicons.description=Brand SVG icons from Simple Icons. Check each brand's usage guidelines before using its logo. web.import.placeholder.loading=Loading icons... web.import.placeholder.empty=No icons found +web.import.error.icon.download=Failed to download {0} icon ({1}) +web.import.error.font.load=Failed to load {0} font ({1}) +web.import.error.reason.network=Network error +web.import.error.reason.server=Server error +web.import.error.reason.unknown=Unknown error +web.import.error.retry=Retry web.import.search.placeholder=Search icons web.import.font.customize.header=Customize web.import.font.customize.reset=Reset From 3f91fc0b117d7455764f0cbc23ce2fdb1ad059fc Mon Sep 17 00:00:00 2001 From: t-regbs Date: Wed, 6 May 2026 16:54:58 +0100 Subject: [PATCH 2/3] chore: update changelog --- tools/idea-plugin/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/idea-plugin/CHANGELOG.md b/tools/idea-plugin/CHANGELOG.md index b55f55452..bebd91186 100644 --- a/tools/idea-plugin/CHANGELOG.md +++ b/tools/idea-plugin/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- [Web Import] Add error banner for icon loading/download - [Web Import] Add `css.gg` icons provider - [Web Import] Add `Octicons` icons provider - [Web Import] Add `Simple` icons provider From da62f8bfb455b53e540ed18f979c79e9766d6e3e Mon Sep 17 00:00:00 2001 From: t-regbs Date: Sun, 10 May 2026 23:47:16 +0100 Subject: [PATCH 3/3] fix: fix import selection reset on failed downloads --- .../common/StandardImportScreenUI.kt | 18 ++++++---- .../webimport/svg/common/SvgImportScreenUI.kt | 35 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt index 1e39aee3e..151cc6d7a 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/StandardImportScreenUI.kt @@ -85,6 +85,7 @@ internal fun StandardImportScreen( val variableFontConfig by provider.variableFontConfig.collectAsState() val bannerManager = rememberBannerManager() val currentOnIconDownloaded by rememberUpdatedState(onIconDownload) + var selectedIcon by rememberMutableState { null } ObserveEvent(viewModel.events) { event -> when (event) { @@ -92,6 +93,7 @@ internal fun StandardImportScreen( currentOnIconDownloaded(event) } is StandardIconEvent.IconDownloadFailed -> { + selectedIcon = null bannerManager.show( message = ErrorBanner( text = message( @@ -130,8 +132,12 @@ internal fun StandardImportScreen( resolveFontWeight = provider::resolveFontWeight, variableFontConfig = variableFontConfig, customizationContent = customizationContent, + selectedIcon = selectedIcon, onBack = onBack, - onSelectIcon = viewModel::downloadIcon, + onSelectIcon = { icon -> + selectedIcon = icon + viewModel.downloadIcon(icon) + }, onSelectCategory = viewModel::selectCategory, onSelectStyle = viewModel::selectStyle, onSearchQueryChange = viewModel::updateSearchQuery, @@ -148,6 +154,7 @@ private fun StandardImportScreenUI( resolveFontWeight: (IconStyle?) -> FontWeight, variableFontConfig: VariableFontConfig?, customizationContent: (@Composable (onClose: () -> Unit) -> Unit)?, + selectedIcon: StandardIcon?, onBack: () -> Unit, onSelectIcon: (StandardIcon) -> Unit, onSelectCategory: (InferredCategory) -> Unit, @@ -199,6 +206,7 @@ private fun StandardImportScreenUI( resolveFontWeight = resolveFontWeight, variableFontConfig = variableFontConfig, customizationContent = customizationContent, + selectedIcon = selectedIcon, onSelectIcon = onSelectIcon, onSelectCategory = onSelectCategory, onSelectStyle = onSelectStyle, @@ -218,6 +226,7 @@ private fun IconsContent( resolveFontWeight: (IconStyle?) -> FontWeight, variableFontConfig: VariableFontConfig?, customizationContent: (@Composable (onClose: () -> Unit) -> Unit)?, + selectedIcon: StandardIcon?, onSelectIcon: (StandardIcon) -> Unit, onSelectCategory: (InferredCategory) -> Unit, onSelectStyle: (IconStyle) -> Unit, @@ -225,8 +234,6 @@ private fun IconsContent( onSettingsChange: (SizeSettings) -> Unit, ) { val scope = rememberCoroutineScope() - - var selectedIcon by rememberMutableState { null } var isSidePanelOpen by rememberMutableState { false } var scaleFactor by rememberMutableState { ZOOM_DEFAULT_SCALE } val lazyGridState = rememberLazyGridState() @@ -316,10 +323,7 @@ private fun IconsContent( IconCard( name = icon.displayName, selected = icon == selectedIcon, - onClick = { - selectedIcon = icon - onSelectIcon(icon) - }, + onClick = { onSelectIcon(icon) }, iconContent = { FontIcon( modifier = Modifier.size(iconSizeDp), diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt index 4507e0d66..03b2640a1 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/svg/common/SvgImportScreenUI.kt @@ -92,27 +92,35 @@ internal fun SvgImportScreen( val state by viewModel.state.collectAsState() val bannerManager = rememberBannerManager() val currentOnIconDownloaded by rememberUpdatedState(onIconDownload) + var selectedIcon by rememberMutableState { null } ObserveEvent(viewModel.events) { event -> when (event) { is WebIconEvent.IconDownloaded -> currentOnIconDownloaded(event) - is WebIconEvent.IconDownloadFailed -> bannerManager.show( - message = ErrorBanner( - text = message( - "web.import.error.icon.download", - event.providerName, - message(event.reason.bundleKey), + is WebIconEvent.IconDownloadFailed -> { + selectedIcon = null + bannerManager.show( + message = ErrorBanner( + text = message( + "web.import.error.icon.download", + event.providerName, + message(event.reason.bundleKey), + ), ), - ), - ) + ) + } } } SvgImportScreenUI( state = state, title = title, + selectedIcon = selectedIcon, onBack = onBack, - onSelectIcon = viewModel::downloadIcon, + onSelectIcon = { icon -> + selectedIcon = icon + viewModel.downloadIcon(icon) + }, onSelectCategory = viewModel::selectCategory, onSelectStyle = viewModel::selectStyle, onSearchQueryChange = viewModel::updateSearchQuery, @@ -126,6 +134,7 @@ internal fun SvgImportScreen( private fun SvgImportScreenUI( state: WebIconState, title: String, + selectedIcon: SvgIcon?, onBack: () -> Unit, onSelectIcon: (SvgIcon) -> Unit, onSelectCategory: (InferredCategory) -> Unit, @@ -163,6 +172,7 @@ private fun SvgImportScreenUI( } is WebIconState.Success -> SvgIconsContent( state = current, + selectedIcon = selectedIcon, onSelectIcon = onSelectIcon, onSelectCategory = onSelectCategory, onSelectStyle = onSelectStyle, @@ -178,6 +188,7 @@ private fun SvgImportScreenUI( @Composable private fun SvgIconsContent( state: WebIconState.Success, + selectedIcon: SvgIcon?, onSelectIcon: (SvgIcon) -> Unit, onSelectCategory: (InferredCategory) -> Unit, onSelectStyle: (IconStyle) -> Unit, @@ -186,7 +197,6 @@ private fun SvgIconsContent( loadPreviewSvg: suspend (SvgIcon) -> String, ) { val scope = rememberCoroutineScope() - var selectedIcon by rememberMutableState { null } var isSidePanelOpen by rememberMutableState { false } var scaleFactor by rememberMutableState { ZOOM_DEFAULT_SCALE } val lazyGridState = rememberLazyGridState() @@ -244,10 +254,7 @@ private fun SvgIconsContent( IconCard( name = icon.displayName, selected = icon == selectedIcon, - onClick = { - selectedIcon = icon - onSelectIcon(icon) - }, + onClick = { onSelectIcon(icon) }, iconContent = { SvgIconPreview( icon = icon,