Skip to content

Commit

Permalink
introduce ViewModel for speakers screen
Browse files Browse the repository at this point in the history
  • Loading branch information
kropp committed Feb 12, 2025
1 parent 1a0e435 commit 26c0efe
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 20 deletions.
2 changes: 2 additions & 0 deletions shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.jetbrains.kotlinconf.screens.PrivacyPolicyViewModel
import org.jetbrains.kotlinconf.screens.ScheduleViewModel
import org.jetbrains.kotlinconf.screens.SessionViewModel
import org.jetbrains.kotlinconf.screens.SettingsViewModel
import org.jetbrains.kotlinconf.screens.SpeakersViewModel
import org.jetbrains.kotlinconf.screens.StartNotificationsViewModel
import org.jetbrains.kotlinconf.storage.ApplicationStorage
import org.jetbrains.kotlinconf.storage.MultiplatformSettingsStorage
Expand Down Expand Up @@ -80,6 +81,7 @@ private fun koinConfiguration(context: ApplicationContext) = koinConfiguration {
viewModelOf(::NewsListViewModel)
viewModelOf(::StartNotificationsViewModel)
viewModelOf(::NewsDetailViewModel)
viewModelOf(::SpeakersViewModel)
}

modules(appModule, viewModelModule)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import org.jetbrains.kotlinconf.navigation.PartnersScreen
import org.jetbrains.kotlinconf.navigation.PrivacyPolicyScreen
import org.jetbrains.kotlinconf.navigation.ScheduleScreen
import org.jetbrains.kotlinconf.navigation.SessionScreen
import org.jetbrains.kotlinconf.navigation.SpeakerDetailsScreen
import org.jetbrains.kotlinconf.navigation.SpeakersScreen
import org.jetbrains.kotlinconf.ui.components.Divider
import org.jetbrains.kotlinconf.ui.components.MainNavDestination
Expand Down Expand Up @@ -80,8 +81,7 @@ fun MainScreen(
}
composable<SpeakersScreen> {
Speakers(
service.speakers.collectAsState().value,
onSpeaker = { rootNavController.navigate(SpeakerDetailsScreen(it)) },
onSpeaker = { rootNavController.navigate(SpeakerDetailsScreen(it)) }
)
}
composable<ScheduleScreen> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -22,7 +23,6 @@ import kotlinconfapp.ui_components.generated.resources.main_header_search_hint
import kotlinconfapp.ui_components.generated.resources.search_24
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.kotlinconf.SpeakerId
import org.jetbrains.kotlinconf.Speakers
import org.jetbrains.kotlinconf.ui.components.Divider
import org.jetbrains.kotlinconf.ui.components.MainHeaderContainer
import org.jetbrains.kotlinconf.ui.components.MainHeaderContainerState
Expand All @@ -31,17 +31,17 @@ import org.jetbrains.kotlinconf.ui.components.MainHeaderTitleBar
import org.jetbrains.kotlinconf.ui.components.SpeakerCard
import org.jetbrains.kotlinconf.ui.components.TopMenuButton
import org.jetbrains.kotlinconf.ui.theme.KotlinConfTheme
import org.jetbrains.kotlinconf.utils.containsDiacritics
import org.jetbrains.kotlinconf.utils.removeDiacritics
import kotlinconfapp.ui_components.generated.resources.Res as UiRes
import org.koin.compose.viewmodel.koinViewModel

@Composable
fun Speakers(
speakers: Speakers,
onSpeaker: (SpeakerId) -> Unit,
viewModel: SpeakersViewModel = koinViewModel()
) {
var searchState by remember { mutableStateOf(MainHeaderContainerState.Title) }
var searchText by remember { mutableStateOf("") }
val searchText by viewModel.searchText.collectAsState()
val filtered by viewModel.filteredSpeakers.collectAsState()

Column(Modifier.fillMaxSize()) {
MainHeaderContainer(
Expand All @@ -61,28 +61,18 @@ fun Speakers(
searchContent = {
MainHeaderSearchBar(
searchValue = searchText,
onSearchValueChange = { searchText = it },
onSearchValueChange = { viewModel.searchText.value = it },
onClose = {
searchState = MainHeaderContainerState.Title
searchText = ""
viewModel.searchText.value = ""
},
onClear = { searchText = "" }
onClear = { viewModel.searchText.value = "" }
)
}
)

Divider(1.dp, KotlinConfTheme.colors.strokePale)

val filtered = speakers.all.filter {
// Look for exact matches if diacritics are present, ignore all diacritics otherwise
val diacriticsSearch = searchText.containsDiacritics()
val targetName = if (diacriticsSearch) it.name else it.name.removeDiacritics()
val targetPosition = if (diacriticsSearch) it.position else it.position.removeDiacritics()
val searchPattern = searchText.toRegex(RegexOption.IGNORE_CASE)

searchText.isEmpty() || searchPattern.containsMatchIn(targetName) || searchPattern.containsMatchIn(targetPosition)
}

LazyColumn(Modifier.fillMaxSize()) {
items(filtered) { speaker ->
SpeakerCard(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.jetbrains.kotlinconf.screens

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import org.jetbrains.kotlinconf.ConferenceService
import org.jetbrains.kotlinconf.Speakers
import org.jetbrains.kotlinconf.utils.containsDiacritics
import org.jetbrains.kotlinconf.utils.removeDiacritics

class SpeakersViewModel(
service: ConferenceService,
) : ViewModel() {
var searchText = MutableStateFlow("")

private val speakers: StateFlow<Speakers> = service.speakers
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), Speakers())

val filteredSpeakers = combine(speakers, searchText) { speakers, searchText ->
if (searchText.isEmpty()) {
speakers.all
} else {
speakers.all.filter {
// Look for exact matches if diacritics are present, ignore all diacritics otherwise
val diacriticsSearch = searchText.containsDiacritics()
val targetName = if (diacriticsSearch) it.name else it.name.removeDiacritics()
val targetPosition = if (diacriticsSearch) it.position else it.position.removeDiacritics()
val searchPattern = searchText.toRegex(RegexOption.IGNORE_CASE)

searchPattern.containsMatchIn(targetName) || searchPattern.containsMatchIn(targetPosition)
}
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), speakers.value.all)
}

0 comments on commit 26c0efe

Please sign in to comment.