-
Notifications
You must be signed in to change notification settings - Fork 0
[feat#26] search view and api #27
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
Conversation
WalkthroughIntroduces mentor search and profile viewing features across all architectural layers. Adds SearchDataSource, SearchRepository, and corresponding DTOs for API integration. Implements SearchScreen and ProfileScreen UI components with navigation. Refactors SocialLogin DTOs. Includes DI bindings, Retrofit service endpoints, and reactive state management via ViewModels and flows. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SearchScreen
participant SearchVM as SearchViewModel
participant SearchRepo as SearchRepository
participant SearchDS as SearchDataSource
participant API as SearchService API
User->>SearchScreen: Navigate to Search
activate SearchScreen
SearchScreen->>SearchVM: LoadMentors intent
activate SearchVM
SearchVM->>SearchRepo: getMentors(page=0, size=15)
activate SearchRepo
SearchRepo->>SearchDS: getMentors(page, size)
activate SearchDS
SearchDS->>API: GET /users/mentors?page=0&size=15
API-->>SearchDS: MentorListResponseDto
deactivate SearchDS
SearchRepo->>SearchRepo: Map DTO to MentorListEntity
SearchRepo-->>SearchVM: Result<MentorListEntity>
deactivate SearchRepo
SearchVM->>SearchVM: Update uiState with mentors
deactivate SearchVM
SearchScreen->>SearchScreen: Display mentor list
deactivate SearchScreen
User->>SearchScreen: Tap mentor item
activate SearchScreen
SearchScreen->>SearchVM: NavigateToProfile(userId)
activate SearchVM
SearchVM->>SearchVM: Emit NavigateToProfile side effect
deactivate SearchVM
SearchScreen->>SearchScreen: Navigate to ProfileScreen
deactivate SearchScreen
User->>ProfileScreen: ProfileScreen loaded
activate ProfileScreen
ProfileScreen->>SearchVM: LoadProfile(userId)
activate SearchVM
SearchVM->>SearchRepo: getUserProfile(userId)
activate SearchRepo
SearchRepo->>SearchDS: getUserProfile(userId)
activate SearchDS
SearchDS->>API: GET /users/profiles/{userId}
API-->>SearchDS: UserProfileResponseDto
deactivate SearchDS
SearchRepo->>SearchRepo: Map DTO to UserProfileEntity
SearchRepo-->>SearchVM: Result<UserProfileEntity>
deactivate SearchRepo
SearchVM->>SearchVM: Update uiState with profile
deactivate SearchVM
ProfileScreen->>ProfileScreen: Display profile details
deactivate ProfileScreen
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Areas requiring extra attention:
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (13)
app/src/main/java/com/hsLink/hslink/presentation/search/component/HsLinkIconActionButton.kt (1)
35-56: Simplify the duplicated textColor logic.The textColor assignment is identical in both the enabled and disabled branches (lines 39-43 vs. 50-54), making the conditional unnecessary for text color. Only the backgroundColor actually changes based on the
isEnabledstate.Additionally, it's unusual that disabled buttons retain the same text color as enabled ones—typically, disabled states have reduced contrast for better UX signaling.
Apply this diff to eliminate the duplication:
val textStyle = size.getTextStyle() - val textColor: Color + val textColor = when(size){ + HsLinkActionButtonSize.Large -> HsLinkTheme.colors.Common + HsLinkActionButtonSize.Medium -> HsLinkTheme.colors.Common + HsLinkActionButtonSize.Small -> HsLinkTheme.colors.Grey500 + } + val backgroundColor: Color if (isEnabled) { - textColor = when(size){ - HsLinkActionButtonSize.Large -> HsLinkTheme.colors.Common - HsLinkActionButtonSize.Medium -> HsLinkTheme.colors.Common - HsLinkActionButtonSize.Small -> HsLinkTheme.colors.Grey500 - } backgroundColor = when (size) { HsLinkActionButtonSize.Large -> HsLinkTheme.colors.DeepBlue500 HsLinkActionButtonSize.Medium -> HsLinkTheme.colors.SkyBlue400 HsLinkActionButtonSize.Small -> HsLinkTheme.colors.Grey100 } } else { - textColor = when (size) { - HsLinkActionButtonSize.Large -> HsLinkTheme.colors.Common - HsLinkActionButtonSize.Medium -> HsLinkTheme.colors.Common - HsLinkActionButtonSize.Small -> HsLinkTheme.colors.Grey500 - } backgroundColor = HsLinkTheme.colors.Grey200 }If the text color is intentionally the same for both states, this refactor preserves that behavior while improving maintainability. If disabled buttons should have reduced contrast, consider using a different color (e.g., Grey400 or Grey300) for the disabled state.
app/src/main/java/com/hsLink/hslink/presentation/search/component/CareerSection.kt (1)
20-59: Consider adding empty state handling.The component renders the title and card structure even when the careers list is empty. Consider conditionally rendering the entire component or showing an appropriate empty state message.
Apply this diff to conditionally render only when careers are present:
@Composable fun CareerCard( careers: List<CareerEntity>, modifier: Modifier = Modifier ) { + if (careers.isEmpty()) return + Column(modifier = modifier) {Alternatively, add an empty state message inside the card when the list is empty.
app/src/main/java/com/hsLink/hslink/presentation/search/component/LinkCard.kt (2)
20-56: Consider empty state handling and clickable links.Similar to CareerCard, this component renders the title and card structure even when the links list is empty. Additionally, the URLs are displayed as plain text rather than clickable links.
Consider these improvements:
- Conditionally render when links are present:
@Composable fun LinkCard( links: List<LinkEntity>, modifier: Modifier = Modifier ) { + if (links.isEmpty()) return + Column(modifier = modifier) {
- Make URLs clickable using
ClickableTextor wrapping in a clickable modifier with platform-specific URL opening logic.
80-88: Consider extracting link type constants.The link type strings are hardcoded magic strings. Consider defining them as constants (in a companion object or separate file) to improve maintainability and reduce typos.
Example:
private object LinkType { const val LINKEDIN = "LINKEDIN" const val GITHUB = "GITHUB" const val INSTAGRAM = "INSTAGRAM" const val BLOG = "BLOG" } private fun getLinkTypeText(type: String): String { return when (type) { LinkType.LINKEDIN -> "LinkedIn" LinkType.GITHUB -> "GitHub" LinkType.INSTAGRAM -> "Instagram" LinkType.BLOG -> "블로그" else -> "기타" } }app/src/main/java/com/hsLink/hslink/presentation/search/component/SearchUserItems.kt (3)
24-26: Consider renaming the preview function to match the component.The preview function is named
MyPageDetailItemContentPreviewbut it previewsSearchUserItems. This naming mismatch could cause confusion.Apply this diff:
-private fun MyPageDetailItemContentPreview() { +private fun SearchUserItemsPreview() {
37-43: Remove or integrate the unused data class.
SearchUserItemsDatais defined but never used by theSearchUserItemscomposable or elsewhere in the file. If this was intended for future use, consider removing it until needed to avoid dead code.
76-98: Prefer theme colors over hardcoded Black.The component uses
HsLinkTheme.colorsfor some colors (e.g.,Grey400,Common) but hardcodesBlackdirectly in multiple places. For consistency and to support potential theming changes, consider using a theme color instead.If
HsLinkTheme.colorsprovides a text color property, apply this diff:Text( text = name, - color = Black, + color = HsLinkTheme.colors.TextPrimary, // or appropriate theme color style = HsLinkTheme.typography.title_20Strong ) Text( text = title, - color = Black, + color = HsLinkTheme.colors.TextPrimary, // or appropriate theme color style = HsLinkTheme.typography.body_14Normal )Icon( imageVector = ImageVector.vectorResource(id = R.drawable.ic_mypage_item_lefrarrow), contentDescription = null, - tint = Black + tint = HsLinkTheme.colors.IconPrimary // or appropriate theme color )app/src/main/java/com/hsLink/hslink/data/di/ServiceModule.kt (1)
21-25: Consider using expression body for consistency.The existing
providesDummyServiceuses an expression body (=), while the newprovideSearchServiceuses a block body with an explicit return. For consistency within the module, consider using an expression body.Apply this diff:
@Provides @Singleton - fun provideSearchService(retrofit: Retrofit): SearchService { - return retrofit.create(SearchService::class.java) - } + fun provideSearchService(retrofit: Retrofit): SearchService = + retrofit.create(SearchService::class.java)app/src/main/java/com/hsLink/hslink/data/dto/response/SearchResponseDto.kt (1)
5-28: Consider nullable fields for API resilience.All DTO fields are non-nullable, which assumes the backend will always provide every field. If the API contract guarantees non-null values, this is fine. However, if there's any chance of missing fields in the response, consider making optional fields nullable to prevent deserialization failures.
Additionally,
totalMentorCountusesLongwhich supports values up to ~9 quintillion. Unless you expect more than 2 billion mentors,Intwould suffice and reduce memory footprint slightly.app/src/main/java/com/hsLink/hslink/presentation/search/viewmodel/ProfileViewModel.kt (2)
38-64: Good error handling with proper fallback.The loadProfile implementation correctly handles both success and failure cases. The fallback to "Unknown error" on Line 61 when
exception.messageis null is a good defensive practice.Similar to the repository layer, consider:
- Using English for log messages for better team readability
- Evaluating log levels for production builds
70-74: Minor: Unnecessary coroutine launch.The
navigateBack()function launches a coroutine just to callpostSideEffect(). SincepostSideEffect()is a suspend function and side effects are typically emitted synchronously, you could makenavigateBack()itself a suspend function or handle the launch at the call site inhandleIntent(). However, the current approach is consistent with how side effects are managed elsewhere in the ViewModel.If you want to simplify:
private fun navigateBack() { viewModelScope.launch { postSideEffect(ProfileSideEffect.NavigateBack) } }is fine, or you could refactor to:
private fun navigateBack() { viewModelScope.launch { _sideEffect.emit(ProfileSideEffect.NavigateBack) } }app/src/main/java/com/hsLink/hslink/presentation/search/viewmodel/SearchViewModel.kt (1)
68-88: Guard loadMore while the first page is in flight
loadMoreMentors()only checksisLoadingMore. If the UI fires a load-more intent before the initialloadMentors()finishes, the page‑1 result can overwrite the page‑0 payload (and resetcurrentPage), so users lose mentors. Add a guard (if (currentState.isLoading || currentState.isLoadingMore) return) or otherwise serialize these jobs so pagination can’t race the initial fetch.app/src/main/java/com/hsLink/hslink/data/repositoryimpl/search/SearchRepositoryImpl.kt (1)
53-92: Add logging for consistency and debugging.The
getUserProfilefunction is implemented correctly, but it lacks the logging statements present ingetMentors. Adding similar logs (request parameters, API response status, errors) would improve debugging consistency across the repository.Consider adding logs similar to the
getMentorspattern:override suspend fun getUserProfile(userId: Long): Result<UserProfileEntity> { + Log.d("SearchRepository", "getUserProfile 호출: userId=$userId") return try { val response = searchDataSource.getUserProfile(userId) + Log.d("SearchRepository", "API 응답: isSuccess=${response.isSuccess}, message=${response.message}") if (response.isSuccess) { val userProfileEntity = UserProfileEntity( // ... mapping code ... ) + Log.d("SearchRepository", "프로필 변환 완료") Result.success(userProfileEntity) } else { + Log.e("SearchRepository", "API 실패: ${response.message}") Result.failure(Exception(response.message)) } } catch (e: Exception) { + Log.e("SearchRepository", "예외 발생: ${e.message}", e) Result.failure(e) } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
app/src/main/java/com/hsLink/hslink/data/di/DataSourceModule.kt(2 hunks)app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt(2 hunks)app/src/main/java/com/hsLink/hslink/data/di/ServiceModule.kt(2 hunks)app/src/main/java/com/hsLink/hslink/data/dto/request/SocialLoginRequestDto.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/dto/response/SearchResponseDto.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/dto/response/SocialLoginResponseDto.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/dto/response/UserProfileResponseDto.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/remote/datasource/SearchDataSource.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/remote/datasourceimpl/SearchDataSourceImpl.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/repositoryimpl/AuthRepositoryImpl.kt(2 hunks)app/src/main/java/com/hsLink/hslink/data/repositoryimpl/search/SearchRepositoryImpl.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/service/login/AuthService.kt(1 hunks)app/src/main/java/com/hsLink/hslink/data/service/search/SearchService.kt(1 hunks)app/src/main/java/com/hsLink/hslink/domain/model/search/SearchEntity.kt(1 hunks)app/src/main/java/com/hsLink/hslink/domain/model/search/UserProfileEntity.kt(1 hunks)app/src/main/java/com/hsLink/hslink/domain/repository/AuthRepository.kt(1 hunks)app/src/main/java/com/hsLink/hslink/domain/repository/search/SearchRepository.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt(2 hunks)app/src/main/java/com/hsLink/hslink/presentation/main/MainNavigator.kt(2 hunks)app/src/main/java/com/hsLink/hslink/presentation/main/MainTab.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/SearchScreen.kt(0 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/component/CareerSection.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/component/HsLinkIconActionButton.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/component/LinkCard.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/component/ProfileCard.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/component/SearchUserItems.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/navigation/ProfileNavigation.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/navigation/SearchNavigation.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/screen/ProfileScreen.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/screen/SearchScreen.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/state/ProfileContract.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/state/SearchUiState.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/viewmodel/ProfileViewModel.kt(1 hunks)app/src/main/java/com/hsLink/hslink/presentation/search/viewmodel/SearchViewModel.kt(1 hunks)app/src/main/res/drawable/ic_search_mail.xml(1 hunks)
💤 Files with no reviewable changes (1)
- app/src/main/java/com/hsLink/hslink/presentation/search/SearchScreen.kt
🧰 Additional context used
🧬 Code graph analysis (8)
app/src/main/java/com/hsLink/hslink/presentation/search/screen/ProfileScreen.kt (5)
app/src/main/java/com/hsLink/hslink/core/designsystem/component/HsLinkTopBar.kt (1)
HsLinkTopBar(22-68)app/src/main/java/com/hsLink/hslink/presentation/search/component/ProfileCard.kt (1)
ProfileCard(91-180)app/src/main/java/com/hsLink/hslink/presentation/search/component/CareerSection.kt (1)
CareerCard(19-59)app/src/main/java/com/hsLink/hslink/presentation/search/component/LinkCard.kt (1)
LinkCard(19-56)app/src/main/java/com/hsLink/hslink/presentation/search/component/HsLinkIconActionButton.kt (1)
HsLinkIconActionButton(24-91)
app/src/main/java/com/hsLink/hslink/presentation/search/navigation/ProfileNavigation.kt (1)
app/src/main/java/com/hsLink/hslink/presentation/search/screen/ProfileScreen.kt (1)
ProfileRoute(61-92)
app/src/main/java/com/hsLink/hslink/presentation/search/viewmodel/SearchViewModel.kt (1)
app/src/main/java/com/hsLink/hslink/presentation/main/MainNavigator.kt (1)
navigateToProfile(76-78)
app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt (2)
app/src/main/java/com/hsLink/hslink/presentation/search/navigation/SearchNavigation.kt (1)
searchNavGraph(16-26)app/src/main/java/com/hsLink/hslink/presentation/search/navigation/ProfileNavigation.kt (1)
profileNavGraph(20-32)
app/src/main/java/com/hsLink/hslink/presentation/search/screen/SearchScreen.kt (3)
app/src/main/java/com/hsLink/hslink/core/designsystem/theme/Theme.kt (1)
HsLinkTheme(37-57)app/src/main/java/com/hsLink/hslink/core/designsystem/component/HsLinkTopBar.kt (1)
HsLinkTopBar(22-68)app/src/main/java/com/hsLink/hslink/presentation/search/component/SearchUserItems.kt (1)
SearchUserItems(45-101)
app/src/main/java/com/hsLink/hslink/presentation/search/component/ProfileCard.kt (1)
app/src/main/java/com/hsLink/hslink/core/designsystem/theme/Theme.kt (1)
HsLinkTheme(37-57)
app/src/main/java/com/hsLink/hslink/presentation/search/navigation/SearchNavigation.kt (2)
app/src/main/java/com/hsLink/hslink/presentation/main/MainNavigator.kt (1)
navigate(36-54)app/src/main/java/com/hsLink/hslink/presentation/search/screen/SearchScreen.kt (1)
SearchRoute(83-114)
app/src/main/java/com/hsLink/hslink/presentation/search/component/SearchUserItems.kt (1)
app/src/main/java/com/hsLink/hslink/core/designsystem/theme/Theme.kt (1)
HsLinkTheme(37-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-test
🔇 Additional comments (31)
app/src/main/res/drawable/ic_search_mail.xml (1)
1-20: Well-formed vector drawable with appropriate styling.The icon is properly structured with valid XML namespace, correct dimensions (24×24 dp), and consistent stroke styling across both paths. The geometry is sound: the first path creates a clean mailbox outline with rounded corners, and the second path forms the envelope flap line. Stroke width (1.5) and line styling (round joins/caps) are appropriate for a minimal 24 dp icon.
app/src/main/java/com/hsLink/hslink/presentation/search/component/HsLinkIconActionButton.kt (1)
24-91: Well-structured composable implementation.The component follows Compose best practices with proper parameter defaults, modifier chaining, and layout composition. The click handling correctly respects the
isEnabledstate, and the UI elements are properly aligned and styled.app/src/main/java/com/hsLink/hslink/data/dto/request/SocialLoginRequestDto.kt (1)
6-9: LGTM! Clean DTO naming convention.The rename from
SocialLoginRequesttoSocialLoginRequestDtofollows consistent DTO naming patterns and all consuming code has been updated accordingly.app/src/main/java/com/hsLink/hslink/domain/repository/AuthRepository.kt (1)
3-9: LGTM! Return type updated consistently.The import and return type are correctly updated to
SocialLoginResponseDto, maintaining consistency with the DTO rename across the authentication flow.app/src/main/java/com/hsLink/hslink/data/service/login/AuthService.kt (1)
3-11: LGTM! Service interface updated consistently.The imports and method signature are correctly updated to use
SocialLoginRequestDtoandSocialLoginResponseDto, maintaining type safety and consistency across the Retrofit service layer.app/src/main/java/com/hsLink/hslink/data/repositoryimpl/AuthRepositoryImpl.kt (1)
3-31: LGTM! Implementation updated consistently.The repository implementation correctly uses the renamed DTO types:
- Line 18: Request constructed with
SocialLoginRequestDto- Line 16: Return type updated to
Result<SocialLoginResponseDto>- Error handling and null safety maintained
app/src/main/java/com/hsLink/hslink/data/dto/response/SocialLoginResponseDto.kt (1)
6-11: TheneedsOnboardingfield is properly integrated and handled throughout the codebase.Verification confirms the new field flows correctly from the API response through the ViewModel, into the LoginState, and is used in KaKaoLoginScreen for conditional navigation (directing users to onboarding or home based on this flag). All consuming code properly accesses and handles the field.
Note: The AI summary was incorrect in stating "No behavioral changes or additional fields introduced"—the new field does represent a behavioral change to support onboarding logic, but it is properly implemented end-to-end.
app/src/main/java/com/hsLink/hslink/presentation/main/MainTab.kt (1)
10-10: LGTM: Clean package refactoring.The import update correctly reflects the relocation of Search navigation to its dedicated package.
app/src/main/java/com/hsLink/hslink/data/dto/response/UserProfileResponseDto.kt (3)
5-11: LGTM: Standard API response wrapper.The DTO follows a consistent pattern for API responses with success indicators and error handling fields.
12-24: LGTM: Well-structured profile DTO.The DTO comprehensively captures user profile data with appropriate types. Empty lists for careers and links are preferable to nullable lists.
25-34: LGTM: Nullable endYm correctly models ongoing employment.The nullable
endYmfield appropriately represents current positions where the end date is not yet determined.app/src/main/java/com/hsLink/hslink/presentation/search/component/CareerSection.kt (1)
83-87: LGTM: Period formatting handles current employment correctly.The logic appropriately displays "현재" for ongoing positions. Note that this assumes
startYmandendYmare pre-formatted strings from the API.app/src/main/java/com/hsLink/hslink/domain/repository/search/SearchRepository.kt (1)
6-9: LGTM: Clean repository interface.The interface follows best practices with suspend functions and Result types for proper async operations and error handling.
app/src/main/java/com/hsLink/hslink/domain/model/search/UserProfileEntity.kt (1)
3-30: LGTM: Well-structured domain entities.The domain models are immutable and properly separated from DTOs, following clean architecture principles. The nullable
endYmfield in CareerEntity correctly models ongoing employment.app/src/main/java/com/hsLink/hslink/domain/model/search/SearchEntity.kt (1)
3-18: LGTM: Clean domain models with pagination support.The entities properly model paginated mentor search results with appropriate fields for list display.
app/src/main/java/com/hsLink/hslink/presentation/search/navigation/SearchNavigation.kt (2)
1-1: LGTM: Package reorganization.Moving search navigation to its dedicated package improves code organization and modularity.
16-26: LGTM: Enhanced navigation signature.The updated function signature adds profile navigation support while maintaining clean parameter naming. The default empty lambda for
onNavigateToProfileprovides flexibility for callers.app/src/main/java/com/hsLink/hslink/presentation/search/state/SearchUiState.kt (1)
5-24: LGTM!Clean MVI pattern implementation with immutable state, well-defined intents, and appropriate side effects. The use of sealed classes for intents and side effects provides type safety, and the state defaults are sensible.
app/src/main/java/com/hsLink/hslink/data/di/RepositoryModule.kt (1)
44-47: Verify the scope requirement for SearchRepository.The binding follows the module's pattern (most repositories are not
@Singleton, exceptAuthRepository). Please confirm thatSearchRepositorydoesn't need to be a singleton or maintain state across the application lifecycle.app/src/main/java/com/hsLink/hslink/data/di/DataSourceModule.kt (1)
27-30: LGTM!The data source binding follows the established pattern in the module and correctly maps
SearchDataSourceImpltoSearchDataSource.app/src/main/java/com/hsLink/hslink/presentation/main/MainNavHost.kt (1)
39-49: LGTM with a minor observation on parameter naming.The navigation wiring correctly integrates the profile navigation flow. The search graph now properly passes the
onNavigateToProfilecallback to enable navigation from search results to profile details.Minor note: The parameter name
paddingValuesin the search and profile nav graphs differs frompaddingused in other nav graphs (e.g.,homeNavGraph,communityNavGraph). While not an issue, consistent parameter naming across all nav graphs would improve readability.app/src/main/java/com/hsLink/hslink/presentation/main/MainNavigator.kt (1)
76-78: LGTM!The
navigateToProfilefunction follows the established pattern in the navigator and correctly delegates to the extension function with appropriate parameters.app/src/main/java/com/hsLink/hslink/data/remote/datasource/SearchDataSource.kt (1)
6-9: LGTM!Clean data source interface with appropriate suspend functions for async operations and pagination support for mentor listing.
app/src/main/java/com/hsLink/hslink/presentation/search/component/SearchUserItems.kt (1)
95-95: Code correctly references existing drawable resource.The resource
ic_mypage_item_lefrarrowexists as a valid drawable (app/src/main/res/drawable/ic_mypage_item_lefrarrow.xml). The code at line 95 correctly references this resource. While the resource name may contain a spelling variation ("lefr" vs "left"), the reference is valid and will not cause runtime issues.app/src/main/java/com/hsLink/hslink/data/service/search/SearchService.kt (1)
9-21: LGTM!The service interface is well-structured with appropriate default pagination parameters and proper use of Retrofit annotations.
app/src/main/java/com/hsLink/hslink/data/remote/datasourceimpl/SearchDataSourceImpl.kt (1)
9-20: LGTM!Clean data source implementation that properly delegates to the service layer. The dependency injection setup is correct.
app/src/main/java/com/hsLink/hslink/presentation/search/state/ProfileContract.kt (1)
5-20: LGTM!Well-structured state management following the MVI pattern. The separation of UI state, intents, and side effects is clean and appropriate. Nullable types are correctly used for optional state fields.
app/src/main/java/com/hsLink/hslink/presentation/search/navigation/ProfileNavigation.kt (1)
13-35: LGTM!Excellent use of type-safe navigation with Kotlin Serialization. The navigation setup correctly extracts the userId parameter and passes it through to the ProfileRoute composable.
app/src/main/java/com/hsLink/hslink/presentation/search/viewmodel/ProfileViewModel.kt (1)
23-27: LGTM!Proper use of StateFlow for UI state and SharedFlow for one-time side effects. The flow setup correctly exposes immutable views of the internal mutable flows.
app/src/main/java/com/hsLink/hslink/data/repositoryimpl/search/SearchRepositoryImpl.kt (2)
1-15: LGTM! Clean architecture and DI setup.The class follows proper clean architecture principles with dependency injection. The repository implementation correctly depends on the data source abstraction, and all necessary imports are present.
17-50: Implementation looks solid.The
getMentorsfunction properly handles API calls, error cases, and DTO-to-entity mapping. The extensive logging will be helpful for debugging.
| StatusItem( | ||
| title = if (profile.jobSeeking) "구직 중" else "구직 안함", | ||
| subtitle = "구직 여부" | ||
| ) | ||
| VerticalDivider( | ||
| modifier = Modifier.height(40.dp), | ||
| color = HsLinkTheme.colors.Grey200 | ||
| ) | ||
| StatusItem( | ||
| title = if (profile.employed) "재직 중" else "미재직", | ||
| subtitle = "재직 여부" | ||
| ) | ||
| VerticalDivider( | ||
| modifier = Modifier.height(40.dp), | ||
| color = HsLinkTheme.colors.Grey200 | ||
| ) | ||
| StatusItem( | ||
| title = getAcademicStatusText(profile.academicStatus), | ||
| subtitle = "재학 여부" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Externalize hard-coded strings to string resources.
The status labels are hard-coded in Korean ("구직 중", "구직 안함", "재직 중", "미재직", "구직 여부", "재직 여부", "재학 여부"). These should be moved to string resources for proper localization support and easier maintenance.
Apply this pattern to externalize strings:
+// In strings.xml
+<string name="job_seeking_yes">구직 중</string>
+<string name="job_seeking_no">구직 안함</string>
+<string name="job_seeking_label">구직 여부</string>
+// ... other strings
StatusItem(
- title = if (profile.jobSeeking) "구직 중" else "구직 안함",
- subtitle = "구직 여부"
+ title = stringResource(if (profile.jobSeeking) R.string.job_seeking_yes else R.string.job_seeking_no),
+ subtitle = stringResource(R.string.job_seeking_label)
)Committable suggestion skipped: line range outside the PR's diff.
| private fun getAcademicStatusText(status: String): String { | ||
| return when (status) { | ||
| "ENROLLED" -> "재학" | ||
| "GRADUATED" -> "졸업" | ||
| "ON_LEAVE" -> "휴학" | ||
| else -> "기타" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Externalize hard-coded strings and consider logging unknown statuses.
Like the status labels above, these academic status strings should be externalized. Additionally, consider logging when an unknown status is encountered to help identify data issues or missing mappings.
private fun getAcademicStatusText(status: String): String {
return when (status) {
- "ENROLLED" -> "재학"
- "GRADUATED" -> "졸업"
- "ON_LEAVE" -> "휴학"
- else -> "기타"
+ "ENROLLED" -> stringResource(R.string.academic_status_enrolled)
+ "GRADUATED" -> stringResource(R.string.academic_status_graduated)
+ "ON_LEAVE" -> stringResource(R.string.academic_status_on_leave)
+ else -> {
+ Log.w("ProfileCard", "Unknown academic status: $status")
+ stringResource(R.string.academic_status_other)
+ }
}
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
app/src/main/java/com/hsLink/hslink/presentation/search/component/ProfileCard.kt
around lines 183 to 190, the academic status labels are hard-coded and unknown
statuses are not logged; move the Korean labels ("재학", "졸업", "휴학", "기타") into
string resources (res/values/strings.xml) and reference them via
context.getString(...) or Composable stringResource; update
getAcademicStatusText to return those resource-backed strings (or keys resolved
by the caller) and add a logging call (e.g., Log.w or your logger) in the else
branch to record unknown status values for diagnostics.
| Spacer(modifier = Modifier.height(32.dp)) | ||
| val context = LocalContext.current | ||
| HsLinkIconActionButton( | ||
| label = "멘토링 메일 보내기", | ||
| iconRes = R.drawable.ic_search_mail, | ||
| onClick = { | ||
| val intent = Intent(Intent.ACTION_SENDTO).apply { | ||
| data = Uri.parse("mailto:${profile.email}") // 프로필의 이메일 주소 사용 | ||
| putExtra(Intent.EXTRA_SUBJECT, "멘토링 문의드립니다.") | ||
| putExtra(Intent.EXTRA_TEXT, "안녕하세요, ${profile.name}님.\n\n멘토링 관련하여 문의드립니다.") | ||
| } | ||
| context.startActivity(intent) | ||
| }, | ||
| size = HsLinkActionButtonSize.Large, | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 16.dp) | ||
| .padding(bottom = 32.dp) | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle the mail intent defensively
Starting the ACTION_SENDTO intent without checking for a matching email client will crash the app (ActivityNotFoundException) on devices that lack one. Please gate the launch with resolveActivity (or wrap it in runCatching and surface an error side effect) and optionally use Intent.createChooser so the flow degrades gracefully instead of killing the screen.
- HsLinkIconActionButton(
+ HsLinkIconActionButton(
label = "멘토링 매일 보내기",
iconRes = R.drawable.ic_search_mail,
onClick = {
- val intent = Intent(Intent.ACTION_SENDTO).apply {
- data = Uri.parse("mailto:${profile.email}")
- putExtra(Intent.EXTRA_SUBJECT, "멘토링 문의드립니다.")
- putExtra(Intent.EXTRA_TEXT, "안녕하세요, ${profile.name}님.\n\n멘토링 관련하여 문의드립니다.")
- }
- context.startActivity(intent)
+ val intent = Intent(Intent.ACTION_SENDTO).apply {
+ data = Uri.parse("mailto:${profile.email}")
+ putExtra(Intent.EXTRA_SUBJECT, "멘토링 문의드립니다.")
+ putExtra(Intent.EXTRA_TEXT, "안녕하세요, ${profile.name}님.\n\n멘토링 관련하여 문의드립니다.")
+ }
+ intent.resolveActivity(context.packageManager)?.let {
+ context.startActivity(Intent.createChooser(intent, "멘토링 메일 보내기"))
+ } ?: run {
+ // TODO: show a toast/snackbar informing the user that no email app is available.
+ }
},🤖 Prompt for AI Agents
In
app/src/main/java/com/hsLink/hslink/presentation/search/screen/ProfileScreen.kt
around lines 169 to 188, the mail ACTION_SENDTO intent is launched unguarded
which can throw ActivityNotFoundException on devices without an email client;
fix by creating a chooser with Intent.createChooser(intent, "Send email") and
only call context.startActivity(chooser) after verifying there is a handler
(e.g., intent.resolveActivity(context.packageManager) != null) or wrap the start
in runCatching and surface an error side-effect (Toast/Snackbar) when no
activity is available so the UI fails gracefully.
ISSUE
❗ WORK DESCRIPTIONAdd commentMore actions
📸 SCREENSHOT
2025-11-11.18.10.04.mov
서버에 데이터가없어서 안나오는거같긴한데 Preview로 보면 다 나오게되어있습니다.
📢 TO REVIEWERS
Summary by CodeRabbit