Skip to content

Commit 372c964

Browse files
committed
[Simple mode] Handle errors during parsing
1 parent 8bb9801 commit 372c964

File tree

5 files changed

+184
-105
lines changed

5 files changed

+184
-105
lines changed

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/SimpleConversionScreen.kt

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package io.github.composegears.valkyrie.ui.screen.mode.simple.conversion
22

33
import androidx.compose.desktop.ui.tooling.preview.Preview
4+
import androidx.compose.foundation.layout.Column
45
import androidx.compose.foundation.layout.fillMaxSize
56
import androidx.compose.runtime.Composable
6-
import androidx.compose.runtime.LaunchedEffect
77
import androidx.compose.runtime.collectAsState
88
import androidx.compose.runtime.getValue
9+
import androidx.compose.ui.Alignment
910
import androidx.compose.ui.Modifier
1011
import com.composegears.tiamat.compose.back
1112
import com.composegears.tiamat.compose.navArgs
@@ -14,7 +15,12 @@ import com.composegears.tiamat.compose.navDestination
1415
import com.composegears.tiamat.compose.navigate
1516
import com.composegears.tiamat.compose.saveableViewModel
1617
import io.github.composegears.valkyrie.compose.codeviewer.KotlinCodeViewer
18+
import io.github.composegears.valkyrie.compose.core.layout.WeightSpacer
1719
import io.github.composegears.valkyrie.ui.domain.model.PreviewType
20+
import io.github.composegears.valkyrie.ui.foundation.AppBarTitle
21+
import io.github.composegears.valkyrie.ui.foundation.BackAction
22+
import io.github.composegears.valkyrie.ui.foundation.SettingsAction
23+
import io.github.composegears.valkyrie.ui.foundation.TopAppBar
1824
import io.github.composegears.valkyrie.ui.foundation.conversion.GenericConversionScreen
1925
import io.github.composegears.valkyrie.ui.foundation.rememberSnackbar
2026
import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme
@@ -25,13 +31,14 @@ import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.Si
2531
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction.OnCopyInClipboard
2632
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction.OnIconNameChange
2733
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState
34+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState.ConversionState
2835
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.ui.action.EditActionContent
2936
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.ui.action.PreviewActionContent
3037
import io.github.composegears.valkyrie.ui.screen.settings.SettingsScreen
38+
import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.ErrorContent
39+
import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.LoadingContent
3140
import io.github.composegears.valkyrie.util.IR_STUB
3241
import java.nio.file.Path
33-
import kotlinx.coroutines.flow.launchIn
34-
import kotlinx.coroutines.flow.onEach
3542

3643
sealed interface SimpleConversionParamsSource {
3744
data class PathSource(val path: Path) : SimpleConversionParamsSource
@@ -52,36 +59,56 @@ val SimpleConversionScreen by navDestination<SimpleConversionParamsSource> {
5259
val state by viewModel.state.collectAsState()
5360
val settings by viewModel.inMemorySettings.settings.collectAsState()
5461

55-
LaunchedEffect(Unit) {
56-
viewModel.events
57-
.onEach(snackbar::show)
58-
.launchIn(this)
59-
}
60-
61-
state?.let { state ->
62-
SimpleConversionContent(
63-
state = state,
64-
previewType = settings.previewType,
65-
onBack = navController::back,
66-
openSettings = {
67-
navController.navigate(dest = SettingsScreen)
68-
},
69-
onAction = {
70-
when (it) {
71-
is OnCopyInClipboard -> {
72-
copyInClipboard(it.text)
73-
snackbar.show(message = "Copied in clipboard")
62+
when (val state = state) {
63+
is ConversionState -> {
64+
SimpleConversionContent(
65+
state = state,
66+
previewType = settings.previewType,
67+
onBack = navController::back,
68+
openSettings = {
69+
navController.navigate(SettingsScreen)
70+
},
71+
onAction = {
72+
when (it) {
73+
is OnCopyInClipboard -> {
74+
copyInClipboard(it.text)
75+
snackbar.show(message = "Copied in clipboard")
76+
}
77+
is OnIconNameChange -> viewModel.changeIconName(it.name)
7478
}
75-
is OnIconNameChange -> viewModel.changeIconName(it.name)
79+
},
80+
)
81+
}
82+
is SimpleConversionState.Error -> {
83+
Column(modifier = Modifier.fillMaxSize()) {
84+
TopAppBar {
85+
BackAction(onBack = navController::back)
86+
AppBarTitle(title = "Simple conversion")
87+
WeightSpacer()
88+
SettingsAction(
89+
openSettings = {
90+
navController.navigate(SettingsScreen)
91+
},
92+
)
7693
}
77-
},
78-
)
94+
WeightSpacer(weight = 0.3f)
95+
ErrorContent(
96+
modifier = Modifier.align(Alignment.CenterHorizontally),
97+
message = state.message,
98+
description = state.stacktrace,
99+
)
100+
WeightSpacer(weight = 0.7f)
101+
}
102+
}
103+
is SimpleConversionState.Loading -> {
104+
LoadingContent()
105+
}
79106
}
80107
}
81108

82109
@Composable
83110
private fun SimpleConversionContent(
84-
state: SimpleConversionState,
111+
state: ConversionState,
85112
previewType: PreviewType,
86113
onBack: () -> Unit,
87114
openSettings: () -> Unit,
@@ -122,7 +149,7 @@ private fun SimpleConversionContent(
122149
@Composable
123150
private fun SimpleConversionPreviewUiPreview() = PreviewTheme {
124151
SimpleConversionContent(
125-
state = SimpleConversionState(
152+
state = ConversionState(
126153
iconSource = IconSource.StringBasedIcon(""),
127154
iconContent = IconContent(
128155
name = "IconName",

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/SimpleConversionViewModel.kt

Lines changed: 59 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGene
1111
import io.github.composegears.valkyrie.parser.unified.ParserType
1212
import io.github.composegears.valkyrie.parser.unified.SvgXmlParser
1313
import io.github.composegears.valkyrie.parser.unified.ext.toIOPath
14+
import io.github.composegears.valkyrie.sdk.core.extensions.safeAs
1415
import io.github.composegears.valkyrie.ui.di.DI
1516
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconContent
1617
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconSource.FileBasedIcon
1718
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconSource.StringBasedIcon
1819
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState
20+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState.ConversionState
1921
import java.nio.file.Path
2022
import kotlinx.coroutines.Dispatchers
21-
import kotlinx.coroutines.flow.MutableSharedFlow
22-
import kotlinx.coroutines.flow.asSharedFlow
2323
import kotlinx.coroutines.flow.flowOn
2424
import kotlinx.coroutines.flow.launchIn
2525
import kotlinx.coroutines.flow.onEach
@@ -32,47 +32,41 @@ class SimpleConversionViewModel(
3232

3333
val inMemorySettings = inject(DI.core.inMemorySettings)
3434

35-
private val stateRecord = savedState.recordOf<SimpleConversionState?>(
35+
private val stateRecord = savedState.recordOf<SimpleConversionState>(
3636
key = "conversionState",
37-
initialValue = null,
37+
initialValue = SimpleConversionState.Loading,
3838
)
3939
val state = stateRecord.asStateFlow()
4040

41-
private val _events = MutableSharedFlow<String>()
42-
val events = _events.asSharedFlow()
43-
4441
init {
4542
when (params) {
4643
is SimpleConversionParamsSource.PathSource -> selectPath(params.path)
4744
is SimpleConversionParamsSource.TextSource -> fromText(text = params.text, name = params.name)
4845
}
4946
inMemorySettings.settings
5047
.onEach {
51-
val currentState = stateRecord.value ?: return@onEach
48+
val currentState = stateRecord.value.safeAs<ConversionState>() ?: return@onEach
5249

5350
when (val source = currentState.iconSource) {
5451
is FileBasedIcon -> {
55-
val output = parseIcon(
52+
parseIcon(
5653
path = source.path,
5754
iconName = currentState.iconContent.name,
58-
)
59-
if (output != null) {
60-
stateRecord.value = SimpleConversionState(
55+
).onSuccess {
56+
stateRecord.value = ConversionState(
6157
iconSource = FileBasedIcon(source.path),
62-
iconContent = output,
58+
iconContent = it,
6359
)
6460
}
6561
}
6662
is StringBasedIcon -> {
67-
val output = parseIcon(
63+
parseIcon(
6864
text = source.text,
6965
iconName = currentState.iconContent.name,
70-
)
71-
72-
if (output != null) {
73-
stateRecord.value = SimpleConversionState(
66+
).onSuccess {
67+
stateRecord.value = ConversionState(
7468
iconSource = StringBasedIcon(source.text),
75-
iconContent = output,
69+
iconContent = it,
7670
)
7771
}
7872
}
@@ -83,16 +77,19 @@ class SimpleConversionViewModel(
8377
}
8478

8579
fun selectPath(path: Path) = viewModelScope.launch(Dispatchers.Default) {
86-
val output = parseIcon(path)
87-
88-
if (output == null) {
89-
_events.emit("Failed to parse icon")
90-
} else {
91-
stateRecord.value = SimpleConversionState(
92-
iconSource = FileBasedIcon(path),
93-
iconContent = output,
94-
)
95-
}
80+
parseIcon(path)
81+
.onFailure {
82+
stateRecord.value = SimpleConversionState.Error(
83+
message = "Failed to parse icon",
84+
stacktrace = "Error: ${it.message}",
85+
)
86+
}
87+
.onSuccess {
88+
stateRecord.value = ConversionState(
89+
iconSource = FileBasedIcon(path),
90+
iconContent = it,
91+
)
92+
}
9693
}
9794

9895
fun fromText(text: String, name: String) = pasteFromClipboard(text = text, iconName = name)
@@ -101,55 +98,50 @@ class SimpleConversionViewModel(
10198
text: String,
10299
iconName: String = "IconName",
103100
) = viewModelScope.launch(Dispatchers.Default) {
104-
val output = parseIcon(text = text, iconName = iconName)
105-
106-
if (output == null) {
107-
_events.emit("Failed to parse icon from clipboard")
108-
} else {
109-
stateRecord.value = SimpleConversionState(
110-
iconSource = StringBasedIcon(text),
111-
iconContent = output,
112-
)
113-
}
101+
parseIcon(text = text, iconName = iconName)
102+
.onFailure {
103+
stateRecord.value = SimpleConversionState.Error(
104+
message = "Failed to parse icon from clipboard",
105+
stacktrace = "Error: ${it.message}",
106+
)
107+
}
108+
.onSuccess {
109+
stateRecord.value = ConversionState(
110+
iconSource = StringBasedIcon(text),
111+
iconContent = it,
112+
)
113+
}
114114
}
115115

116116
fun changeIconName(name: String) = viewModelScope.launch(Dispatchers.Default) {
117-
val conversionState = stateRecord.value ?: return@launch
117+
val conversionState = stateRecord.value.safeAs<ConversionState>() ?: return@launch
118118

119-
when (conversionState.iconSource) {
119+
when (val source = conversionState.iconSource) {
120120
is FileBasedIcon -> {
121-
val output = parseIcon(
122-
path = conversionState.iconSource.path,
123-
iconName = name,
124-
)
125-
126-
if (output != null) {
127-
stateRecord.value = SimpleConversionState(
128-
iconSource = FileBasedIcon(conversionState.iconSource.path),
129-
iconContent = output,
130-
)
131-
}
121+
parseIcon(path = source.path, iconName = name)
122+
.onSuccess {
123+
stateRecord.value = ConversionState(
124+
iconSource = FileBasedIcon(conversionState.iconSource.path),
125+
iconContent = it,
126+
)
127+
}
132128
}
133129
is StringBasedIcon -> {
134-
val output = parseIcon(
135-
text = conversionState.iconSource.text,
136-
iconName = name,
137-
)
138-
139-
if (output != null) {
140-
stateRecord.value = SimpleConversionState(
141-
iconSource = StringBasedIcon(conversionState.iconSource.text),
142-
iconContent = output,
143-
)
144-
}
130+
parseIcon(text = source.text, iconName = name)
131+
.onSuccess {
132+
stateRecord.value = ConversionState(
133+
iconSource = StringBasedIcon(conversionState.iconSource.text),
134+
iconContent = it,
135+
)
136+
}
145137
}
146138
}
147139
}
148140

149141
private fun parseIcon(
150142
path: Path,
151143
iconName: String? = null,
152-
): IconContent? {
144+
): Result<IconContent> {
153145
return runCatching {
154146
val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path.toIOPath())
155147
val name = iconName ?: parserOutput.iconName
@@ -164,13 +156,13 @@ class SimpleConversionViewModel(
164156
code = output.content,
165157
irImageVector = parserOutput.irImageVector,
166158
)
167-
}.getOrNull()
159+
}
168160
}
169161

170162
private fun parseIcon(
171163
text: String,
172164
iconName: String,
173-
): IconContent? {
165+
): Result<IconContent> {
174166
return runCatching {
175167
val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, text, iconName)
176168

@@ -184,7 +176,7 @@ class SimpleConversionViewModel(
184176
code = output.content,
185177
irImageVector = parserOutput.irImageVector,
186178
)
187-
}.getOrNull()
179+
}
188180
}
189181

190182
private fun createGeneratorConfig(): ImageVectorGeneratorConfig {

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/model/SimpleConversionState.kt renamed to tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/model/ConversionState.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,21 @@ import androidx.compose.runtime.Stable
44
import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector
55
import java.nio.file.Path
66

7-
data class SimpleConversionState(
8-
val iconSource: IconSource,
9-
val iconContent: IconContent,
10-
)
7+
@Stable
8+
sealed interface SimpleConversionState {
9+
10+
data class ConversionState(
11+
val iconSource: IconSource,
12+
val iconContent: IconContent,
13+
) : SimpleConversionState
14+
15+
data class Error(
16+
val message: String,
17+
val stacktrace: String?,
18+
) : SimpleConversionState
19+
20+
data object Loading : SimpleConversionState
21+
}
1122

1223
@Stable
1324
sealed interface IconSource {

0 commit comments

Comments
 (0)