Skip to content

Commit 6a47e29

Browse files
authored
Add support for UNAssociationsPopupElement to Popup (#981)
* WIP * WIP * Update Popup and UtilityAssociationsElementState * WIP * WIP * clean up code * clean up imports and unused code * WIP * remove duplicate title from old popup remove support for hasEdits from contentAwareTopbar * move popupelementstates creation to PopupState replace form references with popup remove caching attachments in popupstate * update micro app * optimize imports * enable isNavigationEnabled add doc * add support for ignore list * add support for dynamic entity * update api file deprecate popup composable * update micro app * update micro app to not show close icon * add copyright disable lint for missing translations * remove parcelize where it is not being used update API file * address code review comments * remove support for isNavigationEnabled and NavigationAction * remove showConfirmationDialog in UtilityAssocationsDetails * add support for display count add some suggested optimizations * Optimize imports optimize mediaElementState * update logic in mediaPopupElement composable * enable close button add doc * add search box to filter UtilityAssociationGroupResults with a user specified string * update showall behavior on search update AssociationItem properties to display with the latest on FeatureBranch * Add fix for navigation not working Change bottomsheet behavior for onDismiss * Add default value for UtilityElement title * reset geoElement when the bottomsheet is dismissed * Pick up refactor changes from feature forms Update API file * update micro app * address code review comments * FIx navigation issue
1 parent 02c1b54 commit 6a47e29

33 files changed

+2699
-491
lines changed

microapps/PopupApp/app/src/main/java/com/arcgismaps/toolkit/popupapp/MainActivity.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,34 @@ package com.arcgismaps.toolkit.popupapp
2323
import android.os.Bundle
2424
import androidx.activity.ComponentActivity
2525
import androidx.activity.compose.setContent
26-
import androidx.activity.viewModels
2726
import androidx.compose.runtime.Composable
2827
import com.arcgismaps.ArcGISEnvironment
2928
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallenge
3029
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallengeHandler
3130
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallengeResponse
3231
import com.arcgismaps.httpcore.authentication.TokenCredential
3332
import com.arcgismaps.toolkit.popupapp.screens.mapscreen.MainScreen
34-
import com.arcgismaps.toolkit.popupapp.screens.mapscreen.MapViewModel
3533
import com.arcgismaps.toolkit.popupapp.ui.theme.PopupAppTheme
3634

3735
class MainActivity : ComponentActivity() {
3836
override fun onCreate(savedInstanceState: Bundle?) {
3937
super.onCreate(savedInstanceState)
40-
val viewModel: MapViewModel by viewModels { MapViewModel.Factory }
4138
ArcGISEnvironment.authenticationManager.arcGISAuthenticationChallengeHandler =
4239
TestArcGISAuthenticationChallengeHandler(
4340
BuildConfig.webMapUser,
4441
BuildConfig.webMapPassword
4542
)
4643
setContent {
4744
PopupAppTheme {
48-
PopupApp(viewModel)
45+
PopupApp()
4946
}
5047
}
5148
}
5249
}
5350

5451
@Composable
55-
fun PopupApp(viewModel: MapViewModel) {
56-
MainScreen(viewModel)
52+
fun PopupApp() {
53+
MainScreen()
5754
}
5855

5956
class TestArcGISAuthenticationChallengeHandler(

microapps/PopupApp/app/src/main/java/com/arcgismaps/toolkit/popupapp/screens/mapscreen/MainScreen.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,23 @@ package com.arcgismaps.toolkit.popupapp.screens.mapscreen
2222

2323
import android.widget.Toast
2424
import androidx.compose.animation.animateContentSize
25+
import androidx.compose.foundation.layout.WindowInsets
26+
import androidx.compose.foundation.layout.asPaddingValues
2527
import androidx.compose.foundation.layout.fillMaxSize
2628
import androidx.compose.foundation.layout.padding
29+
import androidx.compose.foundation.layout.safeDrawing
2730
import androidx.compose.material3.BottomSheetScaffold
2831
import androidx.compose.material3.ExperimentalMaterial3Api
2932
import androidx.compose.material3.SheetValue
3033
import androidx.compose.material3.rememberBottomSheetScaffoldState
3134
import androidx.compose.material3.rememberStandardBottomSheetState
3235
import androidx.compose.runtime.Composable
36+
import androidx.compose.runtime.LaunchedEffect
3337
import androidx.compose.runtime.rememberCoroutineScope
3438
import androidx.compose.ui.Modifier
3539
import androidx.compose.ui.platform.LocalContext
3640
import androidx.compose.ui.unit.dp
41+
import androidx.lifecycle.viewmodel.compose.viewModel
3742
import com.arcgismaps.data.Feature
3843
import com.arcgismaps.mapping.GeoElement
3944
import com.arcgismaps.mapping.layers.FeatureLayer
@@ -53,7 +58,7 @@ private fun unselectFeature(feature: GeoElement?, layer: Layer?) {
5358
}
5459
@OptIn(ExperimentalMaterial3Api::class)
5560
@Composable
56-
fun MainScreen(viewModel: MapViewModel) {
61+
fun MainScreen(viewModel: MapViewModel = viewModel()) {
5762
val scope = rememberCoroutineScope()
5863
val context = LocalContext.current
5964

@@ -63,13 +68,29 @@ fun MainScreen(viewModel: MapViewModel) {
6368
skipHiddenState = false
6469
)
6570
)
71+
LaunchedEffect(scaffoldState.bottomSheetState.currentValue) {
72+
if (scaffoldState.bottomSheetState.currentValue == SheetValue.Hidden) {
73+
resetSelection(viewModel)
74+
}
75+
}
76+
6677
BottomSheetScaffold(
6778
sheetContent = {
68-
if (viewModel.popup != null) {
69-
Popup(viewModel.popup!!, Modifier.animateContentSize())
79+
val state = viewModel.popupState
80+
if (state != null) {
81+
Popup(
82+
state,
83+
Modifier.animateContentSize(),
84+
onDismiss = { scope.launch {
85+
resetSelection(viewModel)
86+
scaffoldState.bottomSheetState.hide()
87+
} }
88+
)
7089
}
7190
},
72-
modifier = Modifier.fillMaxSize(),
91+
modifier = Modifier
92+
.fillMaxSize()
93+
.padding(WindowInsets.safeDrawing.asPaddingValues()),
7394
scaffoldState = scaffoldState,
7495
sheetSwipeEnabled = true,
7596
topBar = null
@@ -82,17 +103,15 @@ fun MainScreen(viewModel: MapViewModel) {
82103
.padding(padding)
83104
.fillMaxSize(),
84105
onSingleTapConfirmed = {
106+
resetSelection(viewModel)
85107
scope.launch {
86108
viewModel.proxy.identifyLayers(
87109
screenCoordinate = it.screenCoordinate,
88110
tolerance = 22.dp,
89111
returnPopupsOnly = true
90112
).onSuccess { results ->
91113
if (results.isEmpty()) {
92-
unselectFeature(viewModel.geoElement, viewModel.layer)
93-
viewModel.setPopup(null)
94114
viewModel.setLayer(null)
95-
viewModel.setGeoElement(null)
96115
if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) {
97116
scaffoldState.bottomSheetState.hide()
98117
}
@@ -133,11 +152,6 @@ fun MainScreen(viewModel: MapViewModel) {
133152
).show()
134153
}
135154
} else {
136-
unselectFeature(
137-
viewModel.geoElement,
138-
viewModel.layer
139-
)
140-
141155
when (newLayer) {
142156
is FeatureLayer -> {
143157
// the Popup exists on a FeatureLayer
@@ -164,7 +178,7 @@ fun MainScreen(viewModel: MapViewModel) {
164178
throw IllegalStateException("popups on sublayers are not supported by the PopupApp")
165179
}
166180
}
167-
viewModel.setPopup(popup)
181+
viewModel.updatePopupState(popup)
168182
scaffoldState.bottomSheetState.expand()
169183
}
170184
} catch (e: Exception) {
@@ -185,6 +199,12 @@ fun MainScreen(viewModel: MapViewModel) {
185199
}
186200
}
187201

202+
private fun resetSelection(viewModel: MapViewModel) {
203+
viewModel.updatePopupState(null)
204+
unselectFeature(viewModel.geoElement, viewModel.layer)
205+
viewModel.setGeoElement(null)
206+
}
207+
188208
private fun GeoElement?.sameSelection(other: GeoElement): Boolean =
189209
if (this == null) {
190210
false

microapps/PopupApp/app/src/main/java/com/arcgismaps/toolkit/popupapp/screens/mapscreen/MapViewModel.kt

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@ import android.app.Application
2020
import androidx.compose.runtime.MutableState
2121
import androidx.compose.runtime.mutableStateOf
2222
import androidx.lifecycle.AndroidViewModel
23-
import androidx.lifecycle.SavedStateHandle
24-
import androidx.lifecycle.ViewModel
25-
import androidx.lifecycle.ViewModelProvider
26-
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
27-
import androidx.lifecycle.createSavedStateHandle
28-
import androidx.lifecycle.viewmodel.CreationExtras
23+
import androidx.lifecycle.viewModelScope
2924
import com.arcgismaps.mapping.ArcGISMap
3025
import com.arcgismaps.mapping.GeoElement
3126
import com.arcgismaps.mapping.PortalItem
@@ -34,29 +29,15 @@ import com.arcgismaps.mapping.layers.Layer
3429
import com.arcgismaps.mapping.popup.Popup
3530
import com.arcgismaps.portal.Portal
3631
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
37-
import kotlinx.coroutines.CoroutineScope
38-
import kotlinx.coroutines.Dispatchers
39-
import kotlinx.coroutines.SupervisorJob
40-
import kotlinx.coroutines.cancel
32+
import com.arcgismaps.toolkit.popup.PopupState
4133
import kotlinx.coroutines.launch
42-
import java.io.Closeable
43-
import kotlin.coroutines.CoroutineContext
44-
45-
/**
46-
* Base class for context aware AndroidViewModel. This class must have only a single application
47-
* parameter.
48-
*/
49-
open class BaseMapViewModel(application: Application) : AndroidViewModel(application)
5034

5135
/**
5236
* Simple android view model for the Popup app map screen.
5337
*/
54-
@Suppress("unused_parameter")
5538
class MapViewModel(
56-
savedStateHandle: SavedStateHandle,
5739
application: Application,
58-
coroutineScope: CoroutineScope = CloseableCoroutineScope()
59-
) : BaseMapViewModel(application) {
40+
) : AndroidViewModel(application) {
6041

6142
private var _geoElement: GeoElement? = null
6243
val geoElement: GeoElement?
@@ -74,12 +55,12 @@ class MapViewModel(
7455
private val streamServiceMap = "aef32323d1f248368b1663cfc938995e"
7556

7657
/**
77-
* The Popup read by the composition is held as a state variable.
58+
* The PopupState read by the composition is held as a state variable.
7859
* We want the composition to recompose when the Popup changes.
7960
*/
80-
private var _popup: MutableState<Popup?> = mutableStateOf(null)
81-
val popup: Popup?
82-
get() = _popup.value
61+
private var _popupState: MutableState<PopupState?> = mutableStateOf(null)
62+
val popupState: PopupState?
63+
get() = _popupState.value
8364

8465
val map = ArcGISMap(
8566
PortalItem(
@@ -93,7 +74,7 @@ class MapViewModel(
9374
val proxy: MapViewProxy = MapViewProxy()
9475

9576
init {
96-
coroutineScope.launch {
77+
viewModelScope.launch {
9778
map.load()
9879
}
9980
}
@@ -106,45 +87,8 @@ class MapViewModel(
10687
_layer = layer
10788
}
10889

109-
fun setPopup(popup: Popup?) {
110-
_popup.value = popup
111-
}
112-
companion object {
113-
114-
/**
115-
* The factory needed by the androidx ktx component activity to instantiate the view model.
116-
* See onCreate() for usage.
117-
*/
118-
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
119-
@Suppress("UNCHECKED_CAST")
120-
override fun <T : ViewModel> create(
121-
modelClass: Class<T>,
122-
extras: CreationExtras
123-
): T {
124-
// Get the Application object from extras
125-
val application = checkNotNull(extras[APPLICATION_KEY])
126-
// Create a SavedStateHandle for this ViewModel from extras
127-
val savedStateHandle = extras.createSavedStateHandle()
128-
129-
return MapViewModel(
130-
savedStateHandle,
131-
application
132-
) as T
133-
}
134-
}
90+
fun updatePopupState(popup: Popup?) {
91+
_popupState.value = popup?.let { PopupState(it, viewModelScope) }
13592
}
136-
13793
}
13894

139-
/**
140-
* a CoroutineScope used by the view model. It will by closed when the view model exits its
141-
* lifecycle.
142-
*/
143-
class CloseableCoroutineScope(
144-
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
145-
) : Closeable, CoroutineScope {
146-
override val coroutineContext: CoroutineContext = context
147-
override fun close() {
148-
coroutineContext.cancel()
149-
}
150-
}

toolkit/popup/api/popup.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
public final class com/arcgismaps/toolkit/popup/PopupKt {
22
public static final fun Popup (Lcom/arcgismaps/mapping/popup/Popup;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
3+
public static final fun Popup (Lcom/arcgismaps/toolkit/popup/PopupState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;ZLandroidx/compose/runtime/Composer;II)V
4+
}
5+
6+
public final class com/arcgismaps/toolkit/popup/PopupState {
7+
public static final field $stable I
8+
public fun <init> (Lcom/arcgismaps/mapping/popup/Popup;Lkotlinx/coroutines/CoroutineScope;)V
9+
public final fun getActivePopup ()Lcom/arcgismaps/mapping/popup/Popup;
310
}
411

toolkit/popup/build.gradle.kts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ plugins {
2323
id("artifact-deploy")
2424
id("kotlin-parcelize")
2525
alias(libs.plugins.binary.compatibility.validator) apply true
26+
alias(libs.plugins.kotlin.serialization) apply true
2627
}
2728

2829
kotlin {
@@ -75,6 +76,10 @@ android {
7576
}
7677
}
7778

79+
lint {
80+
disable += "MissingTranslation"
81+
}
82+
7883
}
7984

8085
apiValidation {
@@ -84,7 +89,11 @@ apiValidation {
8489
val composableSingletons = listOf(
8590
"com.arcgismaps.toolkit.popup.internal.ui.ComposableSingletons\$ExpandableCardKt",
8691
"com.arcgismaps.toolkit.popup.internal.ui.fileviewer.ComposableSingletons\$FileViewerKt",
87-
"com.arcgismaps.toolkit.popup.internal.ui.expandablecard.ComposableSingletons\$ExpandableCardKt"
92+
"com.arcgismaps.toolkit.popup.internal.ui.expandablecard.ComposableSingletons\$ExpandableCardKt",
93+
"com.arcgismaps.toolkit.popup.internal.element.utilityassociationselement.ComposableSingletons\$UtilityAssociationDetailsKt",
94+
"com.arcgismaps.toolkit.popup.internal.element.utilityassociationselement.ComposableSingletons\$UtilityAssociationsFilterResultKt",
95+
"com.arcgismaps.toolkit.popup.internal.element.utilityassociationselement.ComposableSingletons\$UtilityAssociationGroupResultKt",
96+
"com.arcgismaps.toolkit.popup.internal.screens.ComposableSingletons\$ContentAwareTopBarKt"
8897
)
8998

9099
ignoredClasses.addAll(composableSingletons)
@@ -98,6 +107,8 @@ dependencies {
98107
implementation(libs.bundles.composeCore)
99108
implementation(libs.bundles.core)
100109
implementation(libs.androidx.activity.compose)
110+
implementation(libs.androidx.compose.navigation)
111+
implementation(libs.kotlinx.serialization.json)
101112
implementation(libs.androidx.material.icons)
102113
implementation(libs.androidx.media3.exoplayer)
103114
implementation(libs.androidx.media3.exoplayer.dash)

0 commit comments

Comments
 (0)