Add a context menu for location items and add remove location functionality

This commit is contained in:
Nebojsa Vuksic 2025-08-12 17:56:38 +02:00
parent 443883d7e4
commit ae6d343913
3 changed files with 136 additions and 8 deletions

View File

@ -0,0 +1,60 @@
package org.jetbrains.plugins.template.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.onClick
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupPositionProvider
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.component.Icon
import org.jetbrains.jewel.ui.component.PopupContainer
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.icon.IconKey
@Composable
fun ContextPopupMenu(
popupPositionProvider: PopupPositionProvider,
onDismissRequest: () -> Unit,
content: @Composable () -> Unit,
) {
PopupContainer(
popupPositionProvider = popupPositionProvider,
modifier = Modifier.wrapContentSize(),
onDismissRequest = { onDismissRequest() },
horizontalAlignment = Alignment.Start
) {
content()
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContextPopupMenuItem(
actionText: String,
actionIcon: IconKey,
onClick: () -> Unit,
) {
Row(
modifier = Modifier
.widthIn(min = 100.dp)
.padding(8.dp)
.onClick { onClick() },
verticalAlignment = Alignment.CenterVertically
) {
Icon(
actionIcon,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = actionText,
style = JewelTheme.defaultTextStyle
)
}
}

View File

@ -1,14 +1,20 @@
package org.jetbrains.plugins.template.weatherApp.ui package org.jetbrains.plugins.template.weatherApp.ui
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.*
import androidx.compose.ui.window.PopupPositionProvider
import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn
import org.jetbrains.jewel.foundation.lazy.SelectionMode import org.jetbrains.jewel.foundation.lazy.SelectionMode
import org.jetbrains.jewel.foundation.lazy.itemsIndexed import org.jetbrains.jewel.foundation.lazy.itemsIndexed
@ -18,8 +24,10 @@ import org.jetbrains.jewel.ui.component.*
import org.jetbrains.jewel.ui.icon.IconKey import org.jetbrains.jewel.ui.icon.IconKey
import org.jetbrains.jewel.ui.icons.AllIconsKeys import org.jetbrains.jewel.ui.icons.AllIconsKeys
import org.jetbrains.plugins.template.ComposeTemplateBundle import org.jetbrains.plugins.template.ComposeTemplateBundle
import org.jetbrains.plugins.template.components.ContextPopupMenu
import org.jetbrains.plugins.template.components.ContextPopupMenuItem
import org.jetbrains.plugins.template.weatherApp.model.Location import org.jetbrains.plugins.template.weatherApp.model.Location
import org.jetbrains.plugins.template.weatherApp.services.* import org.jetbrains.plugins.template.weatherApp.services.SearchAutoCompletionItemProvider
import org.jetbrains.plugins.template.weatherApp.ui.components.SearchToolbarMenu import org.jetbrains.plugins.template.weatherApp.ui.components.SearchToolbarMenu
import org.jetbrains.plugins.template.weatherApp.ui.components.WeatherDetailsCard import org.jetbrains.plugins.template.weatherApp.ui.components.WeatherDetailsCard
@ -119,6 +127,7 @@ private fun EmptyListPlaceholder(
} }
} }
@OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
private fun MyLocationList( private fun MyLocationList(
myLocationsUIState: LocationsUIState, myLocationsUIState: LocationsUIState,
@ -158,9 +167,66 @@ private fun MyLocationList(
itemsIndexed( itemsIndexed(
items = myLocationsUIState.locations, items = myLocationsUIState.locations,
key = { _, item -> item.label }, key = { _, item -> item.label },
) { index, item -> ) { index, locationItem ->
SimpleListItem(text = item.label, isSelected = myLocationsUIState.selectedIndex == index, isActive = isActive) Box(Modifier.wrapContentSize()) {
val showPopup = remember { mutableStateOf(false) }
val popupPosition = remember { mutableStateOf(IntOffset.Zero) }
val itemPosition = remember { mutableStateOf(Offset.Zero) }
SimpleListItem(
text = locationItem.label,
isSelected = myLocationsUIState.selectedIndex == index,
isActive = isActive,
modifier = Modifier
.onGloballyPositioned { coordinates ->
itemPosition.value = coordinates.positionInWindow()
}
.onPointerEvent(PointerEventType.Press) { pointerEvent ->
if (!pointerEvent.buttons.isSecondaryPressed) return@onPointerEvent
// Calculate exact click position
val clickOffset = pointerEvent.changes.first().position
popupPosition.value = IntOffset(
x = (itemPosition.value.x + clickOffset.x).toInt(),
y = (itemPosition.value.y + clickOffset.y).toInt()
)
showPopup.value = true
}
)
if (showPopup.value) {
val popupPositionProvider = remember(popupPosition.value) {
object : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset = popupPosition.value
}
}
ContextPopupMenu(
popupPositionProvider,
onDismissRequest = {
showPopup.value = false
popupPosition.value = IntOffset.Zero
itemPosition.value = Offset.Zero
}
) {
ContextPopupMenuItem(
ComposeTemplateBundle.getMessage("weather.app.context.menu.delete.option"),
AllIconsKeys.General.Delete
) {
showPopup.value = false
myLocationsViewModelApi.onDeleteLocation(locationItem)
}
}
}
}
} }
} }
} }

View File

@ -9,4 +9,6 @@ weather.app.search.toolbar.menu.add.button.text=Add
weather.app.search.toolbar.menu.add.button.content.description=Add a place to a watch list. weather.app.search.toolbar.menu.add.button.content.description=Add a place to a watch list.
weather.app.7days.forecast.title.text=7-day Forecast weather.app.7days.forecast.title.text=7-day Forecast
weather.app.clear.button.content.description=Clear weather.app.context.menu.delete.option=Delete
weather.app.clear.button.content.description=Clear