diff --git a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/bottomSheets/OrgTreeBottomSheetScreen.kt b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/bottomSheets/OrgTreeBottomSheetScreen.kt index 0719247c6..2ac0cd5ef 100644 --- a/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/bottomSheets/OrgTreeBottomSheetScreen.kt +++ b/common/src/commonMain/kotlin/org/hisp/dhis/common/screens/bottomSheets/OrgTreeBottomSheetScreen.kt @@ -14,6 +14,8 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import org.hisp.dhis.common.screens.previews.lorem +import org.hisp.dhis.common.screens.previews.lorem_medium import org.hisp.dhis.mobile.ui.designsystem.component.Button import org.hisp.dhis.mobile.ui.designsystem.component.ButtonStyle import org.hisp.dhis.mobile.ui.designsystem.component.ColumnComponentContainer @@ -73,12 +75,25 @@ private class OrgTreeItemsFakeRepo { isOpen = false, hasChildren = false, ), + OrgTreeItem( + uid = "31", + label = lorem_medium, + isOpen = false, + hasChildren = false, + ), + OrgTreeItem( + uid = "41", + label = lorem, + isOpen = false, + hasChildren = false, + ), + ) private val childrenOrgItems = listOf( OrgTreeItem( uid = "12-1", - label = "Vijayawada", + label = "Vijayawada-$lorem", isOpen = false, level = 1, hasChildren = false, diff --git a/designsystem/src/androidMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt b/designsystem/src/androidMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt new file mode 100644 index 000000000..ae4b0a3c0 --- /dev/null +++ b/designsystem/src/androidMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt @@ -0,0 +1,36 @@ +package org.hisp.dhis.mobile.ui.designsystem.component.internal + +import android.graphics.Rect +import android.view.ViewTreeObserver +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalView + +@Composable +actual fun keyboardAsState(): State { + val keyboardState = remember { mutableStateOf(Keyboard.Closed) } + val view = LocalView.current + DisposableEffect(view) { + val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { + val rect = Rect() + view.getWindowVisibleDisplayFrame(rect) + val screenHeight = view.rootView.height + val keypadHeight = screenHeight - rect.bottom + keyboardState.value = if (keypadHeight > screenHeight * 0.15) { + Keyboard.Opened + } else { + Keyboard.Closed + } + } + view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) + + onDispose { + view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) + } + } + + return keyboardState +} diff --git a/designsystem/src/androidMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt b/designsystem/src/androidMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt deleted file mode 100644 index 436a83833..000000000 --- a/designsystem/src/androidMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.hisp.dhis.mobile.ui.designsystem.component.internal.bottomSheet - -import android.annotation.SuppressLint -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext - -// TODO - hack to get navigation bar padding does not take into account IME padding (reflection) -// TODO - Should be remove when google publish https://issuetracker.google.com/issues/274872542 -@Composable -@SuppressLint("DiscouragedApi") -actual fun rememberDimensionByName(name: String): Int { - val resources = LocalContext.current.resources - return remember { - val id = resources.getIdentifier(name, "dimen", "android") - if (id == 0) 0 else resources.getDimensionPixelSize(id) - } -} diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/BottomSheet.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/BottomSheet.kt index 76ed519df..f8cb369af 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/BottomSheet.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/BottomSheet.kt @@ -6,13 +6,12 @@ import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -25,9 +24,13 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow @@ -35,7 +38,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch -import org.hisp.dhis.mobile.ui.designsystem.component.internal.bottomSheet.rememberDimensionByName +import org.hisp.dhis.mobile.ui.designsystem.component.internal.Keyboard +import org.hisp.dhis.mobile.ui.designsystem.component.internal.keyboardAsState import org.hisp.dhis.mobile.ui.designsystem.theme.InternalSizeValues import org.hisp.dhis.mobile.ui.designsystem.theme.Shape import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing @@ -161,10 +165,14 @@ fun BottomSheetShell( ) { val sheetState = rememberModalBottomSheetState(true) val scope = rememberCoroutineScope() - // TODO - hack to get navigation bar padding does not take into account IME padding (reflection) - // TODO - Should be remove when google publish https://issuetracker.google.com/issues/274872542 - val topInsets = WindowInsets(top = rememberDimensionByName("status_bar_height")) - val bottomInsets = WindowInsets(bottom = rememberDimensionByName("navigation_bar_height")) + val keyboardState by keyboardAsState() + + var isKeyboardOpen by remember { mutableStateOf(false) } + val showHeader by remember { derivedStateOf { !title.isNullOrBlank() && !isKeyboardOpen } } + + LaunchedEffect(keyboardState) { + isKeyboardOpen = keyboardState == Keyboard.Opened + } ModalBottomSheet( modifier = modifier, @@ -194,13 +202,11 @@ fun BottomSheetShell( } } }, - windowInsets = topInsets, ) { val canScrollForward by derivedStateOf { contentScrollState.canScrollForward } Column( - modifier = Modifier - .padding(bottomInsets.asPaddingValues()), + modifier = Modifier.systemBarsPadding(), ) { Column( modifier = Modifier @@ -210,8 +216,7 @@ fun BottomSheetShell( ) { val hasSearch = searchQuery != null && onSearchQueryChanged != null && onSearch != null - val hasTitle by derivedStateOf { !title.isNullOrBlank() } - if (hasTitle) { + if (showHeader) { BottomSheetHeader( title = title!!, subTitle = subtitle, @@ -225,7 +230,7 @@ fun BottomSheetShell( ) } - if (hasTitle && hasSearch) { + if (showHeader && hasSearch) { Spacer(Modifier.requiredHeight(16.dp)) } @@ -238,7 +243,7 @@ fun BottomSheetShell( ) } - if (hasTitle || hasSearch) { + if (showHeader || hasSearch) { if (showSectionDivider) { Divider( modifier = Modifier.fillMaxWidth() diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/OrgBottomSheet.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/OrgBottomSheet.kt index 54bb4c6ce..f2d6e8017 100644 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/OrgBottomSheet.kt +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/OrgBottomSheet.kt @@ -2,6 +2,7 @@ package org.hisp.dhis.mobile.ui.designsystem.component import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -9,10 +10,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.ClearAll @@ -24,6 +27,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -44,6 +48,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp +import org.hisp.dhis.mobile.ui.designsystem.component.internal.Keyboard +import org.hisp.dhis.mobile.ui.designsystem.component.internal.keyboardAsState import org.hisp.dhis.mobile.ui.designsystem.resource.provideDHIS2Icon import org.hisp.dhis.mobile.ui.designsystem.resource.provideStringResource import org.hisp.dhis.mobile.ui.designsystem.theme.DHIS2SCustomTextStyles @@ -92,6 +98,16 @@ fun OrgBottomSheet( var searchQuery by remember { mutableStateOf("") } var orgTreeHeight by remember { mutableStateOf(0) } val orgTreeHeightInDp = with(LocalDensity.current) { orgTreeHeight.toDp() } + val keyboardState by keyboardAsState() + + var isKeyboardOpen by remember { mutableStateOf(false) } + + LaunchedEffect(keyboardState) { + isKeyboardOpen = keyboardState == Keyboard.Opened + if (isKeyboardOpen) { + listState.scrollToItem(0) + } + } BottomSheetShell( modifier = modifier, @@ -121,7 +137,7 @@ fun OrgBottomSheet( orgTreeHeight = treeHeight } } - .requiredHeightIn(min = orgTreeHeightInDp), + .requiredHeightIn(min = if (isKeyboardOpen) Spacing.Spacing0 else orgTreeHeightInDp), ) }, buttonBlock = { @@ -172,6 +188,7 @@ private fun OrgTreeList( onItemClick: (orgUnitUid: String) -> Unit, onItemSelected: (orgUnitUid: String, checked: Boolean) -> Unit, ) { + val scrollState = rememberScrollState() val hasSearchQuery by derivedStateOf { searchQuery.isNotBlank() } if (orgTreeItems.isEmpty() && hasSearchQuery) { Text( @@ -188,7 +205,9 @@ private fun OrgTreeList( } else { LazyColumn( modifier = modifier - .testTag("ORG_TREE_LIST"), + .fillMaxWidth() + .testTag("ORG_TREE_LIST") + .horizontalScroll(scrollState), state = state, horizontalAlignment = Alignment.Start, ) { @@ -247,28 +266,48 @@ fun OrgUnitSelectorItem( contentDescription = "", ) - Text( - modifier = Modifier.weight(1f), - text = orgTreeItemLabel( - orgTreeItem = orgTreeItem, - searchQuery = searchQuery, - ), - style = DHIS2SCustomTextStyles.bodyLargeBold.copy( - fontWeight = if (orgTreeItem.selectedChildrenCount > 0 || orgTreeItem.selected) { - FontWeight.Bold - } else { - FontWeight.Normal - }, - ), - ) + val clickableModifier = if (orgTreeItem.canBeSelected) { + Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { + onItemSelected(orgTreeItem.uid, !orgTreeItem.selected) + }, + ) + } else { + Modifier + } - if (orgTreeItem.canBeSelected) { - Checkbox( - modifier = Modifier.testTag("$ITEM_CHECK_TEST_TAG${orgTreeItem.label}"), - checked = orgTreeItem.selected, - onCheckedChange = { isChecked -> - onItemSelected(orgTreeItem.uid, isChecked) - }, + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = clickableModifier, + ) { + if (orgTreeItem.canBeSelected) { + Checkbox( + modifier = Modifier.testTag("$ITEM_CHECK_TEST_TAG${orgTreeItem.label}"), + checked = orgTreeItem.selected, + onCheckedChange = { isChecked -> + onItemSelected(orgTreeItem.uid, isChecked) + }, + ) + } else { + Spacer(modifier = Modifier.size(Spacing.Spacing16)) + } + + Text( + text = orgTreeItemLabel( + orgTreeItem = orgTreeItem, + searchQuery = searchQuery, + ), + maxLines = 1, + style = DHIS2SCustomTextStyles.bodyLargeBold.copy( + fontWeight = if (orgTreeItem.selectedChildrenCount > 0 || orgTreeItem.selected) { + FontWeight.Bold + } else { + FontWeight.Normal + }, + ), ) } } diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt new file mode 100644 index 000000000..28ef4988a --- /dev/null +++ b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt @@ -0,0 +1,11 @@ +package org.hisp.dhis.mobile.ui.designsystem.component.internal + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State + +enum class Keyboard { + Opened, Closed +} + +@Composable +expect fun keyboardAsState(): State diff --git a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt b/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt deleted file mode 100644 index 798cae28b..000000000 --- a/designsystem/src/commonMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.hisp.dhis.mobile.ui.designsystem.component.internal.bottomSheet - -import androidx.compose.runtime.Composable - -// TODO - hack to get navigation bar padding does not take into account IME padding (reflection) -// TODO - Should be remove when google publish https://issuetracker.google.com/issues/274872542 - -@Composable -expect fun rememberDimensionByName(name: String): Int diff --git a/designsystem/src/desktopMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt b/designsystem/src/desktopMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt new file mode 100644 index 000000000..f3cd01e8d --- /dev/null +++ b/designsystem/src/desktopMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/Keyboard.kt @@ -0,0 +1,12 @@ +package org.hisp.dhis.mobile.ui.designsystem.component.internal + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +@Composable +actual fun keyboardAsState(): State { + val keyboardState = remember { mutableStateOf(Keyboard.Closed) } + return keyboardState +} diff --git a/designsystem/src/desktopMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt b/designsystem/src/desktopMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt deleted file mode 100644 index bcd5922d6..000000000 --- a/designsystem/src/desktopMain/kotlin/org/hisp/dhis/mobile/ui/designsystem/component/internal/bottomSheet/BottomSheetUtils.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.hisp.dhis.mobile.ui.designsystem.component.internal.bottomSheet - -import androidx.compose.runtime.Composable - -// TODO - hack to get navigation bar padding does not take into account IME padding -// TODO - Should be remove when google published https://issuetracker.google.com/issues/274872542 -@Composable -actual fun rememberDimensionByName(name: String) = 0