Skip to content

Conversation

@AndrewCheung360
Copy link
Member

Overview

Implement the ui for the Home/Search screen
https://www.figma.com/design/tRFDgfOFDXGXREx6q33p4P/Hustle?node-id=577-3679&t=ekSdFryK6xeB87PV-0

Changes Made

  • Created Category Button Row for main view of home screen
  • Created MainContent composable for the main view of home screen (as opposed to search view)
  • Created SearchContent composable for the search view of home screen
  • Created Search Header composable with animation for switching between main and search view
  • Created routes for CategoryServices along with enum class with companion object function for determining the enum based on associated string type name
  • Created CategoryServices entry in homenavigation navgraph and passed in some nav functions to home screen
  • Put together composables to implement home screen ui with test variables
  • Updated testing constants with more variety

Test Coverage

  • Emulator and Preview testing

Next Steps

  • Add networking with vm usage once backend is ready
  • Implement the search result screens and add proper navigation logic

Related PRs

Screenshots

Details
Screen.Recording.2025-11-16.at.1.21.47.AM.mov

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements the Home/Search Screen UI for the Hustle Android app, introducing a two-mode interface with smooth animations between main browsing and search states.

Key Changes:

  • Created a dual-mode home screen with animated transitions between main content and search views
  • Implemented category-based navigation with button row and clickable section headers
  • Added search functionality with recent searches and recently viewed services

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
TestingConstants.kt Enhanced test data with diverse service examples across multiple categories
HomeScreen.kt Main screen composable with state management and animated content switching
HomeNavigation.kt Added navigation handlers for service details and category subpages
Routes.kt Introduced CategoryType enum with serialization support and typeName mapping
HustleNavigation.kt Removed unused currentRoute variable
SearchHeader.kt Animated header with search bar that slides up when search is active
SearchContent.kt Search view with recent searches and recently viewed services sections
MainContent.kt Main view with category buttons and multiple service carousels
CategoryButtonRow.kt Horizontal scrollable row of category filter buttons

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

),
displayImageUrl = "https://news.cornell.edu/sites/default/files/styles/full_size/public/06_2023_1114_sh_005-n_1.jpg?itok=E3ecxgYl",
priceUnit = "/hour",
displayImageUrl = "https://s3-media0.fl.yelpcdn.com/bphoto/1KwtwltxdEYVz4TIHAzaow/1000s.jpg"
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

Missing priceUnit field for Service id=3. While the Service model has a default value of \"\", this service should have priceUnit = \"/hour\" for consistency with the other test services.

Suggested change
displayImageUrl = "https://s3-media0.fl.yelpcdn.com/bphoto/1KwtwltxdEYVz4TIHAzaow/1000s.jpg"
displayImageUrl = "https://s3-media0.fl.yelpcdn.com/bphoto/1KwtwltxdEYVz4TIHAzaow/1000s.jpg",
priceUnit = "/hour",

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

some services might not be by hour

companion object {
fun fromTypeName(typeName: String): CategoryType {
return entries.firstOrNull { it.typeName == typeName }
?: throw IllegalArgumentException("No CategoryType with typeName $typeName found.")
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

The error message could be more helpful by including the valid typeName options. Consider: \"Unknown CategoryType '$typeName'. Valid types: ${entries.map { it.typeName }}\"

Suggested change
?: throw IllegalArgumentException("No CategoryType with typeName $typeName found.")
?: throw IllegalArgumentException("Unknown CategoryType '$typeName'. Valid types: ${entries.map { it.typeName }}")

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

will end up removing this function

onClick = {
navigateToCategorySubpage(CategoryType.POPULAR_RIGHT_NOW)
},
modifier = Modifier.padding(start = 32.dp)
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

Hardcoded padding value 32.dp should use HustleSpacing.large (which is 24.dp) or another appropriate spacing constant for consistency. This value appears on lines 58, 73, 88, and 103.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

good thought, but would need to consult design

@AndrewCheung360 AndrewCheung360 self-assigned this Nov 19, 2025
@AndrewCheung360 AndrewCheung360 added the enhancement New feature or request label Nov 19, 2025
Copy link

@zachseidner1 zachseidner1 left a comment

Choose a reason for hiding this comment

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

Nice! I think we should get rid of the fromTypeName function but looks good otherwise

Comment on lines 74 to 79
popularRightNowListings: List<Service>,
newOnHustleListings: List<Service>,
servicesNearYouListings: List<Service>,
availableThisWeekListings: List<Service>,
recentSearches: List<String>,
recentlyViewedServices: List<Service>,

Choose a reason for hiding this comment

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

Nit: This is a lot of data to be passing in as separate parameters. To avoid parameter drilling, it could be nicer to make a data class HomeScreenViewState that contains the data the home screen needs to render

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes sense, I'll add it to the HomeScreen file

AVAILABLE_THIS_WEEK("Available this week");

companion object {
fun fromTypeName(typeName: String): CategoryType {

Choose a reason for hiding this comment

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

do you think we should just make this fromTypeNameOrNull and have the caller handle the failure case to avoid the potential for crashing the app?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm thinking of refactoring this such that the composable that will use this won't need to use this function, so I'll probably remove it.

onSearch = onSearch,
modifier = Modifier.padding(horizontal = HustleSpacing.large)
)
AnimatedContent(

Choose a reason for hiding this comment

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

You found a way to state the two states of this screen very simply so we don't have to navigate to a new screen when this is pressed. This looks great, nice job!

Comment on lines 115 to 118
val popularRightNowListings = TEST_SERVICES
val newOnHustleListings = TEST_SERVICES
val servicesNearYouListings = TEST_SERVICES
val availableThisWeekListings = TEST_SERVICES

Choose a reason for hiding this comment

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

nit: maybe we just pass in TEST_SERVICES directly?

Copy link
Member Author

Choose a reason for hiding this comment

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

lol, I kinda forgot about that.

Comment on lines +115 to +119
enter = fadeIn() + scaleIn(),
exit = fadeOut(animationSpec = tween(150)) + shrinkHorizontally(
shrinkTowards = Alignment.Start,
animationSpec = tween(150)
)

Choose a reason for hiding this comment

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

Love this animation!

) {
items(SERVICE_CATEGORIES, key = { it.name }) { category ->
HustleButton(
onClick = { onCategoryClick(CategoryType.fromTypeName(category.name)) },

Choose a reason for hiding this comment

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

can't we just directly pass in category to onCategoryClick, removing the need for the fromTypeName function entirely?

Copy link
Member Author

@AndrewCheung360 AndrewCheung360 Nov 19, 2025

Choose a reason for hiding this comment

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

I'm planning on moving the categorytype definition to HustleConstants and replacing the ServiceCategory type's name field with categoryType: CategoryType, so that should remove the need for the extra function

@AndrewCheung360 AndrewCheung360 merged commit 971f99e into main Nov 20, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants