Skip to content

Commit 866726d

Browse files
committed
Introduce UniversalPicker component
1 parent e0e17c8 commit 866726d

7 files changed

Lines changed: 243 additions & 198 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.composegears.valkyrie.ui.common.picker
2+
3+
import java.nio.file.Path
4+
5+
sealed interface PickerEvent {
6+
data class PickDirectory(val path: Path) : PickerEvent
7+
data class PickFiles(val paths: List<Path>) : PickerEvent
8+
data class ClipboardText(val text: String) : PickerEvent
9+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package io.github.composegears.valkyrie.ui.common.picker
2+
3+
import androidx.compose.animation.core.animateDpAsState
4+
import androidx.compose.desktop.ui.tooling.preview.Preview
5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.BoxScope
9+
import androidx.compose.foundation.layout.Column
10+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
11+
import androidx.compose.foundation.layout.FlowRow
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.layout.heightIn
14+
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.material3.Icon
16+
import androidx.compose.material3.LocalContentColor
17+
import androidx.compose.material3.MaterialTheme
18+
import androidx.compose.material3.Text
19+
import androidx.compose.material3.TextButton
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.rememberCoroutineScope
23+
import androidx.compose.ui.Alignment
24+
import androidx.compose.ui.Modifier
25+
import androidx.compose.ui.draw.clip
26+
import androidx.compose.ui.graphics.Color
27+
import androidx.compose.ui.text.style.TextAlign
28+
import androidx.compose.ui.unit.dp
29+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent.PickDirectory
30+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent.PickFiles
31+
import io.github.composegears.valkyrie.ui.foundation.CenterVerticalRow
32+
import io.github.composegears.valkyrie.ui.foundation.VerticalSpacer
33+
import io.github.composegears.valkyrie.ui.foundation.WeightSpacer
34+
import io.github.composegears.valkyrie.ui.foundation.dashedBorder
35+
import io.github.composegears.valkyrie.ui.foundation.disabled
36+
import io.github.composegears.valkyrie.ui.foundation.icons.AddFile
37+
import io.github.composegears.valkyrie.ui.foundation.icons.ValkyrieIcons
38+
import io.github.composegears.valkyrie.ui.foundation.rememberMutableState
39+
import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme
40+
import io.github.composegears.valkyrie.ui.platform.ClipboardDataType
41+
import io.github.composegears.valkyrie.ui.platform.Os
42+
import io.github.composegears.valkyrie.ui.platform.picker.rememberDirectoryPicker
43+
import io.github.composegears.valkyrie.ui.platform.picker.rememberMultipleFilesPicker
44+
import io.github.composegears.valkyrie.ui.platform.rememberCurrentOs
45+
import io.github.composegears.valkyrie.ui.platform.rememberMultiSelectDragAndDropHandler
46+
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.ClipboardEventColumn
47+
import io.github.composegears.valkyrie.util.stringResource
48+
import java.nio.file.Path
49+
import kotlin.io.path.isDirectory
50+
import kotlin.io.path.isRegularFile
51+
import kotlinx.coroutines.launch
52+
53+
@Composable
54+
fun UniversalPicker(
55+
onPickerEvent: (PickerEvent) -> Unit,
56+
modifier: Modifier = Modifier,
57+
headerSection: @Composable () -> Unit,
58+
) {
59+
val scope = rememberCoroutineScope()
60+
61+
val multipleFilePicker = rememberMultipleFilesPicker()
62+
val directoryPicker = rememberDirectoryPicker()
63+
64+
ClipboardEventColumn(
65+
modifier = modifier,
66+
horizontalAlignment = Alignment.CenterHorizontally,
67+
onPaste = { dataType ->
68+
when (dataType) {
69+
is ClipboardDataType.Files -> onPickerEvent(PickFiles(paths = dataType.paths))
70+
is ClipboardDataType.Text -> onPickerEvent(PickerEvent.ClipboardText(dataType.text))
71+
}
72+
},
73+
) {
74+
headerSection()
75+
WeightSpacer(weight = 0.3f)
76+
SelectableState(
77+
onSelectPath = { paths ->
78+
when {
79+
paths.size == 1 -> {
80+
val path = paths.first()
81+
82+
when {
83+
path.isDirectory() -> onPickerEvent(PickDirectory(path = path))
84+
path.isRegularFile() -> onPickerEvent(PickFiles(paths = paths))
85+
}
86+
}
87+
else -> onPickerEvent(PickFiles(paths = paths))
88+
}
89+
},
90+
onPickDirectory = {
91+
scope.launch {
92+
val path = directoryPicker.launch()
93+
94+
if (path != null) {
95+
onPickerEvent(PickDirectory(path = path))
96+
}
97+
}
98+
},
99+
onPickFiles = {
100+
scope.launch {
101+
val paths = multipleFilePicker.launch()
102+
103+
if (paths.isNotEmpty()) {
104+
onPickerEvent(PickFiles(paths = paths))
105+
}
106+
}
107+
},
108+
)
109+
WeightSpacer(weight = 0.7f)
110+
}
111+
}
112+
113+
@OptIn(ExperimentalLayoutApi::class)
114+
@Composable
115+
private fun SelectableState(
116+
onPickDirectory: () -> Unit,
117+
onPickFiles: () -> Unit,
118+
onSelectPath: (List<Path>) -> Unit,
119+
) {
120+
val dragAndDropHandler = rememberMultiSelectDragAndDropHandler(onDrop = onSelectPath)
121+
val isDragging by rememberMutableState(dragAndDropHandler.isDragging) { dragAndDropHandler.isDragging }
122+
123+
val os = rememberCurrentOs()
124+
125+
DragAndDropBox(isDragging = isDragging) {
126+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
127+
CenterVerticalRow {
128+
Icon(
129+
imageVector = ValkyrieIcons.AddFile,
130+
contentDescription = null,
131+
)
132+
Text(
133+
modifier = Modifier.padding(8.dp),
134+
text = stringResource("picker.dnd"),
135+
textAlign = TextAlign.Center,
136+
style = MaterialTheme.typography.titleSmall,
137+
)
138+
}
139+
Text(
140+
text = when (os) {
141+
Os.MacOS -> stringResource("picker.clipboard.mac")
142+
else -> stringResource("picker.clipboard.other")
143+
},
144+
textAlign = TextAlign.Center,
145+
color = LocalContentColor.current.disabled(),
146+
style = MaterialTheme.typography.labelSmall,
147+
)
148+
VerticalSpacer(16.dp)
149+
Text(
150+
text = stringResource("picker.dnd.or"),
151+
style = MaterialTheme.typography.labelMedium,
152+
)
153+
FlowRow(
154+
modifier = Modifier.fillMaxWidth(),
155+
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
156+
) {
157+
TextButton(onClick = onPickDirectory) {
158+
Text(text = stringResource("picker.pick.directory"))
159+
}
160+
TextButton(onClick = onPickFiles) {
161+
Text(text = stringResource("picker.pick.files"))
162+
}
163+
}
164+
}
165+
}
166+
}
167+
168+
@Composable
169+
private fun DragAndDropBox(
170+
isDragging: Boolean,
171+
modifier: Modifier = Modifier,
172+
content: @Composable BoxScope.() -> Unit,
173+
) {
174+
val dashColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
175+
val border by animateDpAsState(if (isDragging) 4.dp else 1.dp)
176+
177+
Box(
178+
modifier = modifier
179+
.fillMaxWidth(0.8f)
180+
.heightIn(min = 300.dp)
181+
.clip(MaterialTheme.shapes.small)
182+
.dashedBorder(
183+
strokeWidth = border,
184+
gapWidth = 8.dp,
185+
dashWidth = 8.dp,
186+
color = dashColor,
187+
shape = MaterialTheme.shapes.small,
188+
)
189+
.padding(2.dp)
190+
.background(
191+
color = when {
192+
isDragging -> MaterialTheme.colorScheme.primary.copy(alpha = 0.05f)
193+
else -> Color.Transparent
194+
},
195+
shape = MaterialTheme.shapes.small,
196+
),
197+
contentAlignment = Alignment.Center,
198+
content = content,
199+
)
200+
}
201+
202+
@Preview
203+
@Composable
204+
private fun UniversalPickerPreview() = PreviewTheme {
205+
UniversalPicker(
206+
onPickerEvent = {},
207+
headerSection = {},
208+
)
209+
}

idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/IconPackConversionScreen.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ import com.composegears.tiamat.rememberSaveableViewModel
4444
import com.intellij.openapi.application.writeAction
4545
import com.intellij.openapi.vfs.VirtualFileManager
4646
import io.github.composegears.valkyrie.service.GlobalEventsHandler.PendingPathData
47+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent
48+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent.PickDirectory
49+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent.PickFiles
4750
import io.github.composegears.valkyrie.ui.domain.model.PreviewType
4851
import io.github.composegears.valkyrie.ui.foundation.theme.PreviewTheme
4952
import io.github.composegears.valkyrie.ui.platform.rememberMultiSelectDragAndDropHandler
5053
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconPackConversionState.BatchProcessing.ExportingState
5154
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconPackConversionState.BatchProcessing.IconPackCreationState
5255
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconPackConversionState.BatchProcessing.ImportValidationState
5356
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconPackConversionState.IconsPickering
54-
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.PickerEvent.PickDirectory
55-
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.PickerEvent.PickFiles
5657
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.DragAndDropOverlay
5758
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.batch.BatchProcessingStateUi
5859
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.picker.IconPackPickerStateUi

idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/IconPackConversionViewModel.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import io.github.composegears.valkyrie.parser.svgxml.SvgXmlParser
1111
import io.github.composegears.valkyrie.parser.svgxml.util.isSvg
1212
import io.github.composegears.valkyrie.parser.svgxml.util.isXml
1313
import io.github.composegears.valkyrie.settings.ValkyriesSettings
14+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent
1415
import io.github.composegears.valkyrie.ui.di.DI
1516
import io.github.composegears.valkyrie.ui.extension.updateState
1617
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ConversionEvent.OpenPreview
@@ -338,9 +339,3 @@ sealed interface ConversionEvent {
338339
data class OpenPreview(val iconContent: String) : ConversionEvent
339340
data object ExportCompleted : ConversionEvent
340341
}
341-
342-
sealed interface PickerEvent {
343-
data class PickDirectory(val path: Path) : PickerEvent
344-
data class PickFiles(val paths: List<Path>) : PickerEvent
345-
data class ClipboardText(val text: String) : PickerEvent
346-
}

idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/ui/batch/BatchProcessingStateUi.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ import androidx.compose.ui.unit.dp
4040
import io.github.composegears.valkyrie.parser.svgxml.IconNameFormatter
4141
import io.github.composegears.valkyrie.parser.svgxml.util.IconType.SVG
4242
import io.github.composegears.valkyrie.parser.svgxml.util.IconType.XML
43+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent
44+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent.ClipboardText
45+
import io.github.composegears.valkyrie.ui.common.picker.PickerEvent.PickFiles
4346
import io.github.composegears.valkyrie.ui.domain.model.PreviewType
4447
import io.github.composegears.valkyrie.ui.foundation.AppBarTitle
4548
import io.github.composegears.valkyrie.ui.foundation.CenterVerticalRow
@@ -60,8 +63,6 @@ import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconId
6063
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconName
6164
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconPack
6265
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.IconPackConversionState.BatchProcessing.IconPackCreationState
63-
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.PickerEvent
64-
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.PickerEvent.PickFiles
6566
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.ClipboardEventColumn
6667
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.batch.ui.FileTypeBadge
6768
import io.github.composegears.valkyrie.ui.screen.mode.iconpack.conversion.ui.batch.ui.IconPreviewBox
@@ -89,7 +90,7 @@ fun BatchProcessingStateUi(
8990
onPaste = { dataType ->
9091
when (dataType) {
9192
is ClipboardDataType.Files -> onPasteEvent(PickFiles(paths = dataType.paths))
92-
is ClipboardDataType.Text -> onPasteEvent(PickerEvent.ClipboardText(dataType.text))
93+
is ClipboardDataType.Text -> onPasteEvent(ClipboardText(dataType.text))
9394
}
9495
},
9596
) {

0 commit comments

Comments
 (0)