An Interest In:
Web News this Week
- April 3, 2024
- April 2, 2024
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
- March 28, 2024
From Swing to Compose Desktop 3
Welcome to the third post about my journey of transforming a Java Swing app to Compose for Desktop. Today I will cover the actual search for duplicates. Before we start: I found out that the official name is Compose for Desktop with no Jetpack prefix. I left the previous posts unchanged, but from now on will use the correct name.
The old Swing user interface calls the business logic like this:
public void setupContents() { df.clear(); df.scanDir(textfieldBasedir.getText(), true); df.removeSingles(); checksums = df.getChecksums(); updateGUI();}
df
and checksums
are simple member variables.
private TKDupeFinder df = new TKDupeFinder();private String[] checksums = {};
The Swing code for updateGUI()
looks lie this:
private void updateGUI() { boolean enabled = checksums.length > 1; buttonPrev.setEnabled(enabled); buttonNext.setEnabled(enabled); currentPos = 0; updateContents(0);}
To understand what's going on, please recall how the old app looks like:
Files are assumed duplicates if they share the same MD5 hash. That's what is stored in checksums
. buttonPrev
and buttonNext
represent the small arrow buttons, which allow you to browse through the checksums. Each checksum refers to a list of File
s. The mapping takes place in a method called updateContents()
.
private void updateContents(int offset) { modelFiles.removeAllElements(); if (checksums.length < 1) { labelInfo.setText("keine Dubletten gefunden"); } else { currentPos += offset; if (currentPos >= checksums.length) { currentPos = 0; } else if (currentPos < 0) { currentPos = checksums.length - 1; } List<File> files = df.getFiles(checksums[currentPos]); files.stream().forEach((f) -> { modelFiles.addElement(f); }); labelInfo.setText(Integer.toString(currentPos + 1) + " von " + Integer.toString(checksums.length)); } listFiles.getSelectionModel().setSelectionInterval(1, modelFiles.getSize() - 1); updateButtons();}
So how does this translate to our new Kotlin code?
A very important variable is df
. For the sake of simplicity I declare it top-level:
private val df = TKDupeFinder()
We also need to remember
two new states, currentPos
and checksums
. Just like name
I put them in the TKDupeFinderContent
composable:
val currentPos = remember { mutableStateOf(0) }val checksums = remember { mutableStateOf<List<String>>(emptyList()) }
The are passed to some of my other composables, sometimes as a state (when that composable must alter the value), sometimes just the value (when it is used to display something). You may be asking why, regarding checksums
, I do not just remember
a mutable list and change its contents. That's because the old business logic returns a list after a search, so it is easier to replace the reference rather than update the mutable list by removing the old and adding the new contents.
FirstRow(name, currentPos, checksums)SecondRow(currentPos, checksums.value.size)ThirdRow(currentPos.value, checksums.value)
Now, let's take a look at the composables. For the sake of readability I omit some unchanged code.
@Composablefun FirstRow(name: MutableState<TextFieldValue>, currentPos: MutableState<Int>, checksums: MutableState<List<String>>) { Row( ) { Button( onClick = { df.clear() df.scanDir(name.value.text, true) df.removeSingles() currentPos.value = 0 checksums.value = df.checksums.toList() }, modifier = Modifier.alignByBaseline(), enabled = File(name.value.text).isDirectory ) { Text("Find") } }}
I guess the most interesting part here is inside onClick()
. The search logic remains unchanged (invoking clear()
, scanDir()
and removeSingles()
. But through changing currentPos
and checksums
I can nicely trigger a ui refresh.
Next is SecondRow
:
@Composablefun SecondRow(currentPos: MutableState<Int>, checksumsSize: Int) { val current = currentPos.value Row( ) { Button(onClick = { currentPos.value -= 1 }, enabled = current > 0) { Text("\u140A") } MySpacer() Button(onClick = { currentPos.value += 1 }, enabled = (current + 1) < checksumsSize) { Text("\u1405") } MySpacer() Text(text = if (checksumsSize > 0) { "${currentPos.value + 1} of $checksumsSize" } else "No duplicates found") }}
currentPos
is passed as a state, because button clicks need to alter it, whereas checksumsSize
is not changed but used only for checks and output.
Finally, ThirdRow
.
Until today the list simply showed three fixed texts. Now I present the duplicates like this:
@Composablefun ThirdRow(currentPos: Int, checksums: List<String>) { val scrollState = rememberScrollState() ScrollableColumn( scrollState = scrollState, modifier = Modifier.fillMaxSize().padding(8.dp), ) { if (checksums.isNotEmpty()) df.getFiles(checksums[currentPos]).forEach { Text(it.absolutePath) } }}
Here, too, both arguments do not represent a remembered state but its value
, because they are not altered.
This is how the app looks now:
We for sure can beautify the visuals of the list. That's a topic for a future post. The next thing I will cover is list handling. The old app has two buttons to view or delete duplicate files. I am curious how I will map this behavior to Material Design. So please stay tuned.
From Swing to Jetpack Compose Desktop #1
From Swing to Jetpack Compose Desktop #2
Original Link: https://dev.to/tkuenneth/from-swing-to-compose-desktop-3-23ck
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To