diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt index 632608d12..823b06eed 100644 --- a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt @@ -46,8 +46,12 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import com.arcgismaps.mapping.popup.AttachmentsPopupElement import com.arcgismaps.mapping.popup.Popup import com.arcgismaps.mapping.popup.TextPopupElement +import com.arcgismaps.toolkit.popup.internal.element.attachment.AttachmentsElementState +import com.arcgismaps.toolkit.popup.internal.element.attachment.AttachmentsPopupElement +import com.arcgismaps.toolkit.popup.internal.element.attachment.rememberAttachmentElementState import com.arcgismaps.toolkit.popup.internal.element.state.PopupElementStateCollection import com.arcgismaps.toolkit.popup.internal.element.state.mutablePopupElementStateCollection import com.arcgismaps.toolkit.popup.internal.element.textelement.TextElementState @@ -89,6 +93,9 @@ private fun Popup(popupState: PopupState, modifier: Modifier = Modifier) { LaunchedEffect(popup) { popupState.popup.evaluateExpressions() + popupState.popup.evaluatedElements + .filterIsInstance() + .firstOrNull()?.fetchAttachments() evaluated = true } @@ -142,6 +149,12 @@ private fun PopupBody(popupState: PopupState) { ) } + is AttachmentsPopupElement -> { + AttachmentsPopupElement( + state = entry.state as AttachmentsElementState + ) + } + else -> { // other popup elements are not created } @@ -191,6 +204,13 @@ internal fun rememberStates( ) } + is AttachmentsPopupElement -> { + states.add( + element, + rememberAttachmentElementState(popup = popup, element = element) + ) + } + else -> { // TODO remove for release println("encountered element of type ${element::class.java}") diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentTile.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentTile.kt new file mode 100644 index 000000000..e7e0af1b0 --- /dev/null +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentTile.kt @@ -0,0 +1,277 @@ +/* + * Copyright 2024 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arcgismaps.toolkit.popup.internal.element.attachment + +import android.text.format.Formatter +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDownward +import androidx.compose.material.icons.outlined.ErrorOutline +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.arcgismaps.LoadStatus +import com.arcgismaps.mapping.popup.PopupAttachmentType +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +internal fun AttachmentTile( + state: PopupAttachmentState +) { + val loadStatus by state.loadStatus.collectAsState() + val coroutineScope = rememberCoroutineScope() + val thumbnail by state.thumbnail + val colors = AttachmentsElementDefaults.colors() + val shapes = AttachmentsElementDefaults.shapes() + Box( + modifier = Modifier + .width(shapes.tileWidth) + .height(shapes.tileHeight) + .clip(shape = shapes.tileShape) + .border( + border = BorderStroke(shapes.tileStrokeWidth, colors.tileBorderColor), + shape = shapes.tileShape + ) + .clickable { + if (loadStatus is LoadStatus.NotLoaded || loadStatus is LoadStatus.FailedToLoad) { + // load attachment + state.loadAttachment(coroutineScope) + } + // TODO open attachment viewer in `else` here + } + ) { + when (loadStatus) { + LoadStatus.Loaded -> LoadedView( + thumbnail = thumbnail, + attachmentType = state.type, + title = state.name + ) + + LoadStatus.Loading -> DefaultView( + title = state.name, + size = state.size, + attachmentType = state.type, + isLoading = true, + isError = false + ) + + LoadStatus.NotLoaded -> DefaultView( + title = state.name, + size = state.size, + attachmentType = state.type, + isLoading = false, + isError = false + ) + + is LoadStatus.FailedToLoad -> DefaultView( + title = state.name, + size = state.size, + attachmentType = state.type, + isLoading = false, + isError = true + ) + } + } +} + +@Composable +private fun LoadedView( + thumbnail: ImageBitmap?, + title: String, + attachmentType: PopupAttachmentType, + modifier: Modifier = Modifier +) { + + Box( + modifier = modifier + .fillMaxSize() + ) { + if (thumbnail != null) { + Image( + bitmap = thumbnail, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } else { + Icon( + imageVector = attachmentType.getIcon(), + contentDescription = null, + modifier = Modifier + .fillMaxSize() + .padding(top = 10.dp, bottom = 25.dp) + .align(Alignment.Center) + ) + } + Column( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .height(20.dp) + .background( + MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.7f + ) + ), + verticalArrangement = Arrangement.Center + ) { + Title( + text = title, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 5.dp), + color = MaterialTheme.colorScheme.background + ) + } + } +} + +@Composable +private fun DefaultView( + title: String, + size: Long, + attachmentType: PopupAttachmentType, + isLoading: Boolean, + isError: Boolean, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .padding(horizontal = 5.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + AttachmentSize(size = size) + Icon( + imageVector = Icons.Outlined.ArrowDownward, + contentDescription = null, + modifier = Modifier.size(11.dp) + ) + } + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + strokeWidth = 2.dp + ) + } else if (isError) { + Image( + imageVector = Icons.Outlined.ErrorOutline, + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error) + ) + } else { + Icon( + imageVector = attachmentType.getIcon(), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + } + Title(text = title, modifier = Modifier) + } +} + +@Composable +private fun Title( + text: String, + modifier: Modifier = Modifier, + color: Color = Color.Unspecified, + style: TextStyle = MaterialTheme.typography.labelSmall +) { + Text( + text = text, + color = color, + style = style, + textAlign = TextAlign.Center, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = modifier.padding(horizontal = 1.dp) + ) +} + +@Composable +private fun AttachmentSize( + size: Long, modifier: + Modifier = Modifier +) { + val context = LocalContext.current + val fileSize = Formatter.formatFileSize(context, size) + Text( + text = fileSize, + style = MaterialTheme.typography.labelSmall.copy( + fontWeight = FontWeight.W300, + fontSize = 9.sp + ), + overflow = TextOverflow.Ellipsis, + modifier = modifier + .padding(horizontal = 1.dp) + ) +} + +@Preview(showBackground = true) +@Composable +internal fun PreviewAttachmentTile() { + AttachmentTile( + state = PopupAttachmentState( + name = "Some attachment", + size = 1234L, + type = PopupAttachmentType.Other, + loadStatus = MutableStateFlow(LoadStatus.NotLoaded), + onLoadAttachment = { Result.success(Unit) }, + onLoadThumbnail = null + ) + ) +} diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsElementDefaults.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsElementDefaults.kt new file mode 100644 index 000000000..a255a7e3b --- /dev/null +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsElementDefaults.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2024 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arcgismaps.toolkit.popup.internal.element.attachment + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * A central place for theming values. To be promoted to a public theme type. + */ +internal object AttachmentsElementDefaults { + @Composable + fun shapes(): AttachmentElementShapes = AttachmentElementShapes( + borderThickness = 1.dp, + containerShape = RoundedCornerShape(5.dp), + tileShape = RoundedCornerShape(8.dp), + galleryPadding = 15.dp, + tileStrokeWidth = 0.5.dp, + tileWidth = 92.dp, + tileHeight = 75.dp + ) + + @Suppress("unused") + @Composable + fun colors() : AttachmentsElementColors = AttachmentsElementColors( + containerColor = MaterialTheme.colorScheme.background, + galleryContainerColor = MaterialTheme.colorScheme.onBackground, + borderColor = MaterialTheme.colorScheme.outline, + tileBorderColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.6f) + + ) +} + +internal data class AttachmentElementShapes( + val borderThickness: Dp, + val containerShape: RoundedCornerShape, + val tileShape: RoundedCornerShape, + val galleryPadding: Dp, + val tileStrokeWidth: Dp, + val tileWidth: Dp, + val tileHeight: Dp +) + +internal data class AttachmentsElementColors( + val containerColor : Color, + val galleryContainerColor: Color, + val tileBorderColor : Color, + val borderColor : Color +) diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsElementState.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsElementState.kt new file mode 100644 index 000000000..4cf76dafd --- /dev/null +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsElementState.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2024 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arcgismaps.toolkit.popup.internal.element.attachment + +import android.graphics.drawable.BitmapDrawable +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FileCopy +import androidx.compose.material.icons.outlined.FilePresent +import androidx.compose.material.icons.outlined.Image +import androidx.compose.material.icons.outlined.VideoCameraBack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.vector.ImageVector +import com.arcgismaps.LoadStatus +import com.arcgismaps.mapping.popup.AttachmentsPopupElement +import com.arcgismaps.mapping.popup.Popup +import com.arcgismaps.mapping.popup.PopupAttachment +import com.arcgismaps.mapping.popup.PopupAttachmentType +import com.arcgismaps.toolkit.popup.internal.element.state.PopupElementState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +/** + * Represents the state of an [AttachmentsPopupElement] + */ +@Immutable +internal class AttachmentsElementState( + val description: String, + val title: String, + val attachments: List, + override val id : Int = createId(), +) : PopupElementState() { + + constructor(attachmentPopupElement: AttachmentsPopupElement): this( + description = attachmentPopupElement.description, + title = attachmentPopupElement.title, + attachments = attachmentPopupElement.attachments.map { PopupAttachmentState(it) } + ) + + companion object { + fun Saver( + element: AttachmentsPopupElement + ): Saver = Saver( + save = { null }, + restore = { + AttachmentsElementState( + element + ) + } + ) + + } +} + +@Composable +internal fun rememberAttachmentElementState( + element: AttachmentsPopupElement, + popup: Popup +): AttachmentsElementState { + val scope = rememberCoroutineScope() + return rememberSaveable( + inputs = arrayOf(popup, element), + saver = AttachmentsElementState.Saver(element) + ) { + AttachmentsElementState( + element + ).also { + // NOTE: core issue with PopupAttachments not abiding the instance id contract here. + // Loaded attachments are coming back NotLoaded after rotation. + // https://devtopia.esri.com/runtime/apollo/issues/681 + it.attachments + .filter { state -> + state.loadStatus.value == LoadStatus.Loaded + && state.type == PopupAttachmentType.Image + } + .forEach { state -> + state.loadAttachment(scope) + } + } + } +} + +/** + * Represents the state of a [PopupAttachment]. + * + * @param name The name of the attachment. + * @param size The size of the attachment. + * @param loadStatus The load status of the attachment. + * @param onLoadAttachment A function that loads the attachment. + * @param onLoadThumbnail A function that loads the thumbnail of the attachment. + */ +internal class PopupAttachmentState( + val name: String, + val size: Long, + val type: PopupAttachmentType, + val loadStatus: StateFlow, + private val onLoadAttachment: suspend () -> Result, + private val onLoadThumbnail: (suspend () -> Result)? = null +) { + private val _thumbnail: MutableState = mutableStateOf(null) + + /** + * The thumbnail of the attachment. This is `null` until [loadAttachment] is called. + */ + val thumbnail: State = _thumbnail + + constructor(attachment: PopupAttachment) : this( + name = attachment.name, + size = attachment.size, + type = attachment.type, + loadStatus = attachment.loadStatus, + onLoadAttachment = attachment::retryLoad, + onLoadThumbnail = if (attachment.type == PopupAttachmentType.Image) { + attachment::createFullImage + } else { + null + } + ) + + /** + * Loads the attachment and its thumbnail. + */ + fun loadAttachment(scope: CoroutineScope) { + scope.launch { + onLoadAttachment().onSuccess { + onLoadThumbnail?.invoke()?.onSuccess { + if (it != null) { + _thumbnail.value = it.bitmap.asImageBitmap() + } + } + } + } + } +} + +@Composable +internal fun PopupAttachmentType.getIcon(): ImageVector = when (this) { + PopupAttachmentType.Image -> Icons.Outlined.Image + PopupAttachmentType.Video -> Icons.Outlined.VideoCameraBack + PopupAttachmentType.Document -> Icons.Outlined.FilePresent + PopupAttachmentType.Other -> Icons.Outlined.FileCopy +} diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsPopupElement.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsPopupElement.kt new file mode 100644 index 000000000..24abf3bec --- /dev/null +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/attachment/AttachmentsPopupElement.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2024 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arcgismaps.toolkit.popup.internal.element.attachment + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.arcgismaps.LoadStatus +import com.arcgismaps.mapping.popup.PopupAttachmentType +import com.arcgismaps.toolkit.popup.internal.ui.ExpandableCard +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +internal fun AttachmentsPopupElement( + state: AttachmentsElementState +) { + AttachmentsPopupElement( + title = state.title, + description = state.description, + stateId = state.id, + attachments = state.attachments + ) +} + +@Composable +private fun AttachmentsPopupElement( + description: String, + title: String, + @Suppress("UNUSED_PARAMETER") stateId: Int, + attachments: List +) { + ExpandableCard( + title = title, + description = description + ) { + Column( + modifier = Modifier.padding(AttachmentsElementDefaults.shapes().galleryPadding) + ) { + val listState = rememberLazyListState() + AttachmentGallery(listState, attachments) + } + } +} + +@Composable +private fun AttachmentGallery(state: LazyListState, attachments: List) { + LazyRow( + state = state, + horizontalArrangement = Arrangement.spacedBy(15.dp), + ) { + items(attachments, key = { it.name + it.type + it.size }) { + AttachmentTile(it) + } + } +} + +@Preview +@Composable +private fun AttachmentsPopupElementPreview() { + AttachmentsPopupElement( + title = "Attachments", + description = "description of attachments", + stateId = 1, + attachments = listOf( + PopupAttachmentState( + "Photo 1.jpg", + 2024, + PopupAttachmentType.Image, + MutableStateFlow(LoadStatus.Loaded), + { Result.success(Unit) }, + { Result.success(null) } + ) + ) + ) +} diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/state/PopupElementState.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/state/PopupElementState.kt index 76d043249..281626e2c 100644 --- a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/state/PopupElementState.kt +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/state/PopupElementState.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.Immutable */ @Immutable internal abstract class PopupElementState { + // abstract so derived classes can parcelize it if possible abstract val id: Int companion object { diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/textelement/TextPopupElement.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/textelement/TextPopupElement.kt index 5cb134653..7be354d64 100644 --- a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/textelement/TextPopupElement.kt +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/element/textelement/TextPopupElement.kt @@ -91,7 +91,7 @@ private fun HTML(content: String) { */ @Composable internal fun TextPopupElement(state: TextElementState) { - ExpandableCard { + ExpandableCard(toggleable = false) { HTML(content = state.value) } } diff --git a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/ui/ExpandableCard.kt b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/ui/ExpandableCard.kt index fec3740e7..7198fef60 100644 --- a/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/ui/ExpandableCard.kt +++ b/toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/internal/ui/ExpandableCard.kt @@ -56,14 +56,14 @@ import com.arcgismaps.toolkit.popup.R internal fun ExpandableCard( title: String = "", description: String = "", - elementCount: Int = 1, + toggleable: Boolean = true, content: @Composable () -> Unit = {} ) { // TODO: promote to public theme. val shapes = ExpandableCardDefaults.shapes() val colors = ExpandableCardDefaults.colors() var expanded by rememberSaveable { mutableStateOf(true) } - val dynamic = elementCount > 1 + Card( colors = CardDefaults.cardColors( containerColor = colors.containerColor @@ -78,10 +78,10 @@ internal fun ExpandableCard( ExpandableHeader( title = title, description = description, - elementCount = elementCount, + expandable = toggleable, isExpanded = expanded ) { - if (dynamic) { + if (toggleable) { expanded = !expanded } } @@ -98,16 +98,16 @@ internal fun ExpandableCard( private fun ExpandableHeader( title: String = "", description: String = "", - elementCount: Int, + expandable: Boolean, isExpanded: Boolean, onClick: () -> Unit ) { - if (title.isEmpty() && description.isEmpty() && elementCount == 1) return + if (title.isEmpty() && description.isEmpty() && !expandable) return val shapes = ExpandableCardDefaults.shapes() Row( Modifier .fillMaxWidth() - .applyIf(elementCount > 1) { + .applyIf(expandable) { clickable { onClick() } @@ -139,7 +139,7 @@ private fun ExpandableHeader( } - if (elementCount > 1) { + if (expandable) { Crossfade(targetState = isExpanded, label = "expandPopupElement") { Icon( modifier = Modifier @@ -158,8 +158,8 @@ internal fun ExpandableHeaderPreview() { ExpandableHeader( title = "The Title", description = "the description", - 2, - true + expandable = true, + isExpanded = true ) {} } @@ -168,8 +168,7 @@ internal fun ExpandableHeaderPreview() { private fun ExpandableCardPreview() { ExpandableCard( description = "Foo", - title = "Title", - elementCount = 2 + title = "Title" ) { Text( "Hello World",