Skip to content

Commit

Permalink
Make clipboard icon name required
Browse files Browse the repository at this point in the history
  • Loading branch information
egorikftp committed Jan 30, 2025
1 parent 6871cf7 commit 7d0d5f5
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 188 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ package io.github.composegears.valkyrie.ui.foundation

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
Expand Down Expand Up @@ -48,6 +54,7 @@ import io.github.composegears.valkyrie.ui.foundation.icons.Checked
import io.github.composegears.valkyrie.ui.foundation.icons.Close
import io.github.composegears.valkyrie.ui.foundation.icons.Edit
import io.github.composegears.valkyrie.ui.foundation.icons.ValkyrieIcons
import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme

@OptIn(ExperimentalComposeUiApi::class)
@Composable
Expand Down Expand Up @@ -81,7 +88,15 @@ fun FocusableTextField(
.onPointerEvent(PointerEventType.Exit) { isHover = false }
.border(
width = Dp.Hairline,
color = if (mode == Edit) colors.focusedBorderColor else colors.unfocusedBorderColor,
color = when (mode) {
Edit -> {
when {
isError -> colors.errorColor
else -> colors.focusedBorderColor
}
}
else -> colors.unfocusedBorderColor
},
shape = shape,
)
.clip(shape)
Expand All @@ -95,7 +110,7 @@ fun FocusableTextField(
.focusProperties { canFocus = true }
.focusRequester(focusRequester)
.focusable()
.widthIn(max = 150.dp)
.weight(1f)
.height(32.dp)
.onKeyEvent {
when (it.key) {
Expand Down Expand Up @@ -139,11 +154,21 @@ fun FocusableTextField(
contentAlignment = Alignment.CenterStart,
) {
innerTextField()
if (isError && mode == View) {
Text(
text = "icon name required",
style = MaterialTheme.typography.bodyMedium,
color = colors.errorColor,
)
}
}
},
)

AnimatedContent(targetState = mode) { targetMode ->
AnimatedContent(
modifier = Modifier.height(32.dp),
targetState = mode,
) { targetMode ->
when (targetMode) {
View -> {
Icon(
Expand All @@ -161,38 +186,48 @@ fun FocusableTextField(
)
}
Edit -> {
Row {
Icon(
imageVector = ValkyrieIcons.Close,
contentDescription = null,
modifier = Modifier
.padding(4.dp)
.clip(shape)
.clickable {
focusRequester.freeFocus()
mode = View
textFieldValue = textFieldValue.copy(text = value)
}
.padding(4.dp),
)
Icon(
imageVector = ValkyrieIcons.Checked,
contentDescription = null,
modifier = Modifier
.padding(4.dp)
.clip(shape)
.clickable(enabled = !isError) {
focusRequester.freeFocus()
mode = View
onValueChange(textFieldValue.text)
}
.padding(4.dp),
tint = if (isError) {
LocalContentColor.current.disabled()
} else {
LocalContentColor.current
CenterVerticalRow(
modifier = Modifier.padding(end = 4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
FilledIconButton(
modifier = Modifier.size(20.dp),
colors = IconButtonDefaults.filledIconButtonColors()
.copy(containerColor = MaterialTheme.colorScheme.surfaceVariant),
onClick = {
focusRequester.freeFocus()
mode = View
textFieldValue = textFieldValue.copy(text = value)
},
)
) {
Icon(
modifier = Modifier.size(12.dp),
imageVector = ValkyrieIcons.Close,
contentDescription = null,
)
}
FilledIconButton(
modifier = Modifier.size(20.dp),
colors = IconButtonDefaults.filledIconButtonColors()
.copy(containerColor = MaterialTheme.colorScheme.primary.disabled()),
enabled = !isError,
onClick = {
focusRequester.freeFocus()
mode = View
onValueChange(textFieldValue.text)
},
) {
Icon(
modifier = Modifier.size(12.dp),
imageVector = ValkyrieIcons.Checked,
contentDescription = null,
tint = if (isError) {
LocalContentColor.current.disabled()
} else {
LocalContentColor.current
},
)
}
}
}
}
Expand All @@ -212,9 +247,9 @@ object FocusableTextFieldDefaults {
return FocusableTextFieldColor(
focusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant.dim(),
unfocusedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f),
focusedBorderColor = MaterialTheme.colorScheme.primary,
cursorColor = MaterialTheme.colorScheme.onSurfaceVariant,
focusedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f),
unfocusedBorderColor = Color.Transparent,
errorColor = MaterialTheme.colorScheme.error,
)
}
Expand All @@ -233,3 +268,20 @@ private enum class Mode {
Edit,
View,
}

@Preview
@Composable
private fun FocusableTextFieldPreview() = PreviewTheme {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
FocusableTextField(
modifier = Modifier.width(300.dp),
value = "IconName",
onValueChange = {},
)
FocusableTextField(
modifier = Modifier.width(150.dp),
value = "IconName",
onValueChange = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ val ValkyrieIcons.Checked: ImageVector
).apply {
path(
stroke = SolidColor(Color(0xFF6C707E)),
strokeLineWidth = 1.5f,
strokeLineWidth = 1f,
strokeLineCap = StrokeCap.Round,
strokeLineJoin = StrokeJoin.Round,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ sealed interface BatchIcon {
}

@JvmInline
value class IconName(val value: String)
value class IconName(val name: String)

@JvmInline
value class IconId(val id: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ class IconPackConversionViewModel(
private val _events = MutableSharedFlow<ConversionEvent>()
val events = _events.asSharedFlow()

private var clipboardIconCounter = 0

init {
val restoredState = savedState?.getOrNull<List<BatchIcon>>(key = "icons")

Expand All @@ -69,16 +67,11 @@ class IconPackConversionViewModel(
}
}
}

savedState?.getOrNull<Int>(key = "clipboardIconCounter")?.also { clipboardIconCounter = it }
}

override fun saveToSaveState(): SavedState {
return when (val state = _state.value) {
is BatchProcessing.IconPackCreationState -> mapOf(
"icons" to state.icons,
"clipboardIconCounter" to clipboardIconCounter,
)
is BatchProcessing.IconPackCreationState -> mapOf("icons" to state.icons)
else -> mapOf("icons" to emptyList<List<BatchIcon>>())
}
}
Expand All @@ -98,7 +91,6 @@ class IconPackConversionViewModel(
val iconsToProcess = icons.filter { it.id != iconId }

if (iconsToProcess.isEmpty()) {
clipboardIconCounter = 0
IconsPickering
} else {
copy(
Expand Down Expand Up @@ -140,7 +132,7 @@ class IconPackConversionViewModel(
val settings = inMemorySettings.current
val output = ImageVectorGenerator.convert(
vector = icon.irImageVector,
iconName = icon.iconName.value,
iconName = icon.iconName.name,
config = ImageVectorGeneratorConfig(
packageName = icon.iconPack.iconPackage,
iconPackPackage = settings.iconPackPackage,
Expand Down Expand Up @@ -175,7 +167,7 @@ class IconPackConversionViewModel(
is IconPack.Nested -> {
val vectorSpecOutput = ImageVectorGenerator.convert(
vector = icon.irImageVector,
iconName = icon.iconName.value,
iconName = icon.iconName.name,
config = ImageVectorGeneratorConfig(
packageName = icon.iconPack.iconPackage,
iconPackPackage = settings.iconPackPackage,
Expand All @@ -201,7 +193,7 @@ class IconPackConversionViewModel(
is IconPack.Single -> {
val vectorSpecOutput = ImageVectorGenerator.convert(
vector = icon.irImageVector,
iconName = icon.iconName.value,
iconName = icon.iconName.name,
config = ImageVectorGeneratorConfig(
packageName = icon.iconPack.iconPackage,
iconPackPackage = settings.iconPackPackage,
Expand Down Expand Up @@ -254,16 +246,10 @@ class IconPackConversionViewModel(

fun reset() {
_state.updateState { IconsPickering }
clipboardIconCounter = 0
}

private fun processText(text: String) = viewModelScope.launch(Dispatchers.Default) {
val iconName = when (clipboardIconCounter) {
0 -> "IconName"
else -> "IconName_$clipboardIconCounter"
}
clipboardIconCounter++

val iconName = ""
val output = runCatching { SvgXmlParser.toIrImageVector(text, iconName) }.getOrNull()

val icon = when (output) {
Expand Down Expand Up @@ -334,10 +320,10 @@ class IconPackConversionViewModel(

private fun List<BatchIcon>.isAllIconsValid() = isNotEmpty() &&
all { it is BatchIcon.Valid } &&
all { it.iconName.value.isNotEmpty() && !it.iconName.value.contains(" ") } &&
all { it.iconName.name.isNotEmpty() && !it.iconName.name.contains(" ") } &&
hasNoDuplicates()

private fun List<BatchIcon>.hasNoDuplicates() = map { it.iconName.value }.toSet().size == size
private fun List<BatchIcon>.hasNoDuplicates() = map { it.iconName.name }.toSet().size == size
}

sealed interface ConversionEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import io.github.composegears.valkyrie.ui.domain.model.PreviewType
import io.github.composegears.valkyrie.ui.foundation.AppBarTitle
import io.github.composegears.valkyrie.ui.foundation.CenterVerticalRow
import io.github.composegears.valkyrie.ui.foundation.CloseAction
import io.github.composegears.valkyrie.ui.foundation.FocusableTextField
import io.github.composegears.valkyrie.ui.foundation.IconButton
import io.github.composegears.valkyrie.ui.foundation.SettingsAction
import io.github.composegears.valkyrie.ui.foundation.TopAppBar
Expand All @@ -64,7 +65,6 @@ import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.Picker
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.PickerEvent.PickFiles
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.ClipboardEventColumn
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.batch.ui.FileTypeBadge
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.batch.ui.IconNameField
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.batch.ui.IconPreviewBox

@Composable
Expand Down Expand Up @@ -166,11 +166,13 @@ private fun ValidIconItem(
irImageVector = icon.irImageVector,
previewType = previewType,
)
IconNameField(

val name = icon.iconName.name
FocusableTextField(
modifier = Modifier
.weight(1f)
.padding(end = 32.dp),
value = icon.iconName.value,
value = name,
onValueChange = {
onRenameIcon(icon, IconName(it))
},
Expand Down Expand Up @@ -248,7 +250,10 @@ private fun BrokenIconItem(
modifier = Modifier
.weight(1f)
.padding(vertical = 8.dp),
text = "Failed to parse icon: ${broken.iconName.value}",
text = when {
broken.iconName.name.isEmpty() -> "Failed to parse icon"
else -> "Failed to parse icon: ${broken.iconName.name}"
},
)
IconButton(
imageVector = Icons.Default.Delete,
Expand Down Expand Up @@ -364,7 +369,7 @@ private fun BatchProcessingStatePreview() = PreviewTheme {
),
BatchIcon.Broken(
id = IconId("2"),
iconName = IconName(value = "ic_all_path_params_3"),
iconName = IconName(name = "ic_all_path_params_3"),
),
BatchIcon.Valid(
id = IconId("3"),
Expand Down
Loading

0 comments on commit 7d0d5f5

Please sign in to comment.