mirror of
https://github.com/JetBrains/intellij-platform-plugin-template.git
synced 2025-12-05 06:11:52 +00:00
Add an empty list placeholder for MyLocationsList
This commit is contained in:
parent
88c1d4b4bd
commit
e791d63d22
@ -7,7 +7,9 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Color.Companion.Transparent
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.jewel.bridge.retrieveColorOrUnspecified
|
||||
@ -17,8 +19,11 @@ import org.jetbrains.jewel.foundation.lazy.items
|
||||
import org.jetbrains.jewel.foundation.lazy.rememberSelectableLazyListState
|
||||
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||
import org.jetbrains.jewel.ui.component.*
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
import org.jetbrains.jewel.ui.icons.AllIconsKeys
|
||||
import org.jetbrains.plugins.template.ComposeTemplateBundle
|
||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
||||
import org.jetbrains.plugins.template.weatherApp.model.SelectableLocation
|
||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
||||
import org.jetbrains.plugins.template.weatherApp.services.LocationsProvider
|
||||
import org.jetbrains.plugins.template.weatherApp.services.MyLocationsViewModelApi
|
||||
@ -76,14 +81,58 @@ private fun LeftColumn(
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
MyLocationsList(Modifier.fillMaxSize(), myLocationsViewModelApi)
|
||||
MyLocationsListWithEmptyListPlaceholder(Modifier.fillMaxSize(), myLocationsViewModelApi)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun MyLocationsList(modifier: Modifier = Modifier, myLocationsViewModelApi: MyLocationsViewModelApi) {
|
||||
internal fun MyLocationsListWithEmptyListPlaceholder(
|
||||
modifier: Modifier = Modifier,
|
||||
myLocationsViewModelApi: MyLocationsViewModelApi
|
||||
) {
|
||||
val myLocations = myLocationsViewModelApi.myLocationsFlow.collectAsState(emptyList()).value
|
||||
|
||||
if (myLocations.isNotEmpty()) {
|
||||
MyLocationList(myLocations, modifier, myLocationsViewModelApi)
|
||||
} else {
|
||||
EmptyListPlaceholder(modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyListPlaceholder(
|
||||
modifier: Modifier,
|
||||
placeholderText: String = ComposeTemplateBundle.message("weather.app.my.locations.empty.list.placeholder.text"),
|
||||
placeholderIcon: IconKey = AllIconsKeys.Actions.AddList
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize().padding(16.dp),
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
placeholderIcon,
|
||||
contentDescription = ComposeTemplateBundle.message("weather.app.my.locations.empty.list.placeholder.icon.content.description"),
|
||||
Modifier.size(32.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = placeholderText,
|
||||
style = JewelTheme.defaultTextStyle,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MyLocationList(
|
||||
myLocations: List<SelectableLocation>,
|
||||
modifier: Modifier,
|
||||
myLocationsViewModelApi: MyLocationsViewModelApi
|
||||
) {
|
||||
val listState = rememberSelectableLazyListState()
|
||||
// JEWEL-938 This will trigger on SelectableLazyColum's `onSelectedIndexesChange` callback
|
||||
LaunchedEffect(myLocations) {
|
||||
|
||||
@ -2,6 +2,8 @@ weather.app.temperature.text={0}\u00B0C
|
||||
weather.app.humidity.text=Humidity: {0}%
|
||||
weather.app.wind.direction.text=Wind: {0} km/h {1}
|
||||
weather.app.my.locations.header.text=My Locations
|
||||
weather.app.my.locations.empty.list.placeholder.text=No locations added yet. Go and add the first location.
|
||||
weather.app.my.locations.empty.list.placeholder.icon.content.description=Empty list icon.
|
||||
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.7days.forecast.title.text=7-day Forecast
|
||||
@ -0,0 +1,101 @@
|
||||
package org.jetbrains.plugins.template
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme
|
||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
||||
import org.jetbrains.plugins.template.weatherApp.model.SelectableLocation
|
||||
import org.jetbrains.plugins.template.weatherApp.services.MyLocationsViewModelApi
|
||||
import org.jetbrains.plugins.template.weatherApp.ui.MyLocationsListWithEmptyListPlaceholder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
internal class MyLocationListTest {
|
||||
@get:Rule
|
||||
val composeRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun `show placeholder when no locations is added`() = runTest {
|
||||
val myLocationsRobot = MyLocationListRobot(composeRule)
|
||||
|
||||
composeRule.setContentWrappedInTheme {
|
||||
val noLocations = emptyList<Location>()
|
||||
val myLocationsViewModel = FakeMyLocationsViewModel(locations = noLocations)
|
||||
MyLocationsListWithEmptyListPlaceholder(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
myLocationsViewModelApi = myLocationsViewModel
|
||||
)
|
||||
}
|
||||
|
||||
myLocationsRobot
|
||||
.verifyNoLocationsPlaceHolderVisible()
|
||||
}
|
||||
|
||||
private class FakeMyLocationsViewModel(
|
||||
locations: List<Location> = emptyList()
|
||||
) : MyLocationsViewModelApi {
|
||||
|
||||
private val locationsFlow = MutableStateFlow(locations.toMutableList())
|
||||
|
||||
private val selectedItemIndex = MutableStateFlow(if (locations.isNotEmpty()) 0 else -1)
|
||||
|
||||
private val _myLocations = locationsFlow
|
||||
.combine(selectedItemIndex) { locations, selectedIndex ->
|
||||
locations.mapIndexed { index, location ->
|
||||
SelectableLocation(location, index == selectedIndex)
|
||||
}
|
||||
}
|
||||
override val myLocationsFlow: Flow<List<SelectableLocation>> = _myLocations
|
||||
|
||||
override fun onAddLocation(locationToAdd: Location) {
|
||||
val currentLocations = locationsFlow.value
|
||||
currentLocations.add(locationToAdd)
|
||||
locationsFlow.value = currentLocations
|
||||
}
|
||||
|
||||
override fun onDeleteLocation(locationToDelete: Location) {
|
||||
val currentLocations = locationsFlow.value
|
||||
currentLocations.remove(locationToDelete)
|
||||
locationsFlow.value = currentLocations
|
||||
}
|
||||
|
||||
override fun onLocationSelected(selectedLocationIndex: Int) {
|
||||
selectedItemIndex.value = selectedLocationIndex
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.setContentWrappedInTheme(content: @Composable () -> Unit) {
|
||||
setContent {
|
||||
IntUiTheme {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MyLocationListRobot(private val composableRule: ComposeContentTestRule) {
|
||||
fun clickOnItemWithText(text: String) {
|
||||
composableRule
|
||||
.onNodeWithText(text)
|
||||
.performClick()
|
||||
}
|
||||
|
||||
fun verifyNoLocationsPlaceHolderVisible() {
|
||||
composableRule
|
||||
.onNodeWithText("No locations added yet. Go and add the first location.")
|
||||
.assertExists()
|
||||
composableRule
|
||||
.onNodeWithContentDescription("Empty list icon.")
|
||||
.assertExists()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user