Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.cornellappdev.hustle.data.model.services

import com.cornellappdev.hustle.data.model.user.User

//TODO: Update model fields once API is finalized
data class Service(
val id: Int,
val name: String,
val category: String,
val minimumPrice: Double,
val priceUnit: String = "",
val rating: Double,
val displayImageUrl: String,
val isFavorited: Boolean,
val user: User
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.cornellappdev.hustle.ui.components.general

import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.cornellappdev.hustle.R

@Composable
fun BackButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
IconButton(onClick = onClick, modifier = modifier) {
Icon(
painter = painterResource(R.drawable.ic_chevron),
contentDescription = "Back Button",
tint = Color.Unspecified
)
}
}

@Preview(showBackground = true)
@Composable
private fun BackButtonPreview() {
BackButton(onClick = {})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.cornellappdev.hustle.ui.components.general

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.cornellappdev.hustle.R
import com.cornellappdev.hustle.ui.theme.HustleTheme

@Composable
fun ClickableSectionHeader(
title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
textStyle: TextStyle = MaterialTheme.typography.headlineSmall
) {
Row(
modifier = modifier.clickable(onClick = onClick),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
style = textStyle
)
Icon(
painter = painterResource(R.drawable.ic_chevron),
contentDescription = "Chevron Icon",
tint = Color.Unspecified,
modifier = Modifier.rotate(180f)
)
}
}

@Preview(showBackground = true)
@Composable
private fun ClickableSectionHeaderPreview() {
HustleTheme {
ClickableSectionHeader(
title = "Popular right now",
onClick = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.cornellappdev.hustle.ui.components.general

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.cornellappdev.hustle.R
import com.cornellappdev.hustle.ui.theme.HustleColors
import com.cornellappdev.hustle.ui.theme.HustleSpacing
import com.cornellappdev.hustle.ui.theme.HustleTheme

@Composable
fun HustleButton(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Slack Compose lint should be telling you that parameters are not in recommended order here, follow the lint please 🙏

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah 😅, for some reason the lint was not being applied, but now it's working, so will fix!

onClick: () -> Unit,
text: String?,
modifier: Modifier = Modifier,
textStyle: TextStyle = MaterialTheme.typography.titleSmall.copy(fontWeight = FontWeight.Bold),
enabled: Boolean = true,
shape: Shape = RoundedCornerShape(20.dp),
border: BorderStroke? = null,
buttonColors: ButtonColors = ButtonDefaults.buttonColors(
containerColor = HustleColors.hustleGreen,
contentColor = HustleColors.white
),
contentPadding: PaddingValues = PaddingValues(
horizontal = HustleSpacing.small,
vertical = HustleSpacing.extraSmall
),
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = enabled,
shape = shape,
border = border,
colors = buttonColors,
contentPadding = contentPadding
) {
leadingIcon?.let {
leadingIcon()
if (text != null) Spacer(modifier = Modifier.width(4.dp))
}
text?.let {
Text(
text = it,
style = textStyle
)
}
trailingIcon?.let {
if (text != null) Spacer(modifier = Modifier.width(4.dp))
trailingIcon()
}
}
}

@Preview(showBackground = true)
@Composable
private fun HustleButtonPreview() {
HustleTheme {
HustleButton(
onClick = {},
text = "Lessons",
leadingIcon = {
Icon(
painter = painterResource(R.drawable.ic_lessons),
contentDescription = "Lessons Icon"
)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.cornellappdev.hustle.ui.components.general

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
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.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.cornellappdev.hustle.R
import com.cornellappdev.hustle.ui.theme.HustleColors
import com.cornellappdev.hustle.ui.theme.HustleSpacing
import com.cornellappdev.hustle.ui.theme.HustleTheme

@Composable
fun HustleSearchBar(
queryState: TextFieldState,
isSearchActive: Boolean,
onFocus: () -> Unit,
onSearch: () -> Unit,
modifier: Modifier = Modifier
) {
val focusManager = LocalFocusManager.current
TextField(
state = queryState,
lineLimits = TextFieldLineLimits.SingleLine,
textStyle = MaterialTheme.typography.labelLarge.copy(
color = HustleColors.secondaryGray,
lineHeight = 18.sp,
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None
),
),
modifier = modifier
.fillMaxWidth()
.height(32.dp)
.onFocusChanged { if (it.isFocused) onFocus() },
shape = RoundedCornerShape(20.dp),
colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
focusedContainerColor = HustleColors.shadedGray.copy(alpha = 0.47f),
unfocusedContainerColor = HustleColors.shadedGray.copy(alpha = 0.47f),
focusedTextColor = HustleColors.secondaryGray,
unfocusedTextColor = HustleColors.secondaryGray,
cursorColor = HustleColors.secondaryGray
),
contentPadding = PaddingValues(
horizontal = HustleSpacing.medium,
vertical = HustleSpacing.extraSmall
),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search
),
onKeyboardAction = {
onSearch()
focusManager.clearFocus()
},
placeholder = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(HustleSpacing.extraSmall)
) {
AnimatedVisibility(
visible = !isSearchActive,
enter = scaleIn() + fadeIn(),
exit = fadeOut(animationSpec = tween(100)) + shrinkHorizontally(
shrinkTowards = Alignment.Start,
animationSpec = tween(100)
)
) {
Icon(
painter = painterResource(R.drawable.ic_search_leading),
contentDescription = "Search Leading Icon",
tint = Color.Unspecified
)
}
Text(
text = "Search services",
style = MaterialTheme.typography.labelLarge,
color = HustleColors.wash
)
}
}
)
}


@Preview(showBackground = true)
@Composable
private fun HustleSearchBarPreview() {
val queryState = rememberTextFieldState()
var isSearchActive by remember { mutableStateOf(false) }
HustleTheme {
HustleSearchBar(
queryState = queryState,
isSearchActive = isSearchActive,
onFocus = { isSearchActive = true },
onSearch = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.cornellappdev.hustle.ui.components.general

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil3.compose.SubcomposeAsyncImage
import com.cornellappdev.hustle.ui.theme.HustleColors

@Composable
fun UserProfilePicture(
imageUrl: String,
modifier: Modifier = Modifier
) {
// TODO: Add loading and error states
SubcomposeAsyncImage(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why SubcomposeAsyncImage over AsyncImage? Oh I see, I suppose you want custom loading and error states? I think that should be fine, if that's the case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it was mainly for the loading and error states in the future

model = imageUrl,
contentDescription = "User Profile Picture",
contentScale = ContentScale.Crop,
modifier = modifier.clip(shape = CircleShape)
)
}

@Preview(showBackground = true)
@Composable
private fun UserProfilePicturePreview() {
UserProfilePicture(
imageUrl = "",
modifier = Modifier
.size(22.dp)
.border(1.dp, HustleColors.secondaryGray, CircleShape)
)
}
Loading
Loading