Skip to content
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
dbf2140
WIP
puneet-pdx Aug 30, 2025
1dac21a
WIP
puneet-pdx Sep 2, 2025
7327a00
Update Popup and UtilityAssociationsElementState
puneet-pdx Sep 3, 2025
9b7246d
WIP
puneet-pdx Sep 4, 2025
f78a605
WIP
puneet-pdx Sep 8, 2025
3564098
clean up code
puneet-pdx Sep 10, 2025
14b3590
clean up imports and unused code
puneet-pdx Sep 10, 2025
09a55c7
WIP
puneet-pdx Sep 10, 2025
bf29a40
remove duplicate title from old popup
puneet-pdx Sep 10, 2025
bc0bed4
move popupelementstates creation to PopupState
puneet-pdx Sep 11, 2025
ff80241
update micro app
puneet-pdx Sep 11, 2025
a74403a
optimize imports
puneet-pdx Sep 11, 2025
acada3e
enable isNavigationEnabled
puneet-pdx Sep 11, 2025
a105da3
add support for ignore list
puneet-pdx Sep 15, 2025
a4d2504
add support for dynamic entity
puneet-pdx Sep 15, 2025
02fccbd
update api file
puneet-pdx Sep 15, 2025
29241d5
update micro app
puneet-pdx Sep 15, 2025
4cbab74
update micro app to not show close icon
puneet-pdx Sep 15, 2025
5f1e78b
add copyright
puneet-pdx Sep 15, 2025
a748476
Merge branch 'v.next' into puneet/6396_UNAssociationsPopup
puneet-pdx Sep 15, 2025
bb55c82
remove parcelize where it is not being used
puneet-pdx Sep 16, 2025
a8325e6
Merge branch 'v.next' into puneet/6396_UNAssociationsPopup
puneet-pdx Sep 16, 2025
7209c50
address code review comments
puneet-pdx Sep 17, 2025
a453234
remove support for isNavigationEnabled and NavigationAction
puneet-pdx Sep 17, 2025
1d5cdb7
remove showConfirmationDialog in UtilityAssocationsDetails
puneet-pdx Sep 17, 2025
b54f7d0
add support for display count
puneet-pdx Sep 18, 2025
857f62c
Optimize imports
puneet-pdx Sep 18, 2025
10ecb16
update logic in mediaPopupElement composable
puneet-pdx Sep 18, 2025
35a051a
enable close button
puneet-pdx Sep 19, 2025
d5e75ae
add search box to filter UtilityAssociationGroupResults with a user s…
puneet-pdx Sep 22, 2025
74c3047
update showall behavior on search
puneet-pdx Sep 22, 2025
76ec033
Add fix for navigation not working
puneet-pdx Sep 22, 2025
c062f86
Add default value for UtilityElement title
puneet-pdx Sep 22, 2025
b1dc7e7
reset geoElement when the bottomsheet is dismissed
puneet-pdx Sep 23, 2025
c478337
Pick up refactor changes from feature forms
puneet-pdx Sep 23, 2025
c582253
update micro app
puneet-pdx Sep 23, 2025
b328272
address code review comments
puneet-pdx Sep 24, 2025
e8172f6
FIx navigation issue
puneet-pdx Sep 24, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,34 @@ package com.arcgismaps.toolkit.popupapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.runtime.Composable
import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallenge
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallengeHandler
import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallengeResponse
import com.arcgismaps.httpcore.authentication.TokenCredential
import com.arcgismaps.toolkit.popupapp.screens.mapscreen.MainScreen
import com.arcgismaps.toolkit.popupapp.screens.mapscreen.MapViewModel
import com.arcgismaps.toolkit.popupapp.ui.theme.PopupAppTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: MapViewModel by viewModels { MapViewModel.Factory }
ArcGISEnvironment.authenticationManager.arcGISAuthenticationChallengeHandler =
TestArcGISAuthenticationChallengeHandler(
BuildConfig.webMapUser,
BuildConfig.webMapPassword
)
setContent {
PopupAppTheme {
PopupApp(viewModel)
PopupApp()
}
}
}
}

@Composable
fun PopupApp(viewModel: MapViewModel) {
MainScreen(viewModel)
fun PopupApp() {
MainScreen()
}

class TestArcGISAuthenticationChallengeHandler(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@ package com.arcgismaps.toolkit.popupapp.screens.mapscreen

import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.arcgismaps.data.Feature
import com.arcgismaps.mapping.GeoElement
import com.arcgismaps.mapping.layers.FeatureLayer
Expand All @@ -53,7 +58,7 @@ private fun unselectFeature(feature: GeoElement?, layer: Layer?) {
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(viewModel: MapViewModel) {
fun MainScreen(viewModel: MapViewModel = viewModel()) {
val scope = rememberCoroutineScope()
val context = LocalContext.current

Expand All @@ -63,13 +68,29 @@ fun MainScreen(viewModel: MapViewModel) {
skipHiddenState = false
)
)
LaunchedEffect(scaffoldState.bottomSheetState.currentValue) {
if (scaffoldState.bottomSheetState.currentValue == SheetValue.Hidden) {
resetSelection(viewModel)
}
}

BottomSheetScaffold(
sheetContent = {
if (viewModel.popup != null) {
Popup(viewModel.popup!!, Modifier.animateContentSize())
val state = viewModel.popupState
if (state != null) {
Popup(
state,
Modifier.animateContentSize(),
onDismiss = { scope.launch {
resetSelection(viewModel)
scaffoldState.bottomSheetState.hide()
} }
)
}
},
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.padding(WindowInsets.safeDrawing.asPaddingValues()),
scaffoldState = scaffoldState,
sheetSwipeEnabled = true,
topBar = null
Expand All @@ -82,17 +103,15 @@ fun MainScreen(viewModel: MapViewModel) {
.padding(padding)
.fillMaxSize(),
onSingleTapConfirmed = {
resetSelection(viewModel)
scope.launch {
viewModel.proxy.identifyLayers(
screenCoordinate = it.screenCoordinate,
tolerance = 22.dp,
returnPopupsOnly = true
).onSuccess { results ->
if (results.isEmpty()) {
unselectFeature(viewModel.geoElement, viewModel.layer)
viewModel.setPopup(null)
viewModel.setLayer(null)
viewModel.setGeoElement(null)
if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded) {
scaffoldState.bottomSheetState.hide()
}
Expand Down Expand Up @@ -133,11 +152,6 @@ fun MainScreen(viewModel: MapViewModel) {
).show()
}
} else {
unselectFeature(
viewModel.geoElement,
viewModel.layer
)

when (newLayer) {
is FeatureLayer -> {
// the Popup exists on a FeatureLayer
Expand All @@ -164,7 +178,7 @@ fun MainScreen(viewModel: MapViewModel) {
throw IllegalStateException("popups on sublayers are not supported by the PopupApp")
}
}
viewModel.setPopup(popup)
viewModel.updatePopupState(popup)
scaffoldState.bottomSheetState.expand()
}
} catch (e: Exception) {
Expand All @@ -185,6 +199,12 @@ fun MainScreen(viewModel: MapViewModel) {
}
}

private fun resetSelection(viewModel: MapViewModel) {
viewModel.updatePopupState(null)
unselectFeature(viewModel.geoElement, viewModel.layer)
viewModel.setGeoElement(null)
}

private fun GeoElement?.sameSelection(other: GeoElement): Boolean =
if (this == null) {
false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ import android.app.Application
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewModelScope
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.GeoElement
import com.arcgismaps.mapping.PortalItem
Expand All @@ -34,29 +29,15 @@ import com.arcgismaps.mapping.layers.Layer
import com.arcgismaps.mapping.popup.Popup
import com.arcgismaps.portal.Portal
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import com.arcgismaps.toolkit.popup.PopupState
import kotlinx.coroutines.launch
import java.io.Closeable
import kotlin.coroutines.CoroutineContext

/**
* Base class for context aware AndroidViewModel. This class must have only a single application
* parameter.
*/
open class BaseMapViewModel(application: Application) : AndroidViewModel(application)

/**
* Simple android view model for the Popup app map screen.
*/
@Suppress("unused_parameter")
class MapViewModel(
savedStateHandle: SavedStateHandle,
application: Application,
coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : BaseMapViewModel(application) {
) : AndroidViewModel(application) {

private var _geoElement: GeoElement? = null
val geoElement: GeoElement?
Expand All @@ -74,12 +55,12 @@ class MapViewModel(
private val streamServiceMap = "aef32323d1f248368b1663cfc938995e"

/**
* The Popup read by the composition is held as a state variable.
* The PopupState read by the composition is held as a state variable.
* We want the composition to recompose when the Popup changes.
*/
private var _popup: MutableState<Popup?> = mutableStateOf(null)
val popup: Popup?
get() = _popup.value
private var _popupState: MutableState<PopupState?> = mutableStateOf(null)
val popupState: PopupState?
get() = _popupState.value

val map = ArcGISMap(
PortalItem(
Expand All @@ -93,7 +74,7 @@ class MapViewModel(
val proxy: MapViewProxy = MapViewProxy()

init {
coroutineScope.launch {
viewModelScope.launch {
map.load()
}
}
Expand All @@ -106,45 +87,8 @@ class MapViewModel(
_layer = layer
}

fun setPopup(popup: Popup?) {
_popup.value = popup
}
companion object {

/**
* The factory needed by the androidx ktx component activity to instantiate the view model.
* See onCreate() for usage.
*/
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()

return MapViewModel(
savedStateHandle,
application
) as T
}
}
fun updatePopupState(popup: Popup?) {
_popupState.value = popup?.let { PopupState(it, viewModelScope) }
}

}

/**
* a CoroutineScope used by the view model. It will by closed when the view model exits its
* lifecycle.
*/
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
7 changes: 7 additions & 0 deletions toolkit/popup/api/popup.api
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
public final class com/arcgismaps/toolkit/popup/PopupKt {
public static final fun Popup (Lcom/arcgismaps/mapping/popup/Popup;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
public static final fun Popup (Lcom/arcgismaps/toolkit/popup/PopupState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;ZLandroidx/compose/runtime/Composer;II)V
}

public final class com/arcgismaps/toolkit/popup/PopupState {
public static final field $stable I
public fun <init> (Lcom/arcgismaps/mapping/popup/Popup;Lkotlinx/coroutines/CoroutineScope;)V
public final fun getActivePopup ()Lcom/arcgismaps/mapping/popup/Popup;
}

13 changes: 12 additions & 1 deletion toolkit/popup/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ plugins {
id("artifact-deploy")
id("kotlin-parcelize")
alias(libs.plugins.binary.compatibility.validator) apply true
alias(libs.plugins.kotlin.serialization) apply true
}

kotlin {
Expand Down Expand Up @@ -75,6 +76,10 @@ android {
}
}

lint {
disable += "MissingTranslation"
}

}

apiValidation {
Expand All @@ -84,7 +89,11 @@ apiValidation {
val composableSingletons = listOf(
"com.arcgismaps.toolkit.popup.internal.ui.ComposableSingletons\$ExpandableCardKt",
"com.arcgismaps.toolkit.popup.internal.ui.fileviewer.ComposableSingletons\$FileViewerKt",
"com.arcgismaps.toolkit.popup.internal.ui.expandablecard.ComposableSingletons\$ExpandableCardKt"
"com.arcgismaps.toolkit.popup.internal.ui.expandablecard.ComposableSingletons\$ExpandableCardKt",
"com.arcgismaps.toolkit.popup.internal.element.utilityassociationselement.ComposableSingletons\$UtilityAssociationDetailsKt",
"com.arcgismaps.toolkit.popup.internal.element.utilityassociationselement.ComposableSingletons\$UtilityAssociationsFilterResultKt",
"com.arcgismaps.toolkit.popup.internal.element.utilityassociationselement.ComposableSingletons\$UtilityAssociationGroupResultKt",
"com.arcgismaps.toolkit.popup.internal.screens.ComposableSingletons\$ContentAwareTopBarKt"
)

ignoredClasses.addAll(composableSingletons)
Expand All @@ -98,6 +107,8 @@ dependencies {
implementation(libs.bundles.composeCore)
implementation(libs.bundles.core)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.navigation)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.material.icons)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.exoplayer.dash)
Expand Down
Loading