Skip to content

Commit 5a2e030

Browse files
committed
Performance improvements and preload commands
1 parent 19d3057 commit 5a2e030

10 files changed

Lines changed: 58 additions & 31 deletions

File tree

common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ kotlin {
4040
sourceSets {
4141
commonMain {
4242
dependencies {
43+
implementation(libs.kotlinx.collections.immutable)
4344
}
4445
}
4546
commonTest {

common/src/commonMain/kotlin/com/linuxcommandlibrary/shared/MarkdownParser.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.linuxcommandlibrary.shared
22

3+
import kotlinx.collections.immutable.toImmutableList
4+
35
/**
46
* Shared markdown parsing utilities for text formatting, code blocks, and man page links.
57
*/
@@ -120,7 +122,7 @@ object MarkdownParser {
120122
sections.add(
121123
TipSectionElement.Code(
122124
command = cleanMarkdownCommand(codeContent),
123-
elements = elements,
125+
elements = elements.toImmutableList(),
124126
),
125127
)
126128
i++
@@ -140,7 +142,7 @@ object MarkdownParser {
140142
sections.add(
141143
TipSectionElement.Code(
142144
command = cleanMarkdownCommand(codeContent),
143-
elements = elements,
145+
elements = elements.toImmutableList(),
144146
),
145147
)
146148
}

common/src/commonMain/kotlin/com/linuxcommandlibrary/shared/TipSectionElement.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.linuxcommandlibrary.shared
22

3+
import kotlinx.collections.immutable.ImmutableList
4+
35
/**
46
* Represents text formatting elements within tip sections.
57
*/
@@ -16,7 +18,7 @@ sealed class TextElement {
1618
sealed class TipSectionElement {
1719
data class Text(val elements: List<TextElement>) : TipSectionElement()
1820
data class Blockquote(val elements: List<TextElement>) : TipSectionElement()
19-
data class Code(val command: String, val elements: List<CommandElement>) : TipSectionElement()
21+
data class Code(val command: String, val elements: ImmutableList<CommandElement>) : TipSectionElement()
2022
data class Table(
2123
val headers: List<List<TextElement>>,
2224
val rows: List<List<List<TextElement>>>,

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/App.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
6161
import androidx.navigation.compose.rememberNavController
6262
import androidx.navigation.toRoute
6363
import com.linuxcommandlibrary.app.data.BasicsRepository
64+
import com.linuxcommandlibrary.app.data.CommandsRepository
6465
import com.linuxcommandlibrary.app.platform.backIcon
6566
import com.linuxcommandlibrary.app.ui.composables.AppIcon
6667
import com.linuxcommandlibrary.app.ui.composables.rememberIconPainter
@@ -81,15 +82,21 @@ import com.linuxcommandlibrary.app.ui.screens.tips.TipsViewModel
8182
import com.linuxcommandlibrary.app.ui.theme.LinuxTheme
8283
import com.linuxcommandlibrary.app.ui.theme.LocalCustomColors
8384
import com.linuxcommandlibrary.shared.platform.ReviewHandler
85+
import kotlinx.coroutines.Dispatchers
86+
import kotlinx.coroutines.withContext
8487
import org.koin.compose.koinInject
8588
import org.koin.core.parameter.parametersOf
8689

8790
@Composable
8891
fun App(initialDeeplink: String? = null) {
8992
val reviewHandler: ReviewHandler = koinInject()
93+
val commandsRepository: CommandsRepository = koinInject()
9094
LaunchedEffect(Unit) {
9195
reviewHandler.incrementAppStartCount()
9296
reviewHandler.requestReviewIfNeeded()
97+
withContext(Dispatchers.Default) {
98+
commandsRepository.getCommands()
99+
}
93100
}
94101

95102
LinuxTheme {
@@ -260,18 +267,14 @@ fun LinuxApp(initialDeeplink: String? = null) {
260267
BasicGroupsScreen(viewModel = viewModel, onNavigate = onNavigate)
261268
}
262269

263-
composable<Route.CommandDetail> { backStackEntry ->
264-
val route = backStackEntry.toRoute<Route.CommandDetail>()
265-
val viewModel: CommandDetailViewModel = koinInject { parametersOf(route.commandName) }
266-
CommandDetailScreen(viewModel = viewModel, onNavigate = onNavigate)
270+
composable<Route.CommandDetail> {
271+
commandDetailViewModel?.let { viewModel ->
272+
CommandDetailScreen(viewModel = viewModel, onNavigate = onNavigate)
273+
}
267274
}
268275
}
269276

270-
val isSearchVisible by remember(searchTextValue) {
271-
derivedStateOf {
272-
searchTextValue.value.text.isNotEmpty() && !isOnCommandDetail
273-
}
274-
}
277+
val isSearchVisible = searchTextValue.value.text.isNotEmpty() && !isOnCommandDetail
275278
AnimatedVisibility(
276279
visible = isSearchVisible,
277280
enter = fadeIn(animationSpec = tween(300)),

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/data/CommandsRepository.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.linuxcommandlibrary.shared.platform.AssetReader
66
class CommandsRepository(private val assetReader: AssetReader) {
77

88
private var cachedCommands: List<CommandInfo>? = null
9+
private var cachedCommandNames: Set<String>? = null
910

1011
fun getCommands(): List<CommandInfo> {
1112
cachedCommands?.let { return it }
@@ -24,25 +25,27 @@ class CommandsRepository(private val assetReader: AssetReader) {
2425
}
2526

2627
cachedCommands = commands
28+
cachedCommandNames = commands.mapTo(HashSet()) { it.name }
2729
return commands
2830
}
2931

3032
fun getCommandsByQuery(query: String): List<CommandInfo> {
31-
val commands = getCommands()
3233
val lowerQuery = query.lowercase()
3334

34-
return commands
35-
.filter { it.name.lowercase().contains(lowerQuery) }
35+
return getCommands()
36+
.map { cmd -> cmd to cmd.name.lowercase() }
37+
.filter { (_, lowerName) -> lowerName.contains(lowerQuery) }
3638
.sortedWith(
3739
compareBy(
38-
{ it.name.lowercase() != lowerQuery },
39-
{ !it.name.lowercase().startsWith(lowerQuery) },
40-
{ it.name },
40+
{ (_, lowerName) -> lowerName != lowerQuery },
41+
{ (_, lowerName) -> !lowerName.startsWith(lowerQuery) },
42+
{ (cmd, _) -> cmd.name },
4143
),
4244
)
45+
.map { (cmd, _) -> cmd }
4346
}
4447

45-
fun hasCommand(name: String): Boolean = getCommands().any { it.name == name }
48+
fun hasCommand(name: String): Boolean = name in (cachedCommandNames ?: getCommands().let { cachedCommandNames!! })
4649

4750
fun getSections(commandName: String): List<CommandSectionInfo> = try {
4851
val content = assetReader.readFile("commands/$commandName.md") ?: return emptyList()

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/data/TipsRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class TipsRepository(private val assetReader: AssetReader) {
7777
sections.add(
7878
TipSectionElement.Code(
7979
command = MarkdownParser.cleanMarkdownCommand(codeContent),
80-
elements = elements,
80+
elements = elements.toImmutableList(),
8181
),
8282
)
8383
i++

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/ui/composables/TipSectionContent.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.linuxcommandlibrary.app.ui.composables
33
import androidx.compose.foundation.layout.padding
44
import androidx.compose.material.Text
55
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.remember
67
import androidx.compose.ui.Modifier
78
import androidx.compose.ui.graphics.Color
89
import androidx.compose.ui.text.AnnotatedString
@@ -15,7 +16,6 @@ import androidx.compose.ui.unit.Dp
1516
import androidx.compose.ui.unit.dp
1617
import com.linuxcommandlibrary.shared.TextElement
1718
import com.linuxcommandlibrary.shared.TipSectionElement
18-
import kotlinx.collections.immutable.toImmutableList
1919

2020
fun buildTextElementString(
2121
elements: List<TextElement>,
@@ -52,15 +52,21 @@ fun TipSectionContent(
5252
sections.forEach { section ->
5353
when (section) {
5454
is TipSectionElement.Text -> {
55+
val annotatedString = remember(section.elements, textColor) {
56+
buildTextElementString(section.elements, textColor)
57+
}
5558
Text(
56-
text = buildTextElementString(section.elements, textColor),
59+
text = annotatedString,
5760
color = textColor,
5861
)
5962
}
6063

6164
is TipSectionElement.Blockquote -> {
65+
val annotatedString = remember(section.elements, textColor) {
66+
buildTextElementString(section.elements, textColor)
67+
}
6268
Text(
63-
text = buildTextElementString(section.elements, textColor),
69+
text = annotatedString,
6470
color = textColor,
6571
modifier = Modifier.padding(start = 8.dp, bottom = 8.dp),
6672
)
@@ -69,7 +75,7 @@ fun TipSectionContent(
6975
is TipSectionElement.Code -> {
7076
CommandView(
7177
command = section.command,
72-
elements = section.elements.toImmutableList(),
78+
elements = section.elements,
7379
onNavigate = onNavigate,
7480
verticalPadding = commandVerticalPadding,
7581
)

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/ui/screens/commandlist/CommandListScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fun CommandListScreen(
3030
onNavigate: (String) -> Unit,
3131
) {
3232
val commands by viewModel.commands.collectAsState()
33+
val bookmarkedNames by viewModel.bookmarkedNames.collectAsState()
3334

3435
LazyColumn(
3536
modifier = Modifier
@@ -44,7 +45,7 @@ fun CommandListScreen(
4445
CommandListItem(
4546
command = command,
4647
onNavigate = onNavigate,
47-
isBookmarked = viewModel.hasBookmark(command.name),
48+
isBookmarked = command.name in bookmarkedNames,
4849
)
4950
}
5051
}

composeApp/src/commonMain/kotlin/com/linuxcommandlibrary/app/ui/screens/commandlist/CommandListViewModel.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import com.linuxcommandlibrary.app.data.CommandInfo
44
import com.linuxcommandlibrary.app.data.CommandsRepository
55
import com.linuxcommandlibrary.app.data.DataManager
66
import kotlinx.collections.immutable.ImmutableList
7+
import kotlinx.collections.immutable.ImmutableSet
78
import kotlinx.collections.immutable.persistentListOf
9+
import kotlinx.collections.immutable.persistentSetOf
810
import kotlinx.collections.immutable.toImmutableList
11+
import kotlinx.collections.immutable.toImmutableSet
912
import kotlinx.coroutines.CoroutineScope
1013
import kotlinx.coroutines.Dispatchers
1114
import kotlinx.coroutines.flow.MutableStateFlow
1215
import kotlinx.coroutines.flow.asStateFlow
13-
import kotlinx.coroutines.flow.update
1416
import kotlinx.coroutines.launch
1517

1618
class CommandListViewModel(
@@ -21,17 +23,24 @@ class CommandListViewModel(
2123
private val _commands = MutableStateFlow<ImmutableList<CommandInfo>>(persistentListOf())
2224
val commands = _commands.asStateFlow()
2325

26+
private val _bookmarkedNames = MutableStateFlow<ImmutableSet<String>>(persistentSetOf())
27+
val bookmarkedNames = _bookmarkedNames.asStateFlow()
28+
2429
init {
2530
updateCommands()
2631
}
2732

2833
fun updateCommands() {
2934
scope.launch(Dispatchers.Default) {
30-
_commands.update {
31-
commandsRepository.getCommands().sortedBy { !hasBookmark(it.name) }.toImmutableList()
32-
}
35+
val allCommands = commandsRepository.getCommands()
36+
val bookmarks = allCommands
37+
.filter { dataManager.hasBookmark(it.name) }
38+
.map { it.name }
39+
.toImmutableSet()
40+
_bookmarkedNames.value = bookmarks
41+
_commands.value = allCommands
42+
.sortedBy { it.name !in bookmarks }
43+
.toImmutableList()
3344
}
3445
}
35-
36-
fun hasBookmark(name: String): Boolean = dataManager.hasBookmark(name)
3746
}

0 commit comments

Comments
 (0)