Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/profile/edit profile screen UI and tests #108

Merged
merged 18 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
22e27e2
feat: center GlideImage and update EditProfileScreen layout
Harrish92 Oct 30, 2024
d1afd7b
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
Harrish92 Oct 30, 2024
aa7ae4f
fix: modularize EditProfileScreen without changing UI
Harrish92 Oct 30, 2024
d61be6e
feat: add UI tests for `EditProfileScreen` and validate fields
Harrish92 Oct 31, 2024
250ff12
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
Harrish92 Oct 31, 2024
72ce683
feat: implement image change on icon click
Harrish92 Oct 31, 2024
7ba8628
Merge branch 'main' into feat/profile/editProfileScreen-ui-and-tests
Harrish92 Oct 31, 2024
69490a9
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
Harrish92 Nov 3, 2024
27078ca
fix: revamp EditProfile tests, improve layout, and add new UI component
Harrish92 Nov 4, 2024
7837bdf
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
Harrish92 Nov 4, 2024
325ad76
fix: set the correct content for main
Harrish92 Nov 4, 2024
554440b
fix: add tags for sectors, refactor tests, change profile icon, and u…
Harrish92 Nov 5, 2024
58f02bc
fix: correct `EditProfile` to pass tests
Harrish92 Nov 5, 2024
8a91386
Merge branch 'main' of github.com:PeriodPals/periodpals into feat/pro…
Harrish92 Nov 5, 2024
8aacca8
fix: reformat code for better readability
Harrish92 Nov 5, 2024
c9702af
fix: change function names for `editProfileTest`
Harrish92 Nov 6, 2024
a06a3c4
fix: update UI elements in `EditProfileScreen`
Harrish92 Nov 7, 2024
c17d229
fix: remove color assigned to button
Harrish92 Nov 7, 2024
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
francelu marked this conversation as resolved.
Show resolved Hide resolved
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.navigation.compose.rememberNavController
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.periodpals.ui.navigation.NavigationActions
import com.android.periodpals.ui.profile.EditProfileScreen
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class EditProfileTest {

@get:Rule val composeTestRule = createComposeRule()

@Before
// Set up the EditProfileScreen
fun setUp() {
composeTestRule.setContent {
MaterialTheme { EditProfileScreen(NavigationActions(rememberNavController())) }
}
}

@Test
fun testEmailRowDisplayed() {

// Check if the email row is displayed
composeTestRule.onNodeWithTag("email_row").assertIsDisplayed()
}

@Test
fun testProfileImageDisplayed() {

// Check if the profile image is displayed
composeTestRule.onNodeWithTag("profile_image").assertIsDisplayed()

// Check if the add circle icon is displayed
composeTestRule.onNodeWithTag("add_circle_icon").assertIsDisplayed()
}

@Test
fun testPerformTextInput() {

// Perform text input
composeTestRule.onNodeWithTag("name_field").performTextInput("New Name")
composeTestRule.onNodeWithTag("dob_field").performTextInput("02/02/2022")
composeTestRule.onNodeWithTag("description_field").performTextInput("New Description")

// Verify text input
composeTestRule.onNodeWithTag("name_field").assertTextEquals("New Name")
composeTestRule.onNodeWithTag("dob_field").assertTextEquals("02/02/2022")
composeTestRule.onNodeWithTag("description_field").assertTextEquals("New Description")
}

@Test
fun testPerformSaveButtonClick() {
// Perform save button click
composeTestRule.onNodeWithTag("save_button").performClick()
}
}
218 changes: 209 additions & 9 deletions app/src/main/java/com/android/periodpals/ui/profile/EditProfile.kt
francelu marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,33 +1,233 @@
package com.android.periodpals.ui.profile

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircleOutline
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.android.periodpals.ui.navigation.BottomNavigationMenu
import com.android.periodpals.ui.navigation.LIST_TOP_LEVEL_DESTINATION
import com.android.periodpals.ui.navigation.NavigationActions
import com.android.periodpals.ui.navigation.TopAppBar
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage

/* Placeholder Screen, waiting for implementation */
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun EditProfileScreen(navigationActions: NavigationActions) {
// State variables to hold the input values
var name by remember { mutableStateOf("") }
var dob by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }

var profileImageUri by remember { mutableStateOf<Uri?>(Uri.parse("")) }

val launcher =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
profileImageUri = result.data?.data
}
}

val context = LocalContext.current
francelu marked this conversation as resolved.
Show resolved Hide resolved

Scaffold(
francelu marked this conversation as resolved.
Show resolved Hide resolved
bottomBar = ({
BottomNavigationMenu(
onTabSelect = { route -> navigationActions.navigateTo(route) },
tabList = LIST_TOP_LEVEL_DESTINATION,
selectedItem = navigationActions.currentRoute())
}),
bottomBar = {
BottomNavigationMenu(
onTabSelect = { route -> navigationActions.navigateTo(route) },
tabList = LIST_TOP_LEVEL_DESTINATION,
selectedItem = navigationActions.currentRoute())
},
francelu marked this conversation as resolved.
Show resolved Hide resolved
topBar = {
TopAppBar(
title = "Edit Profile",
title = "Edit your Profile",
true,
onBackButtonClick = { navigationActions.navigateTo("profile") })
francelu marked this conversation as resolved.
Show resolved Hide resolved
},
content = { pd ->
Text("Edit Profile Screen", modifier = Modifier.fillMaxSize().padding(pd))
Column(
modifier = Modifier.padding(pd).padding(24.dp).fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// Profile image section
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
Box {
GlideImage(
model = profileImageUri,
modifier =
Modifier.padding(1.dp)
.clip(shape = RoundedCornerShape(100.dp))
.size(124.dp)
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
.testTag("profile_image")
.background(
color = Color(0xFFD9D9D9), shape = RoundedCornerShape(100.dp)),
contentDescription = "image profile",
contentScale = ContentScale.Crop,
)

Icon(
Icons.Filled.AddCircleOutline,
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
contentDescription = "add circle",
modifier =
Modifier.align(Alignment.BottomEnd)
francelu marked this conversation as resolved.
Show resolved Hide resolved
.size(40.dp)
.background(color = Color(0xFF79747E), shape = CircleShape)
.testTag("add_circle_icon")
.clickable {
val pickImageIntent =
Intent(Intent.ACTION_PICK).apply { type = "image/*" }
launcher.launch(pickImageIntent)
},
)
}
}

// Section title
ProfileText("Mandatory Fields")
francelu marked this conversation as resolved.
Show resolved Hide resolved

// Divider
HorizontalDivider(thickness = 2.dp)
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved

// Email row
Row(horizontalArrangement = Arrangement.Start, modifier = Modifier.testTag("email_row")) {
francelu marked this conversation as resolved.
Show resolved Hide resolved
ProfileText("Email: ")
ProfileText(
"emilia.jones@email.com",
TextStyle(fontWeight = FontWeight(400), textDecoration = TextDecoration.Underline))
}

// Name input field
ProfileField(
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
title = "Name: ",
value = name,
onValueChange = { name = it },
modifier = Modifier.testTag("name_field"))

// Date of Birth input field
ProfileField(
title = "Date of Birth: ",
value = dob,
onValueChange = { dob = it },
modifier = Modifier.testTag("dob_field"))

// Section title
ProfileText("Your Profile: ")

// Divider
HorizontalDivider(thickness = 2.dp)

// Description input field
ProfileField(
title = "Description: ",
value = description,
onValueChange = { description = it },
modifier = Modifier.testTag("description_field").height(84.dp))

// Save Changes button
Button(
onClick = {
val errorMessage = validateFields(name, dob, description)
if (errorMessage != null) {
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
} else {
// Save the profile (future implementation)
Toast.makeText(context, "Profile saved", Toast.LENGTH_SHORT).show()
francelu marked this conversation as resolved.
Show resolved Hide resolved
}
},
enabled = true,
modifier =
Modifier.padding(1.dp)
.testTag("save_button")
.align(Alignment.CenterHorizontally)
.background(
color = Color(0xFFD9D9D9), shape = RoundedCornerShape(size = 100.dp)),
colors = ButtonDefaults.buttonColors(Color(0xFFD9D9D9)),
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
) {
Text("Save Changes")
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
}
}
})
}

@Composable
fun ProfileTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
modifier =
modifier
.clip(RoundedCornerShape(10.dp))
.border(1.dp, MaterialTheme.colorScheme.onSurface, RoundedCornerShape(10.dp)))
}

@Composable
fun ProfileText(title: String, textStyle: TextStyle = TextStyle(fontWeight = FontWeight(500))) {
Text(text = title, style = textStyle)
}

@Composable
fun ProfileField(
title: String,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
ProfileText(title)
ProfileTextField(value = value, onValueChange = onValueChange, modifier = modifier)
}
francelu marked this conversation as resolved.
Show resolved Hide resolved

/** Validates the fields of the profile screen. */
private fun validateFields(name: String, date: String, description: String): String? {
return when {
name.isEmpty() -> "Please enter a name"
!validateDate(date) -> "Invalid date"
description.isEmpty() -> "Please enter a description"
else -> null
}
}
2 changes: 1 addition & 1 deletion app/src/main/res/values-night/themes.xml
francelu marked this conversation as resolved.
Show resolved Hide resolved
francelu marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.PeriodPals" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/themes.xml
francelu marked this conversation as resolved.
Show resolved Hide resolved
Harrish92 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.PeriodPals" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
Expand Down
Loading