Skip to content

Commit 15cc585

Browse files
committed
Add export functionality for Simple mode and ImageVector to XML tool
1 parent 425a8cc commit 15cc585

14 files changed

Lines changed: 206 additions & 74 deletions

File tree

tools/idea-plugin/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- Add export as file action for `Simple mode` and `ImageVector to XML` tool
8+
59
### Fixed
610

711
- Fix an incorrect error message when the user cancels the save dialog during export

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/foundation/conversion/GenericConversionScreen.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.composegears.valkyrie.ui.foundation.conversion
22

3-
import androidx.compose.foundation.layout.Column
43
import androidx.compose.foundation.layout.Spacer
54
import androidx.compose.foundation.layout.fillMaxSize
65
import androidx.compose.foundation.layout.fillMaxWidth
@@ -11,6 +10,7 @@ import androidx.compose.ui.Modifier
1110
import io.github.composegears.valkyrie.jewel.BackAction
1211
import io.github.composegears.valkyrie.jewel.CopyAction
1312
import io.github.composegears.valkyrie.jewel.EditToggleAction
13+
import io.github.composegears.valkyrie.jewel.ExportAction
1414
import io.github.composegears.valkyrie.jewel.HorizontalDivider
1515
import io.github.composegears.valkyrie.jewel.PreviewIconToggleAction
1616
import io.github.composegears.valkyrie.jewel.SettingsAction
@@ -22,6 +22,7 @@ import io.github.composegears.valkyrie.sdk.compose.foundation.animation.Expanded
2222
import io.github.composegears.valkyrie.sdk.compose.foundation.layout.WeightSpacer
2323
import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState
2424
import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector
25+
import io.github.composegears.valkyrie.ui.foundation.FocusableColumn
2526

2627
/**
2728
* Represents the expanded action panels in conversion screens.
@@ -60,6 +61,7 @@ fun GenericConversionScreen(
6061
language: SyntaxLanguage,
6162
onBack: () -> Unit,
6263
onIconNameChange: (String) -> Unit,
64+
onExport: (String) -> Unit,
6365
onCopyCode: (String) -> Unit,
6466
onOpenSettings: () -> Unit,
6567
editPanel: @Composable (iconName: String, onNameChange: (String) -> Unit) -> Unit,
@@ -69,7 +71,7 @@ fun GenericConversionScreen(
6971
var latestCode by rememberMutableState { codeContent }
7072
var expandedAction by rememberMutableState { ConversionExpandedAction.None }
7173

72-
Column(modifier = modifier.fillMaxSize()) {
74+
FocusableColumn(modifier = modifier.fillMaxSize()) {
7375
Toolbar {
7476
BackAction(onBack = onBack)
7577
Title(text = title)
@@ -86,6 +88,7 @@ fun GenericConversionScreen(
8688
},
8789
selected = expandedAction == ConversionExpandedAction.Preview,
8890
)
91+
ExportAction(onExport = { onExport(latestCode) })
8992
CopyAction(onCopy = { onCopyCode(latestCode) })
9093
SettingsAction(openSettings = onOpenSettings)
9194
}

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

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import androidx.compose.foundation.layout.Box
44
import androidx.compose.foundation.layout.Column
55
import androidx.compose.foundation.layout.fillMaxSize
66
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.LaunchedEffect
78
import androidx.compose.runtime.collectAsState
89
import androidx.compose.runtime.getValue
910
import androidx.compose.ui.Alignment
1011
import androidx.compose.ui.Modifier
12+
import com.composegears.tiamat.compose.TiamatPreview
1113
import com.composegears.tiamat.compose.back
1214
import com.composegears.tiamat.compose.navArgs
1315
import com.composegears.tiamat.compose.navController
@@ -18,30 +20,34 @@ import io.github.composegears.valkyrie.jewel.BackAction
1820
import io.github.composegears.valkyrie.jewel.SettingsAction
1921
import io.github.composegears.valkyrie.jewel.Title
2022
import io.github.composegears.valkyrie.jewel.Toolbar
23+
import io.github.composegears.valkyrie.jewel.banner.BannerMessage.ErrorBanner
2124
import io.github.composegears.valkyrie.jewel.banner.BannerMessage.SuccessBanner
2225
import io.github.composegears.valkyrie.jewel.banner.rememberBannerManager
2326
import io.github.composegears.valkyrie.jewel.editor.SyntaxLanguage
2427
import io.github.composegears.valkyrie.jewel.platform.copyInClipboard
28+
import io.github.composegears.valkyrie.jewel.platform.picker.SaveResult
29+
import io.github.composegears.valkyrie.jewel.platform.picker.rememberFileSaver
2530
import io.github.composegears.valkyrie.jewel.tooling.ProjectPreviewTheme
2631
import io.github.composegears.valkyrie.jewel.ui.placeholder.ErrorPlaceholder
2732
import io.github.composegears.valkyrie.jewel.ui.placeholder.LoadingPlaceholder
2833
import io.github.composegears.valkyrie.sdk.compose.foundation.layout.WeightSpacer
2934
import io.github.composegears.valkyrie.ui.domain.model.PreviewType
3035
import io.github.composegears.valkyrie.ui.foundation.conversion.GenericConversionScreen
31-
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconContent
32-
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconSource
3336
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction
3437
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction.OnCopyInClipboard
38+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction.OnExport
3539
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction.OnIconNameChange
40+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionEvent
3641
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState
3742
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState.ConversionState
3843
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.ui.action.EditActionContent
3944
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.ui.action.PreviewActionContent
4045
import io.github.composegears.valkyrie.ui.screen.settings.SettingsScreen
41-
import io.github.composegears.valkyrie.util.IR_STUB
4246
import io.github.composegears.valkyrie.util.ValkyrieBundle.message
4347
import io.github.composegears.valkyrie.util.stringResource
4448
import java.nio.file.Path
49+
import kotlinx.coroutines.flow.launchIn
50+
import kotlinx.coroutines.flow.onEach
4551
import org.jetbrains.compose.ui.tooling.preview.Preview
4652

4753
sealed interface SimpleConversionParamsSource {
@@ -60,9 +66,39 @@ val SimpleConversionScreen by navDestination<SimpleConversionParamsSource> {
6066
params = params,
6167
)
6268
}
69+
70+
val fileSaver = rememberFileSaver(
71+
title = stringResource("simple.export.dialog.title"),
72+
description = stringResource("simple.export.dialog.description"),
73+
)
74+
6375
val state by viewModel.state.collectAsState()
6476
val settings by viewModel.inMemorySettings.settings.collectAsState()
6577

78+
LaunchedEffect(Unit) {
79+
viewModel.events
80+
.onEach {
81+
when (it) {
82+
is SimpleConversionEvent.ExportKtFile -> {
83+
when (val result = fileSaver.save(it.fileName, it.content)) {
84+
is SaveResult.Success -> bannerManager.show(
85+
message = SuccessBanner(text = message("general.export.success")),
86+
)
87+
is SaveResult.Error -> bannerManager.show(
88+
message = ErrorBanner(text = message("general.export.error", result.message)),
89+
)
90+
is SaveResult.Cancelled -> Unit
91+
}
92+
}
93+
is SimpleConversionEvent.CopyInClipboard -> {
94+
copyInClipboard(it.text)
95+
bannerManager.show(message = SuccessBanner(text = message("general.action.text.copy.clipboard")))
96+
}
97+
}
98+
}
99+
.launchIn(this)
100+
}
101+
66102
when (val state = state) {
67103
is ConversionState -> {
68104
SimpleConversionContent(
@@ -72,15 +108,7 @@ val SimpleConversionScreen by navDestination<SimpleConversionParamsSource> {
72108
openSettings = {
73109
navController.navigate(SettingsScreen)
74110
},
75-
onAction = {
76-
when (it) {
77-
is OnCopyInClipboard -> {
78-
copyInClipboard(it.text)
79-
bannerManager.show(message = SuccessBanner(text = message("general.action.text.copy.clipboard")))
80-
}
81-
is OnIconNameChange -> viewModel.changeIconName(it.name)
82-
}
83-
},
111+
onAction = viewModel::onAction,
84112
)
85113
}
86114
is SimpleConversionState.Error -> {
@@ -131,6 +159,7 @@ private fun SimpleConversionContent(
131159
language = SyntaxLanguage.KOTLIN,
132160
onBack = onBack,
133161
onIconNameChange = { onAction(OnIconNameChange(it)) },
162+
onExport = { onAction(OnExport(it)) },
134163
onCopyCode = { onAction(OnCopyInClipboard(it)) },
135164
onOpenSettings = openSettings,
136165
editPanel = { name, onNameChange ->
@@ -151,41 +180,18 @@ private fun SimpleConversionContent(
151180
@Preview
152181
@Composable
153182
private fun SimpleConversionPreviewUiPreview() = ProjectPreviewTheme {
154-
SimpleConversionContent(
155-
state = ConversionState(
156-
iconSource = IconSource.StringBasedIcon(""),
157-
iconContent = IconContent(
158-
name = "IconName",
159-
irImageVector = IR_STUB,
160-
code = """
161-
import androidx.compose.ui.graphics.vector.ImageVector
162-
import androidx.compose.ui.unit.dp
163-
164-
val WithoutPath: ImageVector
165-
get() {
166-
if (_WithoutPath != null) {
167-
return _WithoutPath!!
168-
}
169-
_WithoutPath = ImageVector.Builder(
170-
name = "WithoutPath",
171-
defaultWidth = 24.dp,
172-
defaultHeight = 24.dp,
173-
viewportWidth = 18f,
174-
viewportHeight = 18f
175-
).build()
176-
177-
return _WithoutPath!!
178-
}
179-
180-
@Suppress("ObjectPropertyName")
181-
private var _WithoutPath: ImageVector? = null
182-
183-
""".trimIndent(),
184-
),
183+
TiamatPreview(
184+
destination = SimpleConversionScreen,
185+
navArgs = SimpleConversionParamsSource.TextSource(
186+
name = "IconName",
187+
text = """
188+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
189+
android:width="24dp"
190+
android:height="24dp"
191+
android:viewportWidth="18"
192+
android:viewportHeight="18">
193+
</vector>
194+
""".trimIndent(),
185195
),
186-
previewType = PreviewType.Auto,
187-
onBack = {},
188-
openSettings = {},
189-
onAction = {},
190196
)
191197
}

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@ import io.github.composegears.valkyrie.ui.di.DI
1616
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconContent
1717
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconSource.FileBasedIcon
1818
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconSource.StringBasedIcon
19+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionAction
20+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionEvent
21+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionEvent.CopyInClipboard
22+
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionEvent.ExportKtFile
1923
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState
2024
import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.SimpleConversionState.ConversionState
2125
import java.nio.file.Path
2226
import kotlinx.coroutines.Dispatchers
27+
import kotlinx.coroutines.channels.Channel
2328
import kotlinx.coroutines.flow.flowOn
2429
import kotlinx.coroutines.flow.launchIn
2530
import kotlinx.coroutines.flow.onEach
31+
import kotlinx.coroutines.flow.receiveAsFlow
2632
import kotlinx.coroutines.launch
33+
import kotlinx.coroutines.withContext
2734

2835
class SimpleConversionViewModel(
2936
savedState: MutableSavedState,
@@ -38,6 +45,9 @@ class SimpleConversionViewModel(
3845
)
3946
val state = stateRecord.asStateFlow()
4047

48+
private val _events = Channel<SimpleConversionEvent>()
49+
val events = _events.receiveAsFlow()
50+
4151
init {
4252
when (params) {
4353
is SimpleConversionParamsSource.PathSource -> selectPath(params.path)
@@ -76,6 +86,29 @@ class SimpleConversionViewModel(
7686
.launchIn(viewModelScope)
7787
}
7888

89+
fun onAction(action: SimpleConversionAction) {
90+
val state = stateRecord.value.safeAs<ConversionState>() ?: return
91+
92+
viewModelScope.launch {
93+
when (action) {
94+
is SimpleConversionAction.OnExport -> {
95+
_events.send(
96+
ExportKtFile(
97+
fileName = "${state.iconContent.name}.kt",
98+
content = action.text,
99+
),
100+
)
101+
}
102+
is SimpleConversionAction.OnCopyInClipboard -> {
103+
_events.send(CopyInClipboard(action.text))
104+
}
105+
is SimpleConversionAction.OnIconNameChange -> {
106+
changeIconName(action.name)
107+
}
108+
}
109+
}
110+
}
111+
79112
fun selectPath(path: Path) = viewModelScope.launch(Dispatchers.Default) {
80113
parseIcon(path)
81114
.onFailure {
@@ -113,8 +146,8 @@ class SimpleConversionViewModel(
113146
}
114147
}
115148

116-
fun changeIconName(name: String) = viewModelScope.launch(Dispatchers.Default) {
117-
val conversionState = stateRecord.value.safeAs<ConversionState>() ?: return@launch
149+
private suspend fun changeIconName(name: String) = withContext(Dispatchers.Default) {
150+
val conversionState = stateRecord.value.safeAs<ConversionState>() ?: return@withContext
118151

119152
when (val source = conversionState.iconSource) {
120153
is FileBasedIcon -> {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ package io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model
22

33
sealed interface SimpleConversionAction {
44
data class OnCopyInClipboard(val text: String) : SimpleConversionAction
5+
data class OnExport(val text: String) : SimpleConversionAction
56
data class OnIconNameChange(val name: String) : SimpleConversionAction
67
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model
2+
3+
sealed interface SimpleConversionEvent {
4+
data class ExportKtFile(
5+
val fileName: String,
6+
val content: String,
7+
) : SimpleConversionEvent
8+
9+
data class CopyInClipboard(val text: String) : SimpleConversionEvent
10+
}

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

File renamed without changes.

0 commit comments

Comments
 (0)