Skip to content
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 49 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
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 Jan 16, 2025
b648ddc
Merge branch 'develop' into feat/#9-place-detail
Roel4990 Jan 16, 2025
610b812
[Feat/#9] PlaceDetailScreen StoreInfo 추가
Roel4990 Jan 16, 2025
61bc271
Merge branch 'develop' into feat/#9-place-detail
Roel4990 Jan 16, 2025
1e703e4
Merge branch 'develop' into feat/#9-place-detail
Roel4990 Jan 16, 2025
855d556
[Feat/#9] 장소 상세 페이지 PlaceDetailBottomBar 및 PlaceDetailImageLazyRow 추가
Roel4990 Jan 16, 2025
60e5f4e
Merge branch 'develop' into feat/#9-place-detail
Roel4990 Jan 16, 2025
49b48a9
[FEAT/#9] add map icon 수정
Roel4990 Jan 16, 2025
fa8eaca
Merge branch 'refs/heads/develop' into feat/#9-place-detail
Roel4990 Jan 16, 2025
e81f332
[FEAT/#9] 장소 상세 navigation 구현
Roel4990 Jan 16, 2025
6793b12
[FEAT/#9] 더미데이터 Preview 로 이동
Roel4990 Jan 16, 2025
f715978
Merge branch 'develop' into feat/#9-place-detail
Roel4990 Jan 16, 2025
58e8b3f
[FEAT/#9] PlaceDetail 페이지 ViewModel, State 추가 및 IconTagEntity 추가
Roel4990 Jan 17, 2025
53e9c3e
[FEAT/#9] ktLintFormat 처리
Roel4990 Jan 17, 2025
a3232cf
[FEAT/#9] IconTagEntity => IconTagItem 변경 및 state.iconTag 검증 처리 코드 수정
Roel4990 Jan 17, 2025
88617c1
[FEAT/#9] rememberScrollState() 변수화
Roel4990 Jan 17, 2025
0870948
[FEAT/#9] MutableStateFlow 형태 변경
Roel4990 Jan 17, 2025
87f4aac
[FEAT/#9] MainTabRoute -> Route 로 수정
Roel4990 Jan 17, 2025
2aa239c
[FEAT/#9] MainTabRoute -> Route 로 수정
Roel4990 Jan 17, 2025
c8049f6
[FEAT/#9] Column 사용 후 padding 값 제거
Roel4990 Jan 17, 2025
bff3c13
[FEAT/#9] Column 으로 묶어서 공통 패딩 적용하기
Roel4990 Jan 17, 2025
74181d1
[FEAT/#9] 하단 패딩 수정 및 구조 변경
Roel4990 Jan 17, 2025
b90ef50
[FEAT/#9] IconTagItem => IconTagEntity Entity 이름 수정
Roel4990 Jan 17, 2025
0dfd66e
[FEAT/#9] dropdownMenuList 값 Screen 내부에서 관리
Roel4990 Jan 18, 2025
c042f17
Merge branch 'develop' into feat/#9-place-detail
Roel4990 Jan 18, 2025
e867254
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 19, 2025
622c89f
[FEAT/#9] UserEntity 추가
Roel4990 Jan 19, 2025
aefa10b
[FEAT/#9] PostEntity 추가
Roel4990 Jan 19, 2025
d439e70
[FEAT/#9] StoreInfo menuItems -> menuList 수정
Roel4990 Jan 19, 2025
834d6c7
[FEAT/#9] PlaceDetail state, viewModel 수정
Roel4990 Jan 19, 2025
ca16f03
[FEAT/#9] PlaceDetailRoute spoonAmount, userProfile 추가하기
Roel4990 Jan 19, 2025
d644195
[FEAT/#9] dropDownMenuList 유저 정보에 따른 드롭다운 메뉴 리스트 변경을 고려해서 state 에서 관리…
Roel4990 Jan 19, 2025
3cac6c2
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 19, 2025
3ba3653
[FEAT/#9] IconTagEntity 기본값 제거
Roel4990 Jan 19, 2025
cbb6f57
[FEAT/#9] 수정 사항 코드 수정
Roel4990 Jan 19, 2025
e38c436
[FEAT/#9] ktLintFormat 처리
Roel4990 Jan 19, 2025
8fd06f7
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 20, 2025
305377b
[FEAT/#9] PlaceDetailBottomBar Modifier 값 제거
Roel4990 Jan 20, 2025
4b8be80
[FEAT/#9] Scaffold 에서 paddingValues 받아오기
Roel4990 Jan 20, 2025
d62244b
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 20, 2025
4332446
[FEAT/#9] IconTagEntity -> CategoryEntity 로 수정 및 toValidHexColor 함수 추가
Roel4990 Jan 20, 2025
5d663d3
[FEAT/#9] onScoopButtonClick 람다 -> 함수 참조로 수정
Roel4990 Jan 20, 2025
83aa944
[FEAT/#9] persistentListOf -> immutableListOf 로 수정
Roel4990 Jan 20, 2025
7a7be58
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 20, 2025
632f6ef
[FEAT/#9] isScooped default 값 삭제
Roel4990 Jan 20, 2025
0523a45
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 20, 2025
a4e8d49
[FEAT/#9] 코드리뷰 사항 수정
Roel4990 Jan 20, 2025
bb8ab0c
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 20, 2025
6c2d69d
Merge remote-tracking branch 'origin/develop' into feat/#9-place-detail
Roel4990 Jan 20, 2025
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,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 app/src/main/java/com/spoony/spoony/domain/entity/PostEntity.kt
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
)
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
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.navigation.compose.NavHost
import com.spoony.spoony.presentation.explore.navigation.exploreNavGraph
import com.spoony.spoony.presentation.main.component.MainBottomBar
import com.spoony.spoony.presentation.map.navigaion.mapNavGraph
import com.spoony.spoony.presentation.placeDetail.navigation.placeDetailNavGraph
import com.spoony.spoony.presentation.register.navigation.registerNavGraph
import com.spoony.spoony.presentation.report.navigation.reportNavGraph
import kotlinx.collections.immutable.toPersistentList
Expand Down Expand Up @@ -50,6 +51,10 @@ fun MainScreen(
paddingValues = innerPadding
)

placeDetailNavGraph(
paddingValues = innerPadding
)

reportNavGraph()
}
}
Expand Down
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 = ""
)
}
Comment on lines +60 to +72
Copy link
Collaborator

@angryPodo angryPodo Jan 20, 2025

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를 확인하고 데이터를 추출하는 로직이 중복적으로 사용되는데. 추후에 헬퍼함수로도 추출 해볼수 있을것 같네요~

Copy link
Member Author

Choose a reason for hiding this comment

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

의견 감사합니다~ 추후에 수정할게요!


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
)
}
)
}
}
}
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("신고하기")
)
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) {
}
}
Loading
Loading