Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
275bd76
feat/#68: 삭제 옵션을 위한 AlertDialog 버튼 색상 커스텀 옵션 추가
DongChyeon Mar 18, 2026
6b6df5b
feat/#68: 홈 화면 피드 삭제 시 다이얼로그 노출
DongChyeon Mar 18, 2026
90e033b
feat/#68: 알림 상세 화면 피드 삭제 시 다이얼로그 노출
DongChyeon Mar 18, 2026
aa8a0a8
feat/#68: 본인 피드가 아닐 시 팝업에 차단하기 메뉴 추가
DongChyeon Mar 18, 2026
10a733a
feat/#68: 차단하기 API 연동
DongChyeon Mar 18, 2026
feee357
feat/#68: 차단된 계정 메뉴 추가
DongChyeon Mar 18, 2026
a6ead5d
feat/#68: 차단된 유저 컴포넌트 추가
DongChyeon Mar 18, 2026
c90b31c
chore/#68: ktlint 포맷팅
DongChyeon Mar 18, 2026
ec70e8b
feat/#68: 차단된 유저가 없을 때의 화면 처리
DongChyeon Mar 18, 2026
6334f67
feat/#68: 차단된 유저 목록 API 연동
DongChyeon Mar 18, 2026
f3b8070
feat/#68: 차단 유저 텍스트 크기, 차단 해제 버튼 크기 수정
DongChyeon Mar 18, 2026
810e477
feat/#68: 차단 해제 API 연동
DongChyeon Mar 18, 2026
226dbf4
chore/#68: 삭제 완료 스낵바 아이콘 제거
DongChyeon Mar 18, 2026
b381046
chore/#68: 투표 등록 완료 시 업로드 화면 백스택 제거
DongChyeon Mar 18, 2026
507d5c9
fix/#68: API가 실패하더라도 Dialog가 닫히도록 수정
DongChyeon Mar 18, 2026
6c6f86a
chore/#68: authUserId, isOwner 기본값 없이 강제화
DongChyeon Mar 18, 2026
b5ef34b
fix/#68: 비회원일 때 투표 피드에서 더보기 미노출
DongChyeon Mar 18, 2026
edcab1d
feat/#68: 차단된 계정 화면 재차단 기능 구현
DongChyeon Mar 19, 2026
d912619
chore/#68: 닉네임이 null이면 early return
DongChyeon Mar 19, 2026
c32e739
refactor/#68: 차단하기/해제 버튼 Chip 컴포넌트로 대체
DongChyeon Mar 19, 2026
a0081b6
chore/#68: ktlint 포맷팅
DongChyeon Mar 19, 2026
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
Expand Up @@ -27,6 +27,7 @@ import com.sseotdabwa.buyornot.feature.mypage.navigation.navigateToMyPage
import com.sseotdabwa.buyornot.feature.notification.navigation.navigateToNotification
import com.sseotdabwa.buyornot.feature.notification.navigation.navigateToNotificationDetail
import com.sseotdabwa.buyornot.feature.notification.navigation.notificationGraph
import com.sseotdabwa.buyornot.feature.upload.navigation.UPLOAD_ROUTE
import com.sseotdabwa.buyornot.feature.upload.navigation.navigateToUpload
import com.sseotdabwa.buyornot.feature.upload.navigation.uploadScreen

Expand Down Expand Up @@ -106,11 +107,10 @@ fun BuyOrNotNavHost(
tab = HomeTab.MY_FEED,
navOptions =
androidx.navigation.navOptions {
popUpTo(navController.graph.startDestinationId) {
saveState = false
popUpTo(UPLOAD_ROUTE) {
inclusive = true
}
launchSingleTop = true
restoreState = false
},
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import com.sseotdabwa.buyornot.core.network.api.UserApiService
import com.sseotdabwa.buyornot.core.network.dto.request.FcmTokenRequest
import com.sseotdabwa.buyornot.core.network.dto.response.User
import com.sseotdabwa.buyornot.core.network.dto.response.getOrThrow
import com.sseotdabwa.buyornot.domain.model.BlockedUser
import com.sseotdabwa.buyornot.domain.model.UserProfile
import com.sseotdabwa.buyornot.domain.repository.UserRepository
import javax.inject.Inject
import com.sseotdabwa.buyornot.core.network.dto.response.BlockedUser as BlockedUserResponse

class UserRepositoryImpl @Inject constructor(
private val userApiService: UserApiService,
Expand All @@ -21,6 +23,16 @@ class UserRepositoryImpl @Inject constructor(
userApiService.updateFcmToken(FcmTokenRequest(fcmToken)).getOrThrow()
}

override suspend fun getBlockedUsers(): List<BlockedUser> = userApiService.getBlockedUsers().getOrThrow().map { it.toDomain() }

override suspend fun blockUser(userId: Long) {
userApiService.blockUser(userId).getOrThrow()
}

override suspend fun unblockUser(userId: Long) {
userApiService.unblockUser(userId).getOrThrow()
}

private fun User.toDomain(): UserProfile =
UserProfile(
id = id,
Expand All @@ -29,4 +41,11 @@ class UserRepositoryImpl @Inject constructor(
socialAccount = socialAccount,
email = email,
)

private fun BlockedUserResponse.toDomain(): BlockedUser =
BlockedUser(
userId = userId,
nickname = nickname,
profileImage = profileImage,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fun BuyOrNotAlertDialog(
onConfirm: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
confirmButtonColors: BuyOrNotButtonColors = BuyOrNotButtonDefaults.primaryButtonColors(),
) {
Dialog(
onDismissRequest = onDismissRequest,
Expand Down Expand Up @@ -80,13 +81,38 @@ fun BuyOrNotAlertDialog(
text = confirmText,
onClick = onConfirm,
modifier = Modifier.weight(1f),
colors = confirmButtonColors,
)
}
}
}
}
}

@Preview(showBackground = true)
@Composable
private fun BuyOrNotDestructiveAlertDialogPreview() {
BuyOrNotTheme {
Box(
modifier =
Modifier
.background(BuyOrNotTheme.colors.gray1000)
.padding(10.dp),
) {
BuyOrNotAlertDialog(
onDismissRequest = { /* Handle dismiss */ },
title = "피드를 삭제할까요?",
subText = "삭제된 피드는 복구할 수 없어요.",
confirmText = "삭제",
dismissText = "취소",
onConfirm = { /* Handle confirm */ },
onDismiss = { /* Handle dismiss */ },
confirmButtonColors = BuyOrNotButtonDefaults.destructiveButtonColors(),
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun BuyOrNotAlertDialogPreview() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ object BuyOrNotButtonDefaults {
)
}

@Composable
fun destructiveButtonColors() =
BuyOrNotButtonColors(
defaultContainer = BuyOrNotTheme.colors.red100,
hoverContainer = BuyOrNotTheme.colors.red100,
pressedContainer = BuyOrNotTheme.colors.red100,
disabledContainer = BuyOrNotTheme.colors.gray200,
content = BuyOrNotTheme.colors.gray0,
disabledContent = BuyOrNotTheme.colors.gray600,
)

@Composable
fun secondaryOutlinedButtonColors() =
BuyOrNotButtonColors(
Expand Down Expand Up @@ -142,6 +153,7 @@ fun PrimaryButton(
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
size: ButtonSize = ButtonSize.Large,
colors: BuyOrNotButtonColors = BuyOrNotButtonDefaults.primaryButtonColors(),
onClick: () -> Unit,
) {
val height =
Expand All @@ -160,7 +172,7 @@ fun PrimaryButton(
enabled = enabled,
height = height,
shape = RoundedCornerShape(cornerRadius),
buttonColors = BuyOrNotButtonDefaults.primaryButtonColors(),
buttonColors = colors,
contentPadding = PaddingValues(horizontal = 24.dp), // 시안에 맞게 패딩 조절
interactionSource = interactionSource,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ fun FeedCard(
voterProfileImageUrl: String = "", // 사용자가 투표한 경우의 프로필 이미지 URL
onDeleteClick: () -> Unit = {}, // 삭제 클릭 콜백 추가
onReportClick: () -> Unit = {}, // 신고 클릭 콜백 추가
onBlockClick: () -> Unit = {}, // 차단 클릭 콜백 추가
showMoreButton: Boolean = true,
) {
val hasVoted = userVotedOptionIndex != null
val buyPercentage = if (totalVoteCount > 0) (buyVoteCount * 100 / totalVoteCount) else 0
Expand Down Expand Up @@ -139,25 +141,41 @@ fun FeedCard(
)
}
}
Box {
Icon(
imageVector = BuyOrNotIcons.More.asImageVector(),
contentDescription = "More",
modifier =
Modifier
.size(20.dp)
.clickable { showMenu = true },
tint = BuyOrNotTheme.colors.gray500,
)
if (showMenu) {
FeedActionPopup(
label = if (isOwner) "삭제하기" else "신고하기",
onDismiss = { showMenu = false },
onClick = {
showMenu = false
if (isOwner) onDeleteClick() else onReportClick()
},
if (showMoreButton) {
Box {
Icon(
imageVector = BuyOrNotIcons.More.asImageVector(),
contentDescription = "More",
modifier =
Modifier
.size(20.dp)
.clickable { showMenu = true },
tint = BuyOrNotTheme.colors.gray500,
)
val ownerMenuItems =
listOf(
"삭제하기" to {
showMenu = false
onDeleteClick()
},
)
val userMenuItems =
listOf(
"신고하기" to {
showMenu = false
onReportClick()
},
"차단하기" to {
showMenu = false
onBlockClick()
},
)
if (showMenu) {
FeedActionPopup(
items = if (isOwner) ownerMenuItems else userMenuItems,
onDismiss = { showMenu = false },
)
}
}
}
}
Expand Down Expand Up @@ -427,9 +445,8 @@ private fun VoteOption(
*/
@Composable
private fun FeedActionPopup(
label: String,
items: List<Pair<String, () -> Unit>>,
onDismiss: () -> Unit,
onClick: () -> Unit,
) {
val density = LocalDensity.current
val navHeight = 20.dp // Anchor icon height
Expand All @@ -451,8 +468,7 @@ private fun FeedActionPopup(
properties = PopupProperties(focusable = true),
) {
FeedActionPopupContent(
label = label,
onClick = onClick,
items = items,
tonalElevation = 8.dp,
shadowElevation = 8.dp,
)
Expand All @@ -464,8 +480,7 @@ private fun FeedActionPopup(
*/
@Composable
private fun FeedActionPopupContent(
label: String,
onClick: () -> Unit,
items: List<Pair<String, () -> Unit>>,
modifier: Modifier = Modifier,
tonalElevation: Dp = 0.dp,
shadowElevation: Dp = 0.dp,
Expand All @@ -482,22 +497,21 @@ private fun FeedActionPopupContent(
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
) {
Box(
modifier = Modifier.padding(vertical = 14.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = label,
modifier =
Modifier
.clickable { onClick() }
.padding(
horizontal = 20.dp,
vertical = 8.dp,
),
style = BuyOrNotTheme.typography.bodyB3Medium,
color = BuyOrNotTheme.colors.gray800,
)
Column(modifier = Modifier.padding(vertical = 14.dp)) {
items.forEach { (label, onClick) ->
Text(
text = label,
modifier =
Modifier
.clickable { onClick() }
.padding(
horizontal = 20.dp,
vertical = 8.dp,
),
style = BuyOrNotTheme.typography.bodyB3Medium,
color = BuyOrNotTheme.colors.gray800,
)
}
}
}
}
Expand Down Expand Up @@ -569,8 +583,7 @@ private fun FeedCardSquareInteractivePreview() {
private fun FeedActionPopupContentOwnerPreview() {
BuyOrNotTheme {
FeedActionPopupContent(
label = "삭제하기",
onClick = { /* do nothing */ },
items = listOf("삭제하기" to {}),
)
}
}
Expand All @@ -584,8 +597,7 @@ private fun FeedActionPopupContentOwnerPreview() {
private fun FeedActionPopupContentUserPreview() {
BuyOrNotTheme {
FeedActionPopupContent(
label = "신고하기",
onClick = { /* do nothing */ },
items = listOf("신고하기" to {}, "차단하기" to {}),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ object BuyOrNotImgs {
val Error = ImgsResource(R.drawable.img_error)

val NoNotification = ImgsResource(R.drawable.img_no_bell)

val NoBlockedUser = ImgsResource(R.drawable.img_blocked_user_empty)
}

/**
Expand Down
Loading
Loading