Skip to content

Commit 24aff8d

Browse files
committed
[Web Import] Add persistent settings for font customization
1 parent f7cde34 commit 24aff8d

20 files changed

Lines changed: 163 additions & 90 deletions

tools/idea-plugin/CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
### Added
66

77
- Add export as file action for `Simple mode` and `ImageVector to XML` tool
8-
- Add validation for exact duplicate icon names (e.g., `test-icon.svg` and `test_icon.svg` both produce `TestIcon.kt`)
9-
- Add validation for case-insensitive duplicate icon names to prevent file overwrites on macOS/Windows
10-
- Add automatic re-validation when `useFlatPackage` setting changes to detect new conflicts immediately
8+
- [IconPack] Add validation for exact duplicate icon names (e.g., `test-icon.svg` and `test_icon.svg` both produce
9+
`TestIcon.kt`)
10+
- [IconPack] Add validation for case-insensitive duplicate icon names to prevent file overwrites on macOS/Windows
11+
- [IconPack] Add automatic re-validation when `useFlatPackage` setting changes to detect new conflicts immediately
1112
- [Web Import] Add `Bootstrap` icons provider
13+
- [Web Import] Add persistent settings for font customization for all providers
1214
- [Gutter] Add support for multiple icons in kt file
1315

1416
### Fixed

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/service/PersistentSettings.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import io.github.composegears.valkyrie.jewel.tooling.GlobalPreviewState
1010
import io.github.composegears.valkyrie.sdk.shared.ValkyrieMode
1111
import io.github.composegears.valkyrie.service.PersistentSettings.ValkyrieState
1212
import io.github.composegears.valkyrie.ui.domain.model.PreviewType
13+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_FILL
14+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_GRADE
15+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_OPTICAL_SIZE
16+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_WEIGHT
17+
import io.github.composegears.valkyrie.ui.screen.webimport.standard.model.SizeSettings.Companion.DEFAULT_SIZE
1318

1419
@State(name = "Valkyrie.Settings", storages = [Storage("valkyrie_settings.xml")])
1520
class PersistentSettings : SimplePersistentStateComponent<ValkyrieState>(ValkyrieState()) {
@@ -50,6 +55,18 @@ class PersistentSettings : SimplePersistentStateComponent<ValkyrieState>(Valkyri
5055
var showImageVectorPreview: Boolean by property(true)
5156
var showIconsInProjectView: Boolean by property(true)
5257
var indentSize: Int by property(4)
58+
59+
// MaterialSymbols
60+
var materialFontFill: Boolean by property(DEFAULT_FILL)
61+
var materialFontWeight: Int by property(DEFAULT_WEIGHT)
62+
var materialFontGrade: Int by property(DEFAULT_GRADE)
63+
var materialFontOpticalSize: Float by property(DEFAULT_OPTICAL_SIZE)
64+
65+
// Lucide
66+
var lucideSize: Int by property(DEFAULT_SIZE)
67+
68+
// Bootstrap
69+
var bootstrapSize: Int by property(DEFAULT_SIZE)
5370
}
5471

5572
companion object {

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import io.github.composegears.valkyrie.service.PersistentSettings.Companion.pers
99
import io.github.composegears.valkyrie.ui.domain.model.PreviewType
1010
import io.github.composegears.valkyrie.ui.extension.or
1111
import io.github.composegears.valkyrie.ui.extension.updateState
12+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_FILL
13+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_GRADE
14+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_OPTICAL_SIZE
15+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings.Companion.DEFAULT_WEIGHT
16+
import io.github.composegears.valkyrie.ui.screen.webimport.standard.model.SizeSettings.Companion.DEFAULT_SIZE
1217
import java.util.Collections.emptyList
1318
import kotlinx.coroutines.flow.MutableStateFlow
1419
import kotlinx.coroutines.flow.asStateFlow
@@ -36,6 +41,8 @@ class InMemorySettings(project: Project) {
3641
_settings.updateState { persistentSettings.state.toValkyriesSettings() }
3742
}
3843

44+
fun <T> readState(selector: PersistentSettings.ValkyrieState.() -> T): T = selector(persistentSettings.state)
45+
3946
fun clear() = update {
4047
mode = ValkyrieMode.Unspecified
4148
previewType = PreviewType.Pixel
@@ -54,6 +61,12 @@ class InMemorySettings(project: Project) {
5461
showImageVectorPreview = true
5562
showIconsInProjectView = true
5663
indentSize = 4
64+
materialFontFill = DEFAULT_FILL
65+
materialFontWeight = DEFAULT_WEIGHT
66+
materialFontGrade = DEFAULT_GRADE
67+
materialFontOpticalSize = DEFAULT_OPTICAL_SIZE
68+
lucideSize = DEFAULT_SIZE
69+
bootstrapSize = DEFAULT_SIZE
5770
}
5871

5972
fun updateUIState(uiState: SavedState) {

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/common/ui/SidePanel.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import androidx.compose.ui.input.pointer.pointerInput
2020
import androidx.compose.ui.unit.dp
2121
import io.github.composegears.valkyrie.jewel.tooling.PreviewTheme
2222
import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState
23-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.FontSettings
24-
import io.github.composegears.valkyrie.ui.screen.webimport.material.ui.FontCustomization
23+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings
24+
import io.github.composegears.valkyrie.ui.screen.webimport.material.ui.MaterialFontCustomization
2525
import org.jetbrains.compose.ui.tooling.preview.Preview
2626
import org.jetbrains.jewel.foundation.theme.JewelTheme
2727
import org.jetbrains.jewel.ui.component.DefaultButton
@@ -81,9 +81,9 @@ private fun SidePanelPreview() = PreviewTheme {
8181
isOpen = isOpen,
8282
onClose = { isOpen = false },
8383
content = {
84-
FontCustomization(
84+
MaterialFontCustomization(
8585
modifier = Modifier.fillMaxHeight(),
86-
fontSettings = FontSettings(fill = true),
86+
fontSettings = MaterialFontSettings(fill = true),
8787
onClose = { isOpen = false },
8888
onSettingsChange = { },
8989
)

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/material/MaterialSymbolsImportScreen.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.IconLoading
5858
import io.github.composegears.valkyrie.ui.screen.webimport.common.ui.SidePanel
5959
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.Category
6060
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.IconModel
61-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.FontSettings
62-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.IconFontFamily
63-
import io.github.composegears.valkyrie.ui.screen.webimport.material.ui.FontCustomization
61+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings
62+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialIconFontFamily
63+
import io.github.composegears.valkyrie.ui.screen.webimport.material.ui.MaterialFontCustomization
6464
import io.github.composegears.valkyrie.ui.screen.webimport.material.ui.MaterialTopActions
6565
import io.github.composegears.valkyrie.util.stringResource
6666
import kotlinx.coroutines.launch
@@ -103,9 +103,9 @@ private fun MaterialSymbolsImportUI(
103103
state: MaterialState,
104104
onBack: () -> Unit,
105105
onSelectIcon: (IconModel) -> Unit,
106-
onSelectFontFamily: (IconFontFamily) -> Unit,
106+
onSelectFontFamily: (MaterialIconFontFamily) -> Unit,
107107
onSelectCategory: (Category) -> Unit,
108-
onSettingsChange: (FontSettings) -> Unit,
108+
onSettingsChange: (MaterialFontSettings) -> Unit,
109109
onSearchQueryChange: (String) -> Unit,
110110
) {
111111
var isSidePanelOpen by rememberSaveable { mutableStateOf(false) }
@@ -156,7 +156,7 @@ private fun MaterialSymbolsImportUI(
156156
isOpen = isSidePanelOpen,
157157
onClose = { isSidePanelOpen = false },
158158
content = {
159-
FontCustomization(
159+
MaterialFontCustomization(
160160
fontSettings = current.fontSettings,
161161
onClose = { isSidePanelOpen = false },
162162
onSettingsChange = onSettingsChange,
@@ -174,7 +174,7 @@ private fun MaterialSymbolsImportUI(
174174
private fun IconsContent(
175175
state: MaterialState.Success,
176176
onSelectIcon: (IconModel) -> Unit,
177-
onSelectFontFamily: (IconFontFamily) -> Unit,
177+
onSelectFontFamily: (MaterialIconFontFamily) -> Unit,
178178
onSelectCategory: (Category) -> Unit,
179179
onToggleSidePanel: () -> Unit,
180180
onSearchQueryChange: (String) -> Unit,

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/material/MaterialSymbolsViewModel.kt

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.composegears.tiamat.navigation.asStateFlow
99
import com.composegears.tiamat.navigation.recordOf
1010
import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter
1111
import io.github.composegears.valkyrie.sdk.core.extensions.safeAs
12+
import io.github.composegears.valkyrie.ui.di.DI
1213
import io.github.composegears.valkyrie.ui.screen.webimport.common.model.FontByteArray
1314
import io.github.composegears.valkyrie.ui.screen.webimport.common.model.GridItem
1415
import io.github.composegears.valkyrie.ui.screen.webimport.common.util.filterGridItems
@@ -17,8 +18,8 @@ import io.github.composegears.valkyrie.ui.screen.webimport.material.di.MaterialS
1718
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.Category
1819
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.IconModel
1920
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.MaterialConfig
20-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.FontSettings
21-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.IconFontFamily
21+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings
22+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialIconFontFamily
2223
import kotlinx.coroutines.Dispatchers
2324
import kotlinx.coroutines.Job
2425
import kotlinx.coroutines.channels.Channel
@@ -27,8 +28,9 @@ import kotlinx.coroutines.launch
2728

2829
class MaterialSymbolsViewModel(savedState: MutableSavedState) : ViewModel() {
2930

30-
private val fontCache = mutableMapOf<IconFontFamily, FontByteArray>()
31+
private val fontCache = mutableMapOf<MaterialIconFontFamily, FontByteArray>()
3132
private val materialSymbolsConfigUseCase = inject(MaterialSymbolsModule.materialSymbolsConfigUseCase)
33+
private val inMemorySettings = inject(DI.core.inMemorySettings)
3234

3335
private val materialRecord = savedState.recordOf<MaterialState>(
3436
key = "materialSymbols",
@@ -39,7 +41,7 @@ class MaterialSymbolsViewModel(savedState: MutableSavedState) : ViewModel() {
3941
private val _events = Channel<MaterialEvent>()
4042
val events = _events.receiveAsFlow()
4143

42-
private var currentFontFamily: IconFontFamily = IconFontFamily.OUTLINED
44+
private var currentFontFamily: MaterialIconFontFamily = MaterialIconFontFamily.Outlined
4345
private var iconLoadJob: Job? = null
4446

4547
init {
@@ -56,19 +58,28 @@ class MaterialSymbolsViewModel(savedState: MutableSavedState) : ViewModel() {
5658
materialRecord.value = MaterialState.Loading
5759

5860
runCatching {
61+
val initialSettings = inMemorySettings.readState {
62+
MaterialFontSettings(
63+
fill = materialFontFill,
64+
weight = materialFontWeight,
65+
grade = materialFontGrade,
66+
opticalSize = materialFontOpticalSize,
67+
)
68+
}
5969
val config = materialSymbolsConfigUseCase.loadConfig()
6070
materialRecord.value = MaterialState.Success(
6171
config = config,
6272
gridItems = config.gridItems.toGridItems(),
73+
fontSettings = initialSettings,
6374
)
64-
downloadFont(IconFontFamily.OUTLINED)
75+
downloadFont(MaterialIconFontFamily.Outlined)
6576
}.onFailure {
6677
materialRecord.value = MaterialState.Error("Error loading Material Symbols config: ${it.message}")
6778
}
6879
}
6980
}
7081

71-
fun downloadFont(iconFontFamily: IconFontFamily) {
82+
fun downloadFont(iconFontFamily: MaterialIconFontFamily) {
7283
currentFontFamily = iconFontFamily
7384
viewModelScope.launch {
7485
val cachedFont = fontCache[iconFontFamily]
@@ -166,8 +177,14 @@ class MaterialSymbolsViewModel(savedState: MutableSavedState) : ViewModel() {
166177
searchQuery = searchQuery,
167178
)
168179

169-
fun updateFontSettings(fontSettings: FontSettings) {
180+
fun updateFontSettings(fontSettings: MaterialFontSettings) {
170181
viewModelScope.launch {
182+
inMemorySettings.update {
183+
materialFontFill = fontSettings.fill
184+
materialFontWeight = fontSettings.weight
185+
materialFontGrade = fontSettings.grade
186+
materialFontOpticalSize = fontSettings.opticalSize
187+
}
171188
updateSuccess { state ->
172189
state.copy(fontSettings = fontSettings)
173190
}
@@ -198,8 +215,8 @@ sealed interface MaterialState {
198215
val config: MaterialConfig,
199216
val gridItems: List<GridItem> = emptyList(),
200217
val selectedCategory: Category = Category.All,
201-
val fontSettings: FontSettings = FontSettings(),
202-
val iconFontFamily: IconFontFamily = IconFontFamily.OUTLINED,
218+
val fontSettings: MaterialFontSettings,
219+
val iconFontFamily: MaterialIconFontFamily = MaterialIconFontFamily.Outlined,
203220
val fontByteArray: FontByteArray? = null,
204221
) : MaterialState
205222

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/material/data/font/MaterialFontRepository.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.github.composegears.valkyrie.ui.screen.webimport.material.data.font
22

3-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.FontSettings
3+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings
44
import io.ktor.client.HttpClient
55
import io.ktor.client.request.get
66
import io.ktor.client.statement.bodyAsChannel
@@ -20,7 +20,7 @@ class MaterialFontRepository(
2020
suspend fun downloadSvg(
2121
name: String,
2222
fontFamily: String,
23-
fontSettings: FontSettings,
23+
fontSettings: MaterialFontSettings,
2424
): String = withContext(Dispatchers.IO) {
2525
val iconOption = fontSettings.iconOption()
2626
val iconSize = fontSettings.iconSize()
@@ -29,7 +29,7 @@ class MaterialFontRepository(
2929
httpClient.get(url).bodyAsText()
3030
}
3131

32-
private fun FontSettings.iconOption(): String {
32+
private fun MaterialFontSettings.iconOption(): String {
3333
if (!isModified) {
3434
return "default"
3535
}
@@ -50,5 +50,5 @@ class MaterialFontRepository(
5050
}
5151
}
5252

53-
private fun FontSettings.iconSize() = "${opticalSize.toInt()}px"
53+
private fun MaterialFontSettings.iconSize() = "${opticalSize.toInt()}px"
5454
}

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/material/domain/MaterialSymbolsConfigUseCase.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import io.github.composegears.valkyrie.ui.screen.webimport.material.data.font.Ma
77
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.Category
88
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.IconModel
99
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.MaterialConfig
10-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.FontSettings
11-
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.IconFontFamily
10+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialFontSettings
11+
import io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font.MaterialIconFontFamily
1212

1313
class MaterialSymbolsConfigUseCase(
1414
private val configRepository: MaterialSymbolsConfigRepository,
@@ -45,7 +45,7 @@ class MaterialSymbolsConfigUseCase(
4545
)
4646
}
4747

48-
suspend fun loadFont(iconFontFamily: IconFontFamily): FontByteArray {
48+
suspend fun loadFont(iconFontFamily: MaterialIconFontFamily): FontByteArray {
4949
val bytes = fontRepository.downloadFont(iconFontFamily.cdnUrl)
5050

5151
return FontByteArray(bytes = bytes)
@@ -54,7 +54,7 @@ class MaterialSymbolsConfigUseCase(
5454
suspend fun loadIcon(
5555
name: String,
5656
fontFamily: String,
57-
fontSettings: FontSettings,
57+
fontSettings: MaterialFontSettings,
5858
): String {
5959
return fontRepository.downloadSvg(
6060
name = name,

tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/webimport/material/domain/model/font/FontSettings.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.github.composegears.valkyrie.ui.screen.webimport.material.domain.model.font
2+
3+
import io.github.composegears.valkyrie.ui.screen.webimport.common.model.IconSettings
4+
5+
data class MaterialFontSettings(
6+
val fill: Boolean = DEFAULT_FILL,
7+
val weight: Int = DEFAULT_WEIGHT,
8+
val grade: Int = DEFAULT_GRADE,
9+
val opticalSize: Float = DEFAULT_OPTICAL_SIZE,
10+
) : IconSettings {
11+
12+
companion object {
13+
const val DEFAULT_FILL = false
14+
const val DEFAULT_WEIGHT = 400
15+
const val DEFAULT_GRADE = 0
16+
const val DEFAULT_OPTICAL_SIZE = 24f
17+
}
18+
19+
init {
20+
require(weight in 100..700) { "Weight must be between 100 and 700" }
21+
require(grade in -25..200) { "Grade must be between -25 and 200" }
22+
require(opticalSize in 20f..48f) { "Optical size must be between 20 and 48" }
23+
}
24+
25+
override val isModified: Boolean
26+
get() = fill || weight != DEFAULT_WEIGHT || grade != DEFAULT_GRADE || opticalSize != DEFAULT_OPTICAL_SIZE
27+
}

0 commit comments

Comments
 (0)