Skip to content

Commit

Permalink
Merge pull request #20 from PeriodPals/feat/profile/create
Browse files Browse the repository at this point in the history
Feat/profile/create
  • Loading branch information
Harrish92 authored Oct 13, 2024
2 parents 2b6183b + 0729554 commit 3c9ecc7
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 9 deletions.
11 changes: 11 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ dependencies {
// implementation(libs.androidx.fragment.ktx)
// implementation(libs.kotlinx.serialization.json)

implementation(libs.compose)
implementation(libs.mockk.v1120)
implementation(libs.androidx.ui.test.junit4.v105)
implementation(libs.androidx.ui.test.manifest.v105)

configurations.all {
resolutionStrategy {
force("androidx.test.ext:junit:1.1.5")
force("androidx.test.espresso:espresso-core:3.5.0")
}
}
// supabase setup
implementation(platform("io.github.jan-tennert.supabase:bom:3.0.0"))
implementation(libs.github.postgrest.kt)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.android.periodpals.ui.profile

import androidx.compose.ui.test.assertIsDisplayed
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.test.ext.junit.runners.AndroidJUnit4
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class CreateProfileTest {

@get:Rule val composeTestRule = createComposeRule()

@Test
fun testProfileImageDisplayed() {
composeTestRule.setContent { CreateProfile() }

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

@Test
fun testFormFieldsDisplayed() {
composeTestRule.setContent { CreateProfile() }

// Check if the form fields are displayed
composeTestRule.onNodeWithTag("email_field").assertIsDisplayed()
composeTestRule.onNodeWithTag("dob_field").assertIsDisplayed()
composeTestRule.onNodeWithTag("name_field").assertIsDisplayed()
composeTestRule.onNodeWithTag("description_field").assertIsDisplayed()
}

@Test
fun testSaveButtonClickWithValidDate() {
composeTestRule.setContent { CreateProfile() }

// Input valid date
composeTestRule.onNodeWithTag("dob_field").performTextInput("01/01/2000")

// Perform click on the save button
composeTestRule.onNodeWithTag("save_button").performClick()
composeTestRule.waitForIdle()

assertTrue(validateDate("01/01/2000"))
assertTrue(validateDate("31/12/1999"))
}

@Test
fun testSaveButtonClickWithInvalidDate() {
composeTestRule.setContent { CreateProfile() }

// Input invalid date
composeTestRule.onNodeWithTag("dob_field").performTextInput("invalid_date")

// Perform click on the save button
composeTestRule.onNodeWithTag("save_button").performClick()
composeTestRule.waitForIdle()

assertFalse(validateDate("32/01/2000")) // Invalid day
assertFalse(validateDate("01/13/2000")) // Invalid month
assertFalse(validateDate("01/01/abcd")) // Invalid year
assertFalse(validateDate("01-01-2000")) // Invalid format
assertFalse(validateDate("01/01")) // Incomplete date
}
}
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
android:theme="@style/Theme.PeriodPals"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name="com.android.periodpals.MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.PeriodPals">
Expand All @@ -26,5 +26,4 @@
</intent-filter>
</activity>
</application>

</manifest>
5 changes: 4 additions & 1 deletion app/src/main/java/com/android/periodpals/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerIcon.Companion.Text
import androidx.compose.ui.unit.dp
import com.android.periodpals.ui.profile.CreateProfile
import com.android.periodpals.ui.theme.PeriodPalsAppTheme
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
Expand All @@ -30,7 +32,8 @@ class MainActivity : ComponentActivity() {
PeriodPalsAppTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
CountriesList()
// CountriesList()
CreateProfile()
}
}
}
Expand Down
198 changes: 198 additions & 0 deletions app/src/main/java/com/android/periodpals/ui/profile/CreateProfile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package com.android.periodpals.ui.profile

import android.app.Activity
import android.content.Intent
import android.icu.util.GregorianCalendar
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.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
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.unit.dp
import androidx.compose.ui.unit.sp
import com.android.periodpals.R
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage

@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun CreateProfile() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var age by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }

var profileImageUri by remember {
mutableStateOf<Uri?>(
Uri.parse("android.resource://com.android.periodpals/" + R.drawable.generic_avatar))
}
var context = LocalContext.current

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

Scaffold(
modifier = Modifier.fillMaxSize(),
content = { padding ->
Column(
modifier = Modifier.fillMaxSize().padding(16.dp).padding(padding),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier =
Modifier.size(124.dp)
.clip(shape = RoundedCornerShape(100.dp))
.background(
color = MaterialTheme.colorScheme.background,
shape = RoundedCornerShape(100.dp))
.testTag("profile_image")
.clickable {
val pickImageIntent = Intent(Intent.ACTION_PICK).apply { type = "image/*" }
launcher.launch(pickImageIntent)
}) {
GlideImage(
model = profileImageUri,
contentDescription = "profile picture",
contentScale = ContentScale.Crop,
modifier =
Modifier.size(124.dp)
.background(
color = MaterialTheme.colorScheme.background, shape = CircleShape))
}

Box(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Mandatory",
style =
TextStyle(
fontSize = 20.sp,
lineHeight = 20.sp,
fontWeight = FontWeight(500),
letterSpacing = 0.2.sp,
),
)
}

OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
placeholder = { Text("Enter your email") },
modifier = Modifier.testTag("email_field"))

OutlinedTextField(
value = age,
onValueChange = { age = it },
label = { Text("Date of Birth") },
placeholder = { Text("DD/MM/YYYY") },
modifier = Modifier.testTag("dob_field"))

Box(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Your profile",
style =
TextStyle(
fontSize = 20.sp,
lineHeight = 20.sp,
fontWeight = FontWeight(500),
letterSpacing = 0.2.sp,
),
)
}

OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Displayed Name") },
placeholder = { Text("Enter your name") },
modifier = Modifier.testTag("name_field"),
)

OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("Description") },
placeholder = { Text("Enter a description") },
modifier = Modifier.height(124.dp).testTag("description_field"),
)

Button(
onClick = {
if (validateDate(age)) {
// Save the profile (future implementation)
Toast.makeText(context, "Profile saved", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Invalid date", Toast.LENGTH_SHORT).show()
}
},
enabled = true,
modifier =
Modifier.padding(0.dp)
.width(84.dp)
.height(40.dp)
.testTag("save_button")
.background(
color = Color(0xFF65558F), shape = RoundedCornerShape(size = 100.dp)),
colors = ButtonDefaults.buttonColors(Color(0xFF65558F))) {
Text(
"Save",
color = Color.White,
)
}
}
})
}

fun validateDate(date: String): Boolean {
val parts = date.split("/")
val calendar = GregorianCalendar.getInstance()
calendar.isLenient = false
if (parts.size == 3) {
return try {
calendar.set(parts[2].toInt(), parts[1].toInt() - 1, parts[0].toInt())
calendar.time
true
} catch (e: Exception) {
false
}
}
return false
}
10 changes: 6 additions & 4 deletions app/src/main/java/com/android/periodpals/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
Expand All @@ -20,18 +21,19 @@ private val DarkColorScheme =

private val LightColorScheme =
lightColorScheme(
primary = Purple40, secondary = PurpleGrey40, tertiary = Pink40
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40,

/* Other default colors to override
// Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
)

@Composable
fun PeriodPalsAppTheme(
Expand Down
Binary file added app/src/main/res/drawable/generic_avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 3c9ecc7

Please sign in to comment.