Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a88c063
Keep DropdownMenu open after item selection to allow multiple selections
riko111 Sep 8, 2025
7b3cb0a
Merge branch 'DroidKaigi:main' into main
riko111 Sep 8, 2025
bcde281
Merge branch 'DroidKaigi:main' into main
riko111 Sep 8, 2025
6870bea
Merge branch 'DroidKaigi:main' into main
riko111 Sep 8, 2025
c87891a
Merge branch 'DroidKaigi:main' into main
riko111 Sep 8, 2025
691ffca
Add long-press multi-selection mode for category items in DropdownMenu
riko111 Sep 9, 2025
dad2ba7
Apply spotless formatting
riko111 Sep 9, 2025
54245dc
Remove unused onFilterLongPress and simplify multi-selection behavior
riko111 Sep 9, 2025
086ce5d
Merge branch 'DroidKaigi:main' into main
riko111 Sep 9, 2025
efc037e
Remove unused onFilterLongPress and simplify multi-selection behavior
riko111 Sep 9, 2025
fe96229
Keep layout stable by fading out unselected icons and locking chip wi…
riko111 Sep 9, 2025
0cd6f88
Keep layout stable by fading out unselected icons and locking chip wi…
riko111 Sep 9, 2025
ac9f434
Remove sessionType dropdown list from tests after component removal
riko111 Sep 9, 2025
e4a21b2
Anchor DropdownMenu to a stable empty Box (menuAnchor) and remove wid…
riko111 Sep 9, 2025
3c3894b
Remove default empty lambda; make onMultiSelectFinished nullable and …
riko111 Sep 9, 2025
0b4274a
Revert "Remove default empty lambda; make onMultiSelectFinished nulla…
riko111 Sep 9, 2025
fcfa0af
Merge branch 'DroidKaigi:main' into main
riko111 Sep 9, 2025
aad08e5
spotless-check
riko111 Sep 9, 2025
c475cbd
Merge remote-tracking branch 'origin/main'
riko111 Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package io.github.droidkaigi.confsched.sessions.components

import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
Expand All @@ -25,6 +27,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
Expand All @@ -33,7 +38,6 @@ import io.github.droidkaigi.confsched.sessions.SearchScreenUiState
import io.github.droidkaigi.confsched.sessions.SessionsRes
import io.github.droidkaigi.confsched.sessions.filter_chip_category
import io.github.droidkaigi.confsched.sessions.filter_chip_day
import io.github.droidkaigi.confsched.sessions.filter_chip_session_type
import io.github.droidkaigi.confsched.sessions.filter_chip_supported_language
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
Expand Down Expand Up @@ -88,21 +92,6 @@ fun SearchFilterRow(
modifier = Modifier.testTag(SearchFilterRowFilterCategoryChipTestTag),
)
}

// Session type filter dropdown
if (filters.availableSessionTypes.isNotEmpty()) {
FilterDropdown(
label = stringResource(SessionsRes.string.filter_chip_session_type),
selectedItems = filters.selectedSessionTypes,
items = filters.availableSessionTypes,
itemLabel = { it.label.currentLangTitle },
onItemSelected = { sessionType ->
onFilterToggle(SearchScreenEvent.Filter.SessionType(sessionType))
},
modifier = Modifier.testTag(SearchFilterRowFilterSessionTypeChipTestTag),
)
}

// Language filter dropdown
if (filters.availableLanguages.isNotEmpty()) {
FilterDropdown(
Expand All @@ -126,10 +115,13 @@ private fun <T> FilterDropdown(
items: List<T>,
itemLabel: (T) -> String,
onItemSelected: (T) -> Unit,
onMultiSelectFinished: (List<T>) -> Unit = {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying default values for lambda parameters has some downsides: it can make it easy to forget to provide a value, and at a glance it’s not always clear whether a value is actually being passed. Therefore, unless there’s a specific reason, I’d prefer to remove default empty lambdas.

modifier: Modifier = Modifier,
) {
val keyboardController = LocalSoftwareKeyboardController.current
val haptics = LocalHapticFeedback.current
var expanded by remember { mutableStateOf(false) }
var isMultiSelectMode by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()

Box(modifier = modifier) {
Expand All @@ -142,17 +134,17 @@ private fun <T> FilterDropdown(
}
}.invokeOnCompletion { expanded = true }
},
modifier = Modifier,
label = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
if (selectedItems.isNotEmpty()) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
)
}
Comment on lines -150 to -155
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate your effort with this change 🙏🏻, but for this checkmark, we don’t need to control its visibility via alpha. It’s fine to keep the original display logic as it was, since this introduces an unintended visual change.

image

Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.alpha(if (selectedItems.isNotEmpty()) 1f else 0f),
)
Text(
text = if (selectedItems.isNotEmpty()) {
selectedItems.joinToString { itemLabel(it) }
Expand All @@ -162,39 +154,60 @@ private fun <T> FilterDropdown(
style = MaterialTheme.typography.labelLarge,
maxLines = 1,
)
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = null,
)
if (selectedItems.isNotEmpty()) {
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = null,
)
}
}
},
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.secondaryContainer,
selectedLabelColor = MaterialTheme.colorScheme.onSecondaryContainer,
),
)

DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
items.forEach { item ->
DropdownMenuItem(
modifier = Modifier.testTag(DropdownFilterChipTestTagPrefix.plus(item)),
leadingIcon = {
if (selectedItems.contains(item)) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
Box {
DropdownMenu(
expanded = expanded,
onDismissRequest = {
if (isMultiSelectMode) onMultiSelectFinished(selectedItems.toList())
expanded = false
isMultiSelectMode = false
},
) {
items.forEach { item ->
Row(
modifier = Modifier
.testTag(DropdownFilterChipTestTagPrefix.plus(item))
.fillMaxWidth()
.heightIn(min = 48.dp)
.combinedClickable(
onClick = {
onItemSelected(item)
if (!isMultiSelectMode) {
expanded = false
}
},
onLongClick = {
isMultiSelectMode = true
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
onItemSelected(item)
},
)
}
},
text = { Text(itemLabel(item)) },
onClick = {
onItemSelected(item)
expanded = false
},
)
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
Icons.Default.Check,
contentDescription = null,
modifier = Modifier
.padding(end = 12.dp)
.alpha(if (selectedItems.contains(item)) 1f else 0f),
)
Text(itemLabel(item), style = MaterialTheme.typography.bodyLarge)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,32 +113,7 @@ class SearchScreenTest {
}
}
}
describe("when filter session type chip click") {
doIt {
clickFilterSessionTypeChip()
}
itShould("show drop down menu") {
captureScreenWithChecks {
checkDisplayedFilterSessionTypeChip()
}
}
SearchScreenRobot.SessionType.entries.forEach { sessionType ->
describe("when click ${sessionType.name}") {
doIt {
clickSessionType(
sessionType = sessionType,
)
}
itShould("selected ${sessionType.name}") {
captureScreenWithChecks {
checkTimetableListItemBySessionType(sessionType)
checkTimetableListDisplayed()
checkTimetableListItemsDisplayed()
}
}
}
}
}
//
describe("when filter language chip click") {
doIt {
scrollToFilterLanguageChip()
Expand Down
Loading