Skip to content

Popup attachment element #430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c79b40b
pull compose bom into androidTest and test Implementations when neede…
Apr 30, 2024
db4bc96
Address warnings from version upgrades.
Apr 30, 2024
a8d3e85
preliminary setup of state collection.
May 1, 2024
ef7da62
Merge branch 'v.next' into sor10874/sprintx/fix-warnings
May 1, 2024
bdf0edb
add initialization support to the Popup composable.
May 1, 2024
8e79478
workaround for known issue in gradle task graph progress. opt in for …
May 1, 2024
df9c405
Merge remote-tracking branch 'origin/sor10874/sprintx/fix-warnings' i…
May 1, 2024
4400c06
Merge branch 'v.next' into sor10874/state-collection
May 3, 2024
ea75a99
Merge branch 'feature-branches/popup' into sor10874/state-collection
May 3, 2024
8ed508a
Merge branch 'sor10874/sprintx/fix-warnings' into sor10874/state-coll…
May 3, 2024
9e5e96b
view model added to the Popup App.
May 3, 2024
b224eca
Merge branch 'feature-branches/popup' into sor10874/state-collection
May 8, 2024
ac76965
move files around. make state objects Parcelable.
May 8, 2024
ea954bd
ensure states are not created until expressions are evaluated.
May 8, 2024
0b4f7d5
Merge branch 'feature-branches/popup' into sor10874/state-collection
May 8, 2024
c888b6b
attachment initial support
May 9, 2024
45c0cc2
Revert "attachment initial support"
May 10, 2024
e871ab6
attachment initial support
May 9, 2024
385d9a8
Atachments hooked up to state collection and rendering in the Popup.
May 9, 2024
2353a22
remove file from other branch
May 10, 2024
882d106
Update toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt
sorenoid May 10, 2024
b89b97e
remove Parcelable super class from base state class
May 10, 2024
a6db4e8
Merge remote-tracking branch 'origin/sor10874/state-collection' into …
May 10, 2024
e0d75cd
make expandable card more like field maps.
May 11, 2024
79b85e7
add defaults
May 11, 2024
11f1350
popup attachments
May 11, 2024
7f019cb
remove comment
sorenoid May 14, 2024
573ddf0
address PR feedback
May 14, 2024
f45f565
Merge remote-tracking branch 'origin/sor10874/update-expandable-card'…
May 14, 2024
7a2d326
fix formatting
May 15, 2024
372224e
default to expandable card expanded
May 15, 2024
951cce3
merge in expandable card refactors
May 15, 2024
d7b6877
merge in expandable card refactors
May 15, 2024
e35f5ba
support document and other content types.
May 15, 2024
fc89c19
add all shapes sizes and color to theme defaults object
May 15, 2024
4a06a42
merge in expandable card changes.
May 15, 2024
39096ca
remove newlines
May 20, 2024
06036b4
remove element count, add toggleable parameter to expandable card
May 21, 2024
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
20 changes: 20 additions & 0 deletions toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,6 +93,9 @@ private fun Popup(popupState: PopupState, modifier: Modifier = Modifier) {

LaunchedEffect(popup) {
popupState.popup.evaluateExpressions()
popupState.popup.evaluatedElements
.filterIsInstance<AttachmentsPopupElement>()
.firstOrNull()?.fetchAttachments()
evaluated = true
}

Expand Down Expand Up @@ -142,6 +149,12 @@ private fun PopupBody(popupState: PopupState) {
)
}

is AttachmentsPopupElement -> {
AttachmentsPopupElement(
state = entry.state as AttachmentsElementState
)
}

else -> {
// other popup elements are not created
}
Expand Down Expand Up @@ -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}")
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
)
}
Original file line number Diff line number Diff line change
@@ -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
)
Loading