Skip to content

Commit bf86992

Browse files
sorenoidSoren Roth
and
Soren Roth
authored
popup media element and media image thumbnail support (#449)
* Media element images wip * Media element with support for images * use colors from defaults * fix capitalization --------- Co-authored-by: Soren Roth <[email protected]>
1 parent a262d9f commit bf86992

File tree

7 files changed

+596
-1
lines changed

7 files changed

+596
-1
lines changed

toolkit/popup/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ apiValidation {
7676

7777
dependencies {
7878
api(arcgis.mapsSdk)
79+
implementation(platform(libs.coil.bom))
80+
implementation(libs.coil.compose)
7981
implementation(platform(libs.androidx.compose.bom))
8082
implementation(libs.bundles.composeCore)
8183
implementation(libs.bundles.core)

toolkit/popup/src/main/java/com/arcgismaps/toolkit/popup/Popup.kt

+18-1
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ import androidx.compose.ui.graphics.graphicsLayer
4646
import androidx.compose.ui.semantics.contentDescription
4747
import androidx.compose.ui.semantics.semantics
4848
import androidx.compose.ui.unit.dp
49-
import com.arcgismaps.mapping.popup.FieldsPopupElement
5049
import com.arcgismaps.mapping.popup.AttachmentsPopupElement
50+
import com.arcgismaps.mapping.popup.FieldsPopupElement
51+
import com.arcgismaps.mapping.popup.MediaPopupElement
5152
import com.arcgismaps.mapping.popup.Popup
5253
import com.arcgismaps.mapping.popup.TextPopupElement
5354
import com.arcgismaps.toolkit.popup.internal.element.attachment.AttachmentsElementState
@@ -56,6 +57,9 @@ import com.arcgismaps.toolkit.popup.internal.element.attachment.rememberAttachme
5657
import com.arcgismaps.toolkit.popup.internal.element.fieldselement.FieldsElementState
5758
import com.arcgismaps.toolkit.popup.internal.element.fieldselement.FieldsPopupElement
5859
import com.arcgismaps.toolkit.popup.internal.element.fieldselement.rememberFieldsElementState
60+
import com.arcgismaps.toolkit.popup.internal.element.media.MediaElementState
61+
import com.arcgismaps.toolkit.popup.internal.element.media.MediaPopupElement
62+
import com.arcgismaps.toolkit.popup.internal.element.media.rememberMediaElementState
5963
import com.arcgismaps.toolkit.popup.internal.element.state.PopupElementStateCollection
6064
import com.arcgismaps.toolkit.popup.internal.element.state.mutablePopupElementStateCollection
6165
import com.arcgismaps.toolkit.popup.internal.element.textelement.TextElementState
@@ -166,6 +170,12 @@ private fun PopupBody(popupState: PopupState, onFileClicked: (ViewableFile?) ->
166170
)
167171
}
168172

173+
is MediaPopupElement -> {
174+
MediaPopupElement(
175+
entry.state as MediaElementState
176+
)
177+
}
178+
169179
else -> {
170180
// other popup elements are not created
171181
}
@@ -229,6 +239,13 @@ internal fun rememberStates(
229239
)
230240
}
231241

242+
is MediaPopupElement -> {
243+
states.add(
244+
element,
245+
rememberMediaElementState(element = element, popup = popup)
246+
)
247+
}
248+
232249
else -> {
233250
// TODO remove for release
234251
println("encountered element of type ${element::class.java}")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2024 Esri
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.arcgismaps.toolkit.popup.internal.element.media
18+
19+
import androidx.compose.foundation.shape.RoundedCornerShape
20+
import androidx.compose.material3.MaterialTheme
21+
import androidx.compose.runtime.Composable
22+
import androidx.compose.ui.graphics.Color
23+
import androidx.compose.ui.unit.Dp
24+
import androidx.compose.ui.unit.dp
25+
26+
/**
27+
* A central place for theming values. To be promoted to a public theme type.
28+
*/
29+
internal object MediaElementDefaults {
30+
@Composable
31+
fun shapes(): MediaElementShapes = MediaElementShapes(
32+
borderThickness = 1.dp,
33+
containerShape = RoundedCornerShape(5.dp),
34+
tileShape = RoundedCornerShape(8.dp),
35+
galleryPadding = 15.dp,
36+
tileStrokeWidth = 0.5.dp,
37+
tileWidth = 276.dp,
38+
tileHeight = 225.dp
39+
)
40+
41+
@Suppress("unused")
42+
@Composable
43+
fun colors() : MediaElementColors = MediaElementColors(
44+
containerColor = MaterialTheme.colorScheme.background,
45+
galleryContainerColor = MaterialTheme.colorScheme.onBackground,
46+
borderColor = MaterialTheme.colorScheme.outline,
47+
tileBorderColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.6f),
48+
tileTextBackgroundColor = MaterialTheme.colorScheme.onBackground,
49+
tileTextColor = MaterialTheme.colorScheme.background
50+
)
51+
}
52+
53+
internal data class MediaElementShapes(
54+
val borderThickness: Dp,
55+
val containerShape: RoundedCornerShape,
56+
val tileShape: RoundedCornerShape,
57+
val galleryPadding: Dp,
58+
val tileStrokeWidth: Dp,
59+
val tileWidth: Dp,
60+
val tileHeight: Dp
61+
)
62+
63+
internal data class MediaElementColors(
64+
val containerColor : Color,
65+
val galleryContainerColor: Color,
66+
val tileBorderColor : Color,
67+
val borderColor : Color,
68+
val tileTextBackgroundColor: Color,
69+
val tileTextColor: Color
70+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2024 Esri
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.arcgismaps.toolkit.popup.internal.element.media
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.Immutable
21+
import androidx.compose.runtime.saveable.Saver
22+
import androidx.compose.runtime.saveable.rememberSaveable
23+
import com.arcgismaps.mapping.popup.MediaPopupElement
24+
import com.arcgismaps.mapping.popup.Popup
25+
import com.arcgismaps.mapping.popup.PopupMedia
26+
import com.arcgismaps.mapping.popup.PopupMediaType
27+
import com.arcgismaps.toolkit.popup.internal.element.state.PopupElementState
28+
29+
/**
30+
* Represents the state of an [MediaPopupElement]
31+
*/
32+
@Immutable
33+
internal class MediaElementState(
34+
val description: String,
35+
val title: String,
36+
val media: List<PopupMediaState>,
37+
override val id : Int = createId(),
38+
) : PopupElementState() {
39+
40+
constructor(mediaPopupElement: MediaPopupElement): this(
41+
description = mediaPopupElement.description,
42+
title = mediaPopupElement.title,
43+
media = mediaPopupElement.media
44+
.filter { it.type is PopupMediaType.Image }
45+
.map { PopupMediaState(it) }
46+
)
47+
48+
companion object {
49+
fun Saver(
50+
element: MediaPopupElement
51+
): Saver<MediaElementState, Any> = Saver(
52+
save = { null },
53+
restore = {
54+
MediaElementState(
55+
element
56+
)
57+
}
58+
)
59+
60+
}
61+
}
62+
63+
@Composable
64+
internal fun rememberMediaElementState(
65+
element: MediaPopupElement,
66+
popup: Popup
67+
): MediaElementState {
68+
return rememberSaveable(
69+
inputs = arrayOf(popup, element),
70+
saver = MediaElementState.Saver(element)
71+
) {
72+
MediaElementState(
73+
element
74+
)
75+
}
76+
}
77+
78+
/**
79+
* Represents the state of a [PopupMedia value].
80+
*/
81+
internal class PopupMediaState(
82+
val title: String,
83+
val caption: String,
84+
@Suppress("unused") val refreshInterval: Long,
85+
@Suppress("unused") val linkUrl: String,
86+
val sourceUrl: String,
87+
val type: PopupMediaType
88+
) {
89+
constructor(media: PopupMedia) : this(
90+
title = media.title,
91+
caption = media.caption,
92+
refreshInterval = media.imageRefreshInterval,
93+
linkUrl = media.value?.linkUrl ?: "",
94+
sourceUrl = media.value?.sourceUrl ?: "",
95+
type = media.type
96+
)
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2024 Esri
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.arcgismaps.toolkit.popup.internal.element.media
18+
19+
import androidx.compose.foundation.layout.Arrangement
20+
import androidx.compose.foundation.layout.Column
21+
import androidx.compose.foundation.layout.padding
22+
import androidx.compose.foundation.lazy.LazyListState
23+
import androidx.compose.foundation.lazy.LazyRow
24+
import androidx.compose.foundation.lazy.items
25+
import androidx.compose.foundation.lazy.rememberLazyListState
26+
import androidx.compose.runtime.Composable
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import androidx.compose.ui.unit.dp
30+
import com.arcgismaps.mapping.popup.PopupMediaType
31+
import com.arcgismaps.toolkit.popup.internal.ui.ExpandableCard
32+
33+
@Composable
34+
internal fun MediaPopupElement(
35+
state: MediaElementState
36+
) {
37+
MediaPopupElement(
38+
title = state.title,
39+
description = state.description,
40+
stateId = state.id,
41+
media = state.media
42+
)
43+
}
44+
45+
@Composable
46+
private fun MediaPopupElement(
47+
description: String,
48+
title: String,
49+
@Suppress("UNUSED_PARAMETER") stateId: Int,
50+
media: List<PopupMediaState>
51+
) {
52+
ExpandableCard(
53+
title = title,
54+
description = description
55+
) {
56+
Column(
57+
modifier = Modifier.padding(MediaElementDefaults.shapes().galleryPadding)
58+
) {
59+
val listState = rememberLazyListState()
60+
MediaGallery(listState, media)
61+
}
62+
}
63+
}
64+
65+
@Composable
66+
private fun MediaGallery(state: LazyListState, media: List<PopupMediaState>) {
67+
LazyRow(
68+
state = state,
69+
horizontalArrangement = Arrangement.spacedBy(15.dp),
70+
) {
71+
items(media, key = { it.title + it.type + it.caption }) {
72+
MediaTile(it)
73+
}
74+
}
75+
}
76+
77+
@Preview
78+
@Composable
79+
private fun MediaPopupElementPreview() {
80+
MediaPopupElement(
81+
title = "Media",
82+
description = "description of Media",
83+
stateId = 1,
84+
media = listOf(
85+
PopupMediaState(
86+
"Photo 1.jpg",
87+
"caption",
88+
1234L,
89+
linkUrl = "",
90+
sourceUrl = "https://i.postimg.cc/65yws9mR/Screenshot-2024-02-02-at-6-20-49-PM.png",
91+
PopupMediaType.Image,
92+
)
93+
)
94+
)
95+
}

0 commit comments

Comments
 (0)