Skip to content

Commit a4b8bb8

Browse files
Merge pull request #23 from PeriodPals/feat/alert/list-screen
Feat/alert/list screen: Finish UI for the list of alerts
2 parents 17719dd + af9aae3 commit a4b8bb8

File tree

2 files changed

+333
-0
lines changed

2 files changed

+333
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.android.periodpals.ui
2+
3+
import androidx.compose.ui.test.assertIsDisplayed
4+
import androidx.compose.ui.test.assertIsNotSelected
5+
import androidx.compose.ui.test.assertIsSelected
6+
import androidx.compose.ui.test.assertTextEquals
7+
import androidx.compose.ui.test.junit4.createComposeRule
8+
import androidx.compose.ui.test.onNodeWithTag
9+
import androidx.compose.ui.test.performClick
10+
import androidx.test.ext.junit.runners.AndroidJUnit4
11+
import com.android.periodpals.ui.alert.AlertListScreen
12+
import com.android.periodpals.ui.alert.NoAlertDialog
13+
import org.junit.Rule
14+
import org.junit.Test
15+
import org.junit.runner.RunWith
16+
17+
@RunWith(AndroidJUnit4::class)
18+
class AlertListScreenTest {
19+
20+
@get:Rule val composeTestRule = createComposeRule()
21+
22+
@Test
23+
fun titleAndNavigationElementsCorrectlyDisplayed() {
24+
composeTestRule.setContent { AlertListScreen() }
25+
26+
composeTestRule.onNodeWithTag("alertListScreen").assertExists()
27+
composeTestRule.onNodeWithTag("alertListTitle").assertIsDisplayed()
28+
composeTestRule.onNodeWithTag("tabRowAlert").assertIsDisplayed()
29+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsDisplayed()
30+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsDisplayed()
31+
}
32+
33+
@Test
34+
fun palsAlertsContentIsCorrect() {
35+
composeTestRule.setContent { AlertListScreen() }
36+
37+
composeTestRule.onNodeWithTag("palsAlertsTab").performClick()
38+
39+
composeTestRule.onNodeWithTag("noAlertsCard").assertIsDisplayed()
40+
composeTestRule.onNodeWithTag("noAlertsCardText").assertIsDisplayed()
41+
}
42+
43+
@Test
44+
fun myAlertsContentIsCorrect() {
45+
composeTestRule.setContent { AlertListScreen() }
46+
47+
composeTestRule.onNodeWithTag("alertItem").assertIsDisplayed()
48+
}
49+
50+
@Test
51+
fun clickingOnAlertItemDoesNothing() {
52+
composeTestRule.setContent { AlertListScreen() }
53+
54+
// Check that the default tab is My Alerts tab
55+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsSelected()
56+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsNotSelected()
57+
58+
// Check that the alert item is displayed
59+
composeTestRule.onNodeWithTag("alertItem").assertIsDisplayed()
60+
61+
// Click on the card and verify that nothing changes
62+
composeTestRule.onNodeWithTag("alertItem").performClick()
63+
64+
// Check that the default tab is My Alerts tab
65+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsSelected()
66+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsNotSelected()
67+
68+
// Check that the alert item is displayed
69+
composeTestRule.onNodeWithTag("alertItem").assertIsDisplayed()
70+
}
71+
72+
@Test
73+
fun clickingOnNoAlertDialogDoesNothing() {
74+
composeTestRule.setContent { AlertListScreen() }
75+
76+
// Check that the default tab is My Alerts tab
77+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsSelected()
78+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsNotSelected()
79+
80+
// Switch to Pals Alerts screen
81+
composeTestRule.onNodeWithTag("palsAlertsTab").performClick()
82+
83+
// Check that the Pals Alerts tab is selected and my Alerts is not selected
84+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsNotSelected()
85+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsSelected()
86+
87+
// Check that the dialog is displayed
88+
composeTestRule.onNodeWithTag("noAlertsCard").assertIsDisplayed()
89+
composeTestRule.onNodeWithTag("noAlertsCardText").assertIsDisplayed()
90+
91+
// Click on the card
92+
composeTestRule.onNodeWithTag("noAlertsCard").assertIsDisplayed()
93+
94+
// Check that nothing changed
95+
// Check that the Pals Alerts tab is selected and my Alerts is not selected
96+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsNotSelected()
97+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsSelected()
98+
99+
// Check that the dialog is displayed
100+
composeTestRule.onNodeWithTag("noAlertsCard").assertIsDisplayed()
101+
composeTestRule.onNodeWithTag("noAlertsCardText").assertIsDisplayed()
102+
}
103+
104+
@Test
105+
fun switchingTabWorks() {
106+
composeTestRule.setContent { AlertListScreen() }
107+
108+
// Explicitly select My Alerts tab
109+
composeTestRule.onNodeWithTag("myAlertsTab").performClick()
110+
111+
// Check that the default tab is My Alerts tab
112+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsSelected()
113+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsNotSelected()
114+
115+
// Check that My Alert content is correctly displayed
116+
composeTestRule.onNodeWithTag("alertItem").assertIsDisplayed()
117+
118+
// Switch to Pals Alerts tab
119+
composeTestRule.onNodeWithTag("palsAlertsTab").performClick()
120+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsSelected()
121+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsNotSelected()
122+
123+
// Verify Pals Alerts content is displayed
124+
composeTestRule.onNodeWithTag("noAlertsCard").assertIsDisplayed()
125+
composeTestRule.onNodeWithTag("noAlertsCardText").assertIsDisplayed()
126+
127+
// Switch back to My Alerts tab
128+
composeTestRule.onNodeWithTag("myAlertsTab").performClick()
129+
composeTestRule.onNodeWithTag("myAlertsTab").assertIsSelected()
130+
composeTestRule.onNodeWithTag("palsAlertsTab").assertIsNotSelected()
131+
132+
// Verify My Alerts content is displayed
133+
composeTestRule.onNodeWithTag("alertItem").assertIsDisplayed()
134+
}
135+
136+
@Test
137+
fun testNoAlertDialogContent() {
138+
composeTestRule.setContent { NoAlertDialog() }
139+
140+
composeTestRule.onNodeWithTag("noAlertsCard").assertIsDisplayed()
141+
composeTestRule.onNodeWithTag("noAlertsIcon").assertIsDisplayed()
142+
composeTestRule.onNodeWithTag("noAlertsCardText").assertExists()
143+
composeTestRule
144+
.onNodeWithTag("noAlertsCardText")
145+
.assertTextEquals("No alerts here for the moment...")
146+
}
147+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.android.periodpals.ui.alert
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.height
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.material.icons.Icons
13+
import androidx.compose.material.icons.filled.AccountBox
14+
import androidx.compose.material.icons.outlined.Call
15+
import androidx.compose.material.icons.outlined.Warning
16+
import androidx.compose.material3.Card
17+
import androidx.compose.material3.CardDefaults
18+
import androidx.compose.material3.Icon
19+
import androidx.compose.material3.MaterialTheme
20+
import androidx.compose.material3.Scaffold
21+
import androidx.compose.material3.Tab
22+
import androidx.compose.material3.TabRow
23+
import androidx.compose.material3.Text
24+
import androidx.compose.runtime.Composable
25+
import androidx.compose.runtime.getValue
26+
import androidx.compose.runtime.mutableIntStateOf
27+
import androidx.compose.runtime.remember
28+
import androidx.compose.runtime.setValue
29+
import androidx.compose.ui.Alignment
30+
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.platform.testTag
32+
import androidx.compose.ui.unit.dp
33+
34+
/**
35+
* Displays the list of request under two distinct tabs: MyAlerts and PalsAlerts. MyAlerts
36+
* corresponds to the alerts that the user has published. PalsAlerts correspond to the alerts that
37+
* other users have published.
38+
*/
39+
@Composable
40+
fun AlertListScreen(modifier: Modifier = Modifier) {
41+
42+
// Controls which tab is selected (0 -> MyAlerts; 1 -> PalsAlerts)
43+
var selectedTabIndex by remember { mutableIntStateOf(0) }
44+
45+
Scaffold(
46+
modifier = Modifier.testTag("alertListScreen"),
47+
topBar = {
48+
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
49+
Text(
50+
text = "Alerts List",
51+
style = MaterialTheme.typography.headlineMedium,
52+
modifier = Modifier.testTag("alertListTitle"))
53+
54+
Spacer(modifier = Modifier.height(16.dp))
55+
56+
TabRow(selectedTabIndex = selectedTabIndex, modifier = Modifier.testTag("tabRowAlert")) {
57+
Tab(
58+
selected = selectedTabIndex == 0,
59+
onClick = { selectedTabIndex = 0 },
60+
text = { Text("My Alerts") },
61+
modifier = Modifier.testTag("myAlertsTab"))
62+
Tab(
63+
selected = selectedTabIndex == 1,
64+
onClick = { selectedTabIndex = 1 },
65+
text = { Text("Pals Alerts") },
66+
modifier = Modifier.testTag("palsAlertsTab"))
67+
}
68+
}
69+
}) { paddingValues ->
70+
Box(modifier = Modifier.padding(paddingValues)) {
71+
72+
// Change the displayed list in function of the tab that the user selects
73+
when (selectedTabIndex) {
74+
0 -> MyAlerts()
75+
1 -> PalsAlerts()
76+
else -> MyAlerts()
77+
}
78+
}
79+
}
80+
}
81+
82+
/** Displays the list of alerts published by the user. */
83+
@Composable
84+
fun MyAlerts() {
85+
Column(
86+
modifier = Modifier.fillMaxSize(),
87+
horizontalAlignment = Alignment.CenterHorizontally,
88+
verticalArrangement = Arrangement.spacedBy(20.dp)) {
89+
// TODO: Display the items in a LazyColum or the NoAlertDialog if there aren't any
90+
AlertItem()
91+
}
92+
}
93+
94+
/** Displays the list of alerts published by other pals. */
95+
@Composable
96+
fun PalsAlerts() {
97+
// TODO: Display the items in a LazyColum or the NoAlertDialog if there aren't any
98+
NoAlertDialog()
99+
}
100+
101+
/**
102+
* An alert item. It displays in a card the following information: Profile picture, name of
103+
* publisher, time of publish, location, menstrual product type and urgency level.
104+
*/
105+
@Composable
106+
fun AlertItem() {
107+
Card(
108+
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp).testTag("alertItem"),
109+
elevation = CardDefaults.cardElevation(defaultElevation = 3.dp),
110+
onClick = { /* do something */}) {
111+
Row(
112+
modifier = Modifier.padding(7.dp).fillMaxWidth().testTag("alertItemRow"),
113+
verticalAlignment = Alignment.CenterVertically,
114+
) {
115+
// For the moment, the card is using placeholder values.
116+
// TODO: Implement the model and viewmodel and link them with this screen.
117+
118+
// Profile Image
119+
// Image(
120+
// painter = painterResource(id = R.drawable.profile_pic),
121+
// imageVector = Icons.Default.AccountBox,
122+
// contentDescription = "Profile Picture",
123+
// contentScale = ContentScale.Crop,
124+
// modifier = Modifier
125+
// .size(40.dp)
126+
// .clip(CircleShape)
127+
// .testTag("alertListItemImage")
128+
// )
129+
130+
// Placeholder item
131+
Icon(
132+
imageVector = Icons.Default.AccountBox,
133+
contentDescription = "Profile Picture",
134+
modifier = Modifier.testTag("alertProfilePicture"))
135+
136+
// Info about the user. For the moment all of this are placeholder values
137+
Column(modifier = Modifier.weight(1f).padding(horizontal = 8.dp)) {
138+
Text(text = "Bruno Lazarini", modifier = Modifier.testTag("alertItemName"))
139+
Text(text = "7:00", modifier = Modifier.testTag("alertItemTime"))
140+
Text(text = "EPFL", modifier = Modifier.testTag("alertItemLocation"))
141+
}
142+
143+
// Spacer to push the remaining items to the right
144+
Spacer(modifier = Modifier.weight(1f))
145+
146+
// Menstrual Product Type
147+
Icon(
148+
imageVector = Icons.Outlined.Call, // TODO: Design Icon
149+
contentDescription = "Menstrual Product Type",
150+
modifier = Modifier.padding(horizontal = 4.dp).testTag("alertItemProductType"))
151+
152+
// Urgency
153+
Icon(
154+
imageVector = Icons.Outlined.Warning, // TODO: Design Icon
155+
contentDescription = "Urgency of the Alert",
156+
modifier = Modifier.padding(horizontal = 4.dp).testTag("alertItemUrgency"))
157+
}
158+
}
159+
}
160+
161+
/** Displays a message in a card indicating that there are no alerts published. */
162+
@Composable
163+
fun NoAlertDialog() {
164+
Column(
165+
modifier = Modifier.fillMaxSize(),
166+
horizontalAlignment = Alignment.CenterHorizontally,
167+
verticalArrangement = Arrangement.Center) {
168+
Card(
169+
elevation = CardDefaults.cardElevation(defaultElevation = 3.dp),
170+
modifier = Modifier.testTag("noAlertsCard")) {
171+
Column(
172+
horizontalAlignment = Alignment.CenterHorizontally,
173+
verticalArrangement = Arrangement.spacedBy(10.dp),
174+
modifier = Modifier.padding(7.dp)) {
175+
Icon(
176+
imageVector = Icons.Outlined.Warning,
177+
contentDescription = "No alerts posted",
178+
modifier = Modifier.testTag("noAlertsIcon"))
179+
180+
Text(
181+
text = "No alerts here for the moment...",
182+
modifier = Modifier.testTag("noAlertsCardText"))
183+
}
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)