Skip to content

Commit a8d6f96

Browse files
Merge branch 'main' into renovate/kotlin-monorepo
2 parents ca7e630 + 7573ded commit a8d6f96

File tree

8 files changed

+241
-156
lines changed

8 files changed

+241
-156
lines changed

composeApp/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ kotlin {
2424
implementation(libs.androidx.lifecycle.viewmodel)
2525
implementation(libs.androidx.lifecycle.runtime.compose)
2626
implementation(libs.material.icons)
27+
implementation(libs.material3)
2728
}
2829
desktopMain.dependencies {
2930
implementation(compose.desktop.currentOs)

composeApp/src/commonMain/composeResources/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@
1111
<string name="stats_filelines">Lines: %1$s</string>
1212
<string name="stats_filereadtime">Read time: %1$s</string>
1313
<string name="search_label">Search Key/Value</string>
14+
<string name="display_mode_render">Render</string>
15+
<string name="display_mode_edit">Edit</string>
1416
</resources>

composeApp/src/desktopMain/kotlin/com/sebastianneubauer/jsontreeviewer/App.kt

Lines changed: 141 additions & 95 deletions
Large diffs are not rendered by default.

composeApp/src/desktopMain/kotlin/com/sebastianneubauer/jsontreeviewer/State.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ object Contract {
77
data object InitialLoading: State
88
data object Loading: State
99
data class Error(val error: ErrorType): State
10-
data class Content(val json: String, val stats: Stats, val searchDirection: SearchDirection?): State
10+
data class Content(
11+
val json: String,
12+
val searchDirection: SearchDirection?,
13+
val displayMode: DisplayMode
14+
): State
1115
}
1216

1317
sealed class ErrorType {
1418
data object DataDragAndDropError: ErrorType()
1519
data object FileReadError: ErrorType()
1620
data object CopyPasteError: ErrorType()
17-
data class JsonParserError(val message: String): ErrorType()
1821
}
1922

2023
sealed interface SearchDirection {
@@ -24,13 +27,10 @@ object Contract {
2427
data class Previous(override val increment: Int): SearchDirection
2528
}
2629

27-
data class Stats(
28-
val filePath: String,
29-
val fileName: String,
30-
val fileSize: String,
31-
val fileReadTime: String,
32-
val fileLines: String,
33-
)
30+
enum class DisplayMode {
31+
Render,
32+
Edit
33+
}
3434
}
3535

3636
val supportedFileTypes = listOf("json", "txt")

composeApp/src/desktopMain/kotlin/com/sebastianneubauer/jsontreeviewer/ViewModel.kt

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,11 @@ import kotlinx.coroutines.CoroutineScope
1313
import kotlinx.coroutines.launch
1414
import java.awt.Toolkit
1515
import java.awt.datatransfer.DataFlavor
16-
import kotlin.io.path.fileSize
17-
import kotlin.time.DurationUnit
18-
import kotlin.time.TimeSource
19-
import kotlin.time.measureTimedValue
2016
import androidx.compose.runtime.State as ComposeState
2117

2218
class ViewModel(
2319
private val coroutineScope: CoroutineScope,
2420
private val ioDispatcher: CoroutineDispatcher,
25-
private val timeSource: TimeSource,
2621
) {
2722
private var viewModelState = mutableStateOf<State>(State.Initial)
2823
val state: ComposeState<State> = viewModelState
@@ -38,19 +33,12 @@ class ViewModel(
3833
val newState = state.file
3934
.takeIf { it.exists() && it.isFile && it.extension in supportedFileTypes }
4035
?.let { validFile ->
41-
val (json, time) = timeSource.measureTimedValue { validFile.readText() }
42-
val fileSize = validFile.toPath().fileSize() / 1024F
36+
val json = validFile.readText()
4337

4438
State.Content(
4539
json = json,
4640
searchDirection = null,
47-
stats = Contract.Stats(
48-
filePath = validFile.path,
49-
fileName = validFile.name,
50-
fileSize = "%.2f".format(fileSize) + "KB",
51-
fileReadTime = time.toString(unit = DurationUnit.MILLISECONDS, decimals = 3),
52-
fileLines = json.lines().count().toString()
53-
)
41+
displayMode = Contract.DisplayMode.Render,
5442
)
5543
}
5644
?: State.Error(error = Contract.ErrorType.FileReadError)
@@ -64,8 +52,13 @@ class ViewModel(
6452

6553
fun onKeyEvent(event: KeyEvent): Boolean {
6654
return if ((event.isCtrlPressed || event.isMetaPressed) && event.key == Key.V) {
67-
viewModelState.value = getStateFromClipboardData()
68-
true
55+
val state = viewModelState.value
56+
if(state is State.Content) {
57+
false
58+
} else {
59+
viewModelState.value = getStateFromClipboardData()
60+
true
61+
}
6962
} else if(event.key == Key.DirectionDown || event.key == Key.Enter) {
7063
val newState = getStateFromSearchDirectionEvent(isDownEvent = true)
7164
viewModelState.value = newState
@@ -79,6 +72,24 @@ class ViewModel(
7972
}
8073
}
8174

75+
fun updateDisplayMode(displayMode: Contract.DisplayMode) {
76+
val state = viewModelState.value
77+
if(state is State.Content) {
78+
viewModelState.value = state.copy(displayMode = displayMode)
79+
}
80+
}
81+
82+
fun updateJson(json: String) {
83+
val state = viewModelState.value
84+
if(state is State.Content) {
85+
viewModelState.value = state.copy(json = json)
86+
}
87+
}
88+
89+
fun reset() {
90+
viewModelState.value = State.Initial
91+
}
92+
8293
private fun getStateFromClipboardData(): State {
8394
val clipboardString = try {
8495
Toolkit.getDefaultToolkit()
@@ -92,13 +103,7 @@ class ViewModel(
92103
State.Content(
93104
json = clipboardString,
94105
searchDirection = null,
95-
stats = Contract.Stats(
96-
filePath = "from clipboard",
97-
fileName = "n/a",
98-
fileSize = "n/a",
99-
fileReadTime = "n/a",
100-
fileLines = clipboardString.lines().count().toString()
101-
)
106+
displayMode = Contract.DisplayMode.Render,
102107
)
103108
} else {
104109
State.Error(error = Contract.ErrorType.CopyPasteError)
@@ -123,10 +128,4 @@ class ViewModel(
123128
currentState.copy(searchDirection = newSearchDirection)
124129
} else currentState
125130
}
126-
127-
fun showJsonParsingError(throwable: Throwable) {
128-
viewModelState.value = State.Error(
129-
error = Contract.ErrorType.JsonParserError(message = throwable.localizedMessage)
130-
)
131-
}
132131
}

composeApp/src/desktopMain/kotlin/com/sebastianneubauer/jsontreeviewer/main.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ import kotlinx.coroutines.CoroutineScope
88
import kotlinx.coroutines.Dispatchers
99
import kotlinx.coroutines.Job
1010
import org.jetbrains.compose.resources.stringResource
11-
import kotlin.time.TimeSource
1211

1312
fun main() = application {
1413
val coroutineScope = CoroutineScope(Job())
1514
val viewModel = ViewModel(
1615
coroutineScope = coroutineScope,
1716
ioDispatcher = Dispatchers.IO,
18-
timeSource = TimeSource.Monotonic
1917
)
2018

2119
Window(

composeApp/src/desktopTest/kotlin/com/sebastianneubauer/jsontreeviewer/ViewModelTest.kt

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,22 @@ import org.junit.Test
99
import java.io.File
1010
import java.nio.file.Files
1111
import kotlin.test.assertEquals
12-
import kotlin.time.TestTimeSource
1312

1413
@OptIn(ExperimentalCoroutinesApi::class)
1514
class ViewModelTest {
1615

1716
private val dispatcher = UnconfinedTestDispatcher()
18-
private val timeSource = TestTimeSource()
1917
private val json = "{\"Hello\": \"World\"}"
2018

2119
private val underTest = ViewModel(
2220
coroutineScope = TestScope(dispatcher),
2321
ioDispatcher = dispatcher,
24-
timeSource = timeSource
22+
)
23+
24+
private val contentState = State.Content(
25+
json = json,
26+
searchDirection = null,
27+
displayMode = Contract.DisplayMode.Render
2528
)
2629

2730
private fun getFile(): File {
@@ -42,17 +45,7 @@ class ViewModelTest {
4245
underTest.updateDragAndDropState(DragAndDropState.Success(file))
4346

4447
assertEquals(
45-
expected = State.Content(
46-
json = json,
47-
searchDirection = null,
48-
stats = Contract.Stats(
49-
filePath = file.path,
50-
fileName = file.name,
51-
fileSize = "0.02KB",
52-
fileReadTime = "0.000ms",
53-
fileLines = "1"
54-
)
55-
),
48+
expected = contentState,
5649
actual = underTest.state.value
5750
)
5851
}
@@ -101,15 +94,59 @@ class ViewModelTest {
10194
}
10295

10396
@Test
104-
fun `jsonTree parsing error returns Error state`() {
105-
val throwable = Throwable(message = "")
106-
underTest.showJsonParsingError(throwable)
97+
fun `updateDisplayMode updates the Content state with the new DisplayMode`() {
98+
// Arrange: Start in Content state with Render mode
99+
val file = getFile()
100+
underTest.updateDragAndDropState(DragAndDropState.Success(file))
101+
assertEquals(
102+
expected = contentState,
103+
actual = underTest.state.value
104+
)
105+
106+
underTest.updateDisplayMode(Contract.DisplayMode.Edit)
107107

108108
assertEquals(
109-
expected = State.Error(
110-
error = Contract.ErrorType.JsonParserError(message = throwable.localizedMessage)
109+
expected = contentState.copy(
110+
displayMode = Contract.DisplayMode.Edit
111111
),
112112
actual = underTest.state.value
113113
)
114114
}
115+
116+
@Test
117+
fun `updateJson updates the Content state with the new json`() {
118+
// Arrange: Start in Content state with Render mode
119+
val file = getFile()
120+
underTest.updateDragAndDropState(DragAndDropState.Success(file))
121+
assertEquals(
122+
expected = contentState,
123+
actual = underTest.state.value
124+
)
125+
126+
underTest.updateJson("hello")
127+
128+
assertEquals(
129+
expected = contentState.copy(json = "hello"),
130+
actual = underTest.state.value
131+
)
132+
}
133+
134+
@Test
135+
fun `reset updates the state to the initial state`() {
136+
// Arrange: Start in Content state with Render mode
137+
val file = getFile()
138+
underTest.updateDragAndDropState(DragAndDropState.Success(file))
139+
assertEquals(
140+
expected = contentState,
141+
actual = underTest.state.value
142+
)
143+
144+
underTest.reset()
145+
146+
assertEquals(
147+
expected = State.Initial,
148+
actual = underTest.state.value
149+
)
150+
}
151+
115152
}

gradle/libs.versions.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
[versions]
2-
androidx-lifecycle = "2.8.4"
2+
androidx-lifecycle = "2.9.1"
33
compose-plugin = "1.8.2"
44
junit = "4.13.2"
55
kotlin = "2.2.0"
6-
kotlinx-coroutines = "1.10.1"
6+
kotlinx-coroutines = "1.10.2"
77
material-icons = "1.7.3"
8+
material3 = "1.3.2"
89
jsontree = "2.5.0"
910
hotreload = "1.0.0-beta04"
1011

@@ -15,6 +16,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
1516
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
1617
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
1718
material-icons = { group = "org.jetbrains.compose.material", name = "material-icons-extended", version.ref = "material-icons" }
19+
material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
1820
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
1921
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
2022
jsontree = { group = "com.sebastianneubauer.jsontree", name = "jsontree", version.ref = "jsontree" }

0 commit comments

Comments
 (0)