-
Notifications
You must be signed in to change notification settings - Fork 0
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/#9] 장소 상세 페이지 navigation 및 UI 구현 #56
Merged
Merged
Changes from all commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
5d5c8a6
[Feat/#9] 장소 상세 페이지 TopAppbar, Profile, Tag, Text 적용
Roel4990 b648ddc
Merge branch 'develop' into feat/#9-place-detail
Roel4990 610b812
[Feat/#9] PlaceDetailScreen StoreInfo 추가
Roel4990 61bc271
Merge branch 'develop' into feat/#9-place-detail
Roel4990 1e703e4
Merge branch 'develop' into feat/#9-place-detail
Roel4990 855d556
[Feat/#9] 장소 상세 페이지 PlaceDetailBottomBar 및 PlaceDetailImageLazyRow 추가
Roel4990 60e5f4e
Merge branch 'develop' into feat/#9-place-detail
Roel4990 49b48a9
[FEAT/#9] add map icon 수정
Roel4990 fa8eaca
Merge branch 'refs/heads/develop' into feat/#9-place-detail
Roel4990 e81f332
[FEAT/#9] 장소 상세 navigation 구현
Roel4990 6793b12
[FEAT/#9] 더미데이터 Preview 로 이동
Roel4990 f715978
Merge branch 'develop' into feat/#9-place-detail
Roel4990 58e8b3f
[FEAT/#9] PlaceDetail 페이지 ViewModel, State 추가 및 IconTagEntity 추가
Roel4990 53e9c3e
[FEAT/#9] ktLintFormat 처리
Roel4990 a3232cf
[FEAT/#9] IconTagEntity => IconTagItem 변경 및 state.iconTag 검증 처리 코드 수정
Roel4990 88617c1
[FEAT/#9] rememberScrollState() 변수화
Roel4990 0870948
[FEAT/#9] MutableStateFlow 형태 변경
Roel4990 87f4aac
[FEAT/#9] MainTabRoute -> Route 로 수정
Roel4990 2aa239c
[FEAT/#9] MainTabRoute -> Route 로 수정
Roel4990 c8049f6
[FEAT/#9] Column 사용 후 padding 값 제거
Roel4990 bff3c13
[FEAT/#9] Column 으로 묶어서 공통 패딩 적용하기
Roel4990 74181d1
[FEAT/#9] 하단 패딩 수정 및 구조 변경
Roel4990 b90ef50
[FEAT/#9] IconTagItem => IconTagEntity Entity 이름 수정
Roel4990 0dfd66e
[FEAT/#9] dropdownMenuList 값 Screen 내부에서 관리
Roel4990 c042f17
Merge branch 'develop' into feat/#9-place-detail
Roel4990 e867254
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 622c89f
[FEAT/#9] UserEntity 추가
Roel4990 aefa10b
[FEAT/#9] PostEntity 추가
Roel4990 d439e70
[FEAT/#9] StoreInfo menuItems -> menuList 수정
Roel4990 834d6c7
[FEAT/#9] PlaceDetail state, viewModel 수정
Roel4990 ca16f03
[FEAT/#9] PlaceDetailRoute spoonAmount, userProfile 추가하기
Roel4990 d644195
[FEAT/#9] dropDownMenuList 유저 정보에 따른 드롭다운 메뉴 리스트 변경을 고려해서 state 에서 관리…
Roel4990 3cac6c2
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 3ba3653
[FEAT/#9] IconTagEntity 기본값 제거
Roel4990 cbb6f57
[FEAT/#9] 수정 사항 코드 수정
Roel4990 e38c436
[FEAT/#9] ktLintFormat 처리
Roel4990 8fd06f7
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 305377b
[FEAT/#9] PlaceDetailBottomBar Modifier 값 제거
Roel4990 4b8be80
[FEAT/#9] Scaffold 에서 paddingValues 받아오기
Roel4990 d62244b
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 4332446
[FEAT/#9] IconTagEntity -> CategoryEntity 로 수정 및 toValidHexColor 함수 추가
Roel4990 5d663d3
[FEAT/#9] onScoopButtonClick 람다 -> 함수 참조로 수정
Roel4990 83aa944
[FEAT/#9] persistentListOf -> immutableListOf 로 수정
Roel4990 7a7be58
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 632f6ef
[FEAT/#9] isScooped default 값 삭제
Roel4990 0523a45
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 a4e8d49
[FEAT/#9] 코드리뷰 사항 수정
Roel4990 bb8ab0c
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 6c2d69d
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
app/src/main/java/com/spoony/spoony/core/util/extension/StringExt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package com.spoony.spoony.core.util.extension | ||
|
||
fun String?.toValidHexColor(): String = | ||
if (this != null && Regex("^[0-9A-Fa-f]{6}$").matches(this)) this else "FFFFFF" |
21 changes: 21 additions & 0 deletions
21
app/src/main/java/com/spoony/spoony/domain/entity/PostEntity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.spoony.spoony.domain.entity | ||
|
||
import kotlinx.collections.immutable.ImmutableList | ||
|
||
data class PostEntity( | ||
val postId: Int, | ||
val userId: Int, | ||
val photoUrlList: ImmutableList<String>, | ||
val title: String, | ||
val date: String, | ||
val menuList: ImmutableList<String>, | ||
val description: String, | ||
val placeName: String, | ||
val placeAddress: String, | ||
val latitude: Double, | ||
val longitude: Double, | ||
val addMapCount: Int, | ||
val isAddMap: Boolean, | ||
val isScooped: Boolean, | ||
val category: CategoryEntity | ||
) |
7 changes: 7 additions & 0 deletions
7
app/src/main/java/com/spoony/spoony/domain/entity/UserEntity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.spoony.spoony.domain.entity | ||
|
||
data class UserEntity( | ||
val userProfileUrl: String, | ||
val userName: String, | ||
val userRegion: String | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
308 changes: 308 additions & 0 deletions
308
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
package com.spoony.spoony.presentation.placeDetail | ||
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.PaddingValues | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
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.sizeIn | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.foundation.rememberScrollState | ||
import androidx.compose.foundation.verticalScroll | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.vector.ImageVector | ||
import androidx.compose.ui.res.vectorResource | ||
import androidx.compose.ui.unit.dp | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.compose.LocalLifecycleOwner | ||
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
import com.spoony.spoony.R | ||
import com.spoony.spoony.core.designsystem.component.button.SpoonyButton | ||
import com.spoony.spoony.core.designsystem.component.tag.IconTag | ||
import com.spoony.spoony.core.designsystem.component.topappbar.TagTopAppBar | ||
import com.spoony.spoony.core.designsystem.theme.SpoonyAndroidTheme | ||
import com.spoony.spoony.core.designsystem.type.ButtonSize | ||
import com.spoony.spoony.core.designsystem.type.ButtonStyle | ||
import com.spoony.spoony.core.state.UiState | ||
import com.spoony.spoony.core.util.extension.hexToColor | ||
import com.spoony.spoony.core.util.extension.noRippleClickable | ||
import com.spoony.spoony.core.util.extension.toValidHexColor | ||
import com.spoony.spoony.domain.entity.CategoryEntity | ||
import com.spoony.spoony.domain.entity.PostEntity | ||
import com.spoony.spoony.domain.entity.UserEntity | ||
import com.spoony.spoony.presentation.placeDetail.component.IconDropdownMenu | ||
import com.spoony.spoony.presentation.placeDetail.component.PlaceDetailImageLazyRow | ||
import com.spoony.spoony.presentation.placeDetail.component.StoreInfo | ||
import com.spoony.spoony.presentation.placeDetail.component.UserProfileInfo | ||
import kotlinx.collections.immutable.ImmutableList | ||
|
||
@Composable | ||
fun PlaceDetailRoute( | ||
paddingValues: PaddingValues, | ||
viewModel: PlaceDetailViewModel = hiltViewModel() | ||
) { | ||
val lifecycleOwner = LocalLifecycleOwner.current | ||
|
||
val state by viewModel.state.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner) | ||
|
||
val spoonAmount = when (state.spoonAmountEntity) { | ||
is UiState.Success -> (state.spoonAmountEntity as UiState.Success<Int>).data | ||
else -> 0 | ||
} | ||
|
||
val userProfile = when (state.userEntity) { | ||
is UiState.Success -> (state.userEntity as UiState.Success<UserEntity>).data | ||
else -> UserEntity( | ||
userProfileUrl = "", | ||
userName = "", | ||
userRegion = "" | ||
) | ||
} | ||
|
||
when (state.postEntity) { | ||
is UiState.Empty -> {} | ||
is UiState.Loading -> {} | ||
is UiState.Failure -> {} | ||
is UiState.Success -> { | ||
with(state.postEntity as UiState.Success<PostEntity>) { | ||
PlaceDetailScreen( | ||
paddingValues = paddingValues, | ||
menuList = data.menuList, | ||
title = data.title, | ||
description = data.description, | ||
userProfileUrl = userProfile.userProfileUrl, | ||
userName = userProfile.userName, | ||
userRegion = userProfile.userRegion, | ||
photoUrlList = data.photoUrlList, | ||
category = data.category, | ||
date = data.date, | ||
placeAddress = data.placeAddress, | ||
placeName = data.placeName, | ||
spoonAmount = spoonAmount, | ||
addMapCount = data.addMapCount, | ||
isScooped = data.isScooped, | ||
isAddMap = data.isAddMap, | ||
onScoopButtonClick = viewModel::useSpoon, | ||
onAddMapButtonClick = viewModel::updateAddMap, | ||
dropdownMenuList = state.dropDownMenuList, | ||
onBackButtonClick = {}, | ||
onReportButtonClick = {} | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
private fun PlaceDetailScreen( | ||
paddingValues: PaddingValues, | ||
menuList: ImmutableList<String>, | ||
title: String, | ||
description: String, | ||
userProfileUrl: String, | ||
userName: String, | ||
userRegion: String, | ||
photoUrlList: ImmutableList<String>, | ||
category: CategoryEntity, | ||
date: String, | ||
placeAddress: String, | ||
placeName: String, | ||
spoonAmount: Int, | ||
addMapCount: Int, | ||
isAddMap: Boolean, | ||
isScooped: Boolean, | ||
onScoopButtonClick: () -> Unit, | ||
onAddMapButtonClick: (Boolean) -> Unit, | ||
onBackButtonClick: () -> Unit, | ||
dropdownMenuList: ImmutableList<String>, | ||
onReportButtonClick: (String) -> Unit | ||
) { | ||
val scrollState = rememberScrollState() | ||
|
||
Column( | ||
modifier = Modifier | ||
.fillMaxSize() | ||
.padding(paddingValues) | ||
) { | ||
TagTopAppBar( | ||
count = spoonAmount, | ||
showBackButton = true, | ||
onBackButtonClick = onBackButtonClick | ||
) | ||
Column( | ||
modifier = Modifier | ||
.weight(1f) | ||
.verticalScroll(scrollState) | ||
) { | ||
Row( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding( | ||
vertical = 8.dp, | ||
horizontal = 20.dp | ||
), | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
UserProfileInfo( | ||
imageUrl = userProfileUrl, | ||
name = userName, | ||
location = "$userRegion 수저", | ||
modifier = Modifier.weight(1f) | ||
) | ||
|
||
IconDropdownMenu( | ||
menuItems = dropdownMenuList, | ||
onMenuItemClick = onReportButtonClick | ||
) | ||
} | ||
|
||
Spacer(modifier = Modifier.height(24.dp)) | ||
|
||
PlaceDetailImageLazyRow( | ||
imageList = photoUrlList, | ||
isBlurred = !isScooped | ||
) | ||
|
||
Spacer(modifier = Modifier.height(32.dp)) | ||
|
||
Column(modifier = Modifier.padding(horizontal = 20.dp)) { | ||
IconTag( | ||
text = category.categoryName, | ||
backgroundColor = Color.hexToColor(category.backgroundColor.toValidHexColor()), | ||
textColor = Color.hexToColor(category.textColor.toValidHexColor()), | ||
iconUrl = category.iconUrl | ||
) | ||
|
||
Spacer(modifier = Modifier.height(8.dp)) | ||
|
||
Text( | ||
text = title, | ||
style = SpoonyAndroidTheme.typography.title1, | ||
color = SpoonyAndroidTheme.colors.black | ||
) | ||
|
||
Spacer(modifier = Modifier.height(8.dp)) | ||
|
||
Text( | ||
text = date, | ||
style = SpoonyAndroidTheme.typography.caption1m, | ||
color = SpoonyAndroidTheme.colors.gray400 | ||
) | ||
|
||
Spacer(modifier = Modifier.height(20.dp)) | ||
|
||
Text( | ||
text = description, | ||
style = SpoonyAndroidTheme.typography.body2m, | ||
color = SpoonyAndroidTheme.colors.gray900 | ||
) | ||
|
||
Spacer(modifier = Modifier.height(32.dp)) | ||
|
||
StoreInfo( | ||
isBlurred = !isScooped, | ||
menuList = menuList, | ||
locationSubTitle = placeName, | ||
location = placeAddress | ||
) | ||
} | ||
Spacer(modifier = Modifier.height(27.dp)) | ||
} | ||
PlaceDetailBottomBar( | ||
addMapCount = addMapCount, | ||
isScooped = isScooped, | ||
isAddMap = isAddMap, | ||
onScoopButtonClick = onScoopButtonClick, | ||
onSearchMapClick = { | ||
// 네이버 길찾기 코드 | ||
}, | ||
onAddMapButtonClick = onAddMapButtonClick | ||
) | ||
} | ||
} | ||
|
||
@Composable | ||
private fun PlaceDetailBottomBar( | ||
addMapCount: Int, | ||
onScoopButtonClick: () -> Unit, | ||
onSearchMapClick: () -> Unit, | ||
onAddMapButtonClick: (Boolean) -> Unit, | ||
modifier: Modifier = Modifier, | ||
isScooped: Boolean = false, | ||
isAddMap: Boolean = false | ||
) { | ||
Row( | ||
modifier = modifier | ||
.fillMaxWidth() | ||
.background(SpoonyAndroidTheme.colors.white) | ||
.padding( | ||
horizontal = 20.dp, | ||
vertical = 10.dp | ||
), | ||
verticalAlignment = Alignment.CenterVertically | ||
) { | ||
if (isScooped) { | ||
SpoonyButton( | ||
text = "길찾기", | ||
onClick = onSearchMapClick, | ||
style = ButtonStyle.Secondary, | ||
size = ButtonSize.Medium, | ||
modifier = Modifier.weight(1f) | ||
) | ||
|
||
Spacer(modifier = Modifier.width(15.dp)) | ||
|
||
Column( | ||
modifier = Modifier | ||
.sizeIn(minWidth = 56.dp, minHeight = 56.dp) | ||
.noRippleClickable { onAddMapButtonClick(isAddMap) }, | ||
verticalArrangement = Arrangement.Center, | ||
horizontalAlignment = Alignment.CenterHorizontally | ||
) { | ||
Icon( | ||
imageVector = if (isAddMap) ImageVector.vectorResource(id = R.drawable.ic_add_map_main400_24) else ImageVector.vectorResource(id = R.drawable.ic_add_map_gray400_24), | ||
modifier = Modifier | ||
.size(32.dp), | ||
contentDescription = null, | ||
tint = Color.Unspecified | ||
) | ||
|
||
Spacer(modifier = Modifier.height(4.dp)) | ||
|
||
Text( | ||
text = addMapCount.toString(), | ||
style = SpoonyAndroidTheme.typography.caption1m, | ||
color = SpoonyAndroidTheme.colors.gray800 | ||
) | ||
} | ||
} else { | ||
SpoonyButton( | ||
text = "떠먹기", | ||
style = ButtonStyle.Secondary, | ||
size = ButtonSize.Medium, | ||
onClick = onScoopButtonClick, | ||
modifier = Modifier.weight(1f), | ||
icon = { | ||
Icon( | ||
imageVector = ImageVector.vectorResource(R.drawable.ic_button_spoon_32), | ||
modifier = Modifier.size(32.dp), | ||
contentDescription = null, | ||
tint = Color.Unspecified | ||
) | ||
} | ||
) | ||
} | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.spoony.spoony.presentation.placeDetail | ||
|
||
import com.spoony.spoony.core.state.UiState | ||
import com.spoony.spoony.domain.entity.PostEntity | ||
import com.spoony.spoony.domain.entity.UserEntity | ||
import kotlinx.collections.immutable.ImmutableList | ||
import kotlinx.collections.immutable.persistentListOf | ||
|
||
data class PlaceDetailState( | ||
val postEntity: UiState<PostEntity> = UiState.Loading, | ||
val userEntity: UiState<UserEntity> = UiState.Loading, | ||
val spoonAmountEntity: UiState<Int> = UiState.Loading, | ||
val scoopDialogVisibility: Boolean = false, | ||
val dropDownMenuList: ImmutableList<String> = persistentListOf("신고하기") | ||
) |
20 changes: 20 additions & 0 deletions
20
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.spoony.spoony.presentation.placeDetail | ||
|
||
import androidx.lifecycle.ViewModel | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
@HiltViewModel | ||
class PlaceDetailViewModel @Inject constructor() : ViewModel() { | ||
var _state: MutableStateFlow<PlaceDetailState> = MutableStateFlow(PlaceDetailState()) | ||
val state: StateFlow<PlaceDetailState> | ||
get() = _state | ||
|
||
fun useSpoon() { | ||
} | ||
|
||
fun updateAddMap(isAddMap: Boolean) { | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
p5: state.spoonAmountEntity와 state.userEntity를 처리할 때 UiState를 확인하고 데이터를 추출하는 로직이 중복적으로 사용되는데. 추후에 헬퍼함수로도 추출 해볼수 있을것 같네요~
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.
의견 감사합니다~ 추후에 수정할게요!