Skip to content

feat: 공동 카테고리 함께하는 사람들, 친구 초대 구현 #783 #825

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

Merged
merged 51 commits into from
May 29, 2025

Conversation

s6m1n
Copy link
Member

@s6m1n s6m1n commented May 14, 2025

⭐️ Issue Number

🚩 Summary

🎥 시연 영상

닉네임으로 검색 사용자 선택 및 삭제
1.mp4
default.mp4
  • 공용 컴포저블 구현
  • 함께하는 사람들 RecyclerView 구현
  • 초대 다이얼로그 UI 구현
  • 검색 기능 구현
  • 초대할 멤버 선택 기능 구현

🛠️ Technical Concerns

🙂 To Reviewer

📋 To Do

API 나오면 할 일

  • 공동 카테고리일 때만 함께하는 사람들 노출
  • 방장 UI
  • 초대하기 구현

@s6m1n s6m1n added this to the sprint-13 milestone May 14, 2025
@s6m1n s6m1n requested review from Junyoung-WON and hxeyexn May 14, 2025 14:25
@s6m1n s6m1n self-assigned this May 14, 2025
@s6m1n s6m1n added android We are android>< feat 기능 (새로운 기능) ui XML, Compose 작업 labels May 14, 2025
@s6m1n s6m1n changed the base branch from main to develop May 14, 2025 14:37
@s6m1n s6m1n closed this May 14, 2025
@s6m1n s6m1n reopened this May 14, 2025
Copy link

github-actions bot commented May 14, 2025

Test Results

148 tests   148 ✅  12s ⏱️
 17 suites    0 💤
 17 files      0 ❌

Results for commit ed7392a.

♻️ This comment has been updated with latest results.

@hxeyexn hxeyexn linked an issue May 14, 2025 that may be closed by this pull request
7 tasks
Copy link
Contributor

@hxeyexn hxeyexn left a comment

Choose a reason for hiding this comment

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

초대 기능 구현하시느라 정말 고생 많으셨습니다! 작업량도 꽤 많았을 텐데 테스트까지 꼼꼼하게 챙겨주셔서 인상 깊었습니다 👏

SearchTextField와 관련해서 약간의 사소한 이슈가 있어 코멘트 남겨두었어요~ 기능 자체는 문제 없이 잘 동작하네요! 😊

Comment on lines +9 to +13
class InvitationDefaultRepository
@Inject
constructor(
private val invitationApiService: InvitationApiService,
) : InvitationRepository {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분은 호두가 DataSource를 만들었다고 했던 것 같아서 충돌이 발생할 수도 있겠네요..! 머지 전에 한 번 같이 의논해보면 좋을 것 같아요~

@Junyoung-WON
@s6m1n

Copy link
Member Author

Choose a reason for hiding this comment

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

그렇군요! 저는 Repository 패턴에서 DataSource를 분리하는 이유는 데이터 출처가 여러 개일 수 있기 때문이라고 생각해요.
하지만 초대(Invite) 기능이 로컬 캐시의 필요도가 낮고, 초대는 보통 서버에서만 처리된다고 생각해서 DataSource 없이 바로 ApiService를 주입해주었습니다!

@@ -10,6 +10,8 @@ data class Category(
val endAt: LocalDate? = null,
val description: String? = null,
val color: String,
val mates: List<Member>,
val mates: List<Participant>,
Copy link
Contributor

Choose a reason for hiding this comment

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

matesparticipants로 변경하면 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines 23 to 28
visibility = if (isGone)View.GONE else View.VISIBLE
}

@BindingAdapter("isInvisible")
fun ImageView.setVisibility(isInvisible: Boolean) {
visibility = if (isInvisible)View.INVISIBLE else View.VISIBLE
Copy link
Contributor

Choose a reason for hiding this comment

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

아주 사소하지만 띄어쓰기 수정 부탁드려도 될까요..? 🥹

Suggested change
visibility = if (isGone)View.GONE else View.VISIBLE
}
@BindingAdapter("isInvisible")
fun ImageView.setVisibility(isInvisible: Boolean) {
visibility = if (isInvisible)View.INVISIBLE else View.VISIBLE
visibility = if (isGone) View.GONE else View.VISIBLE
}
@BindingAdapter("isInvisible")
fun ImageView.setVisibility(isInvisible: Boolean) {
visibility = if (isInvisible) View.INVISIBLE else View.VISIBLE

Copy link
Member Author

@s6m1n s6m1n May 28, 2025

Choose a reason for hiding this comment

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

혹시 시력이 4.0이신가요?ㅋㅋㅋ
반영 완료! chore: 띄어쓰기 수정

Comment on lines 85 to 143
@Preview(showBackground = true)
@Composable
fun EmptyInviteDialogPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = emptyMembers,
searchedMembers = emptyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}

@Preview(showBackground = true)
@Composable
fun EmptySelectedMemberPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = emptyMembers,
searchedMembers = dummyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}

@Preview(showBackground = true)
@Composable
fun EmptySearchedMemberPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = dummyMembers,
searchedMembers = emptyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}

@Preview(showBackground = true)
@Composable
fun InviteDialogPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = dummyMembers,
searchedMembers = dummyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Preview도 결국 유지보수의 영역이라고 생각해서, PreviewParameterProvider를 활용해 불필요한 중복 코드를 줄이는 것이 좋을 것 같은 데 빙티 생각은 어떠신가요?

혹시 상황별로 Preview를 따로 만드신 건 Preview 이름을 통해 각 상황을 명시적으로 표현하려는 의도이신가요?

-> InviteStateComposable, InviteTopBar, SearchedMemberItem, SearchedMembersLazyColumn,
SelectedMemberItem, MembersLazyRow, SearchTextField 모두 위와 동일한 의견입니다!

Copy link
Member Author

@s6m1n s6m1n May 28, 2025

Choose a reason for hiding this comment

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

혹시 상황별로 Preview를 따로 만드신 건 Preview 이름을 통해 각 상황을 명시적으로 표현하려는 의도이신가요?

네에 맞습니다!
저는 단순히 값만 바꿔주는 프리뷰 보단, 다양한 상태와 예외적인 상태를 프리뷰로 만드는데요. (e.g. 값이 비어있을 때, 텍스트 길이가 매우 길 때)
이런 경우에는 입력값에 따라 그려지는 UI가 크게 달라지기 때문에 상태 별로 설명이 명확해야한다고 생각해요.
그래서 함수명이나 name 파라미터를 설정하는 것을 선호합니다.
(EmptySearchedMemberPreview(), @Preview(name = "검색 결과가 비어있을 때"))
PreviewParameterProvider는 이런 가독성 측면에서 아쉬운 것 같아요. 무분별하게 ParameterizedTest를 사용하면 가독성이 떨어졌던 것과 비슷한 느낌입니다.
중복 코드와 가독성의 트레이드 오프일 것 같은데, 어떤 상황에서 PreviewParameterProvider를 사용할지 팀 컨벤션으로 정해보면 좋겠네요!
@Junyoung-WON

Copy link
Contributor

@hxeyexn hxeyexn May 29, 2025

Choose a reason for hiding this comment

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

입력값에 따라 그려지는 UI가 크게 달라지기 때문에 상태 별로 설명이 명확해야한다고 생각해요.

저도 빙티 의견에 동의합니다! 다만 InviteDialog가 전달받는 파라미터가 많아 메서드명이 명시적임에도 불구하고 가독성이 다소 아쉽게 느껴졌어요..!

아래와 같이 기본 InviteDialog를 생성하는 별도의 컴포저블 함수를 정의하고, 이를 활용해 Preview를 작성하는 방식은 어떨까요?
이렇게 하면 빙티가 말씀하신 것처럼 상태별 의미를 명확하게 드러낼 수 있고, 중복 코드도 줄일 수 있어 유지보수성과 가독성 측면 모두 개선될 것 같아요! @s6m1n, @Junyoung-WON

또한 Preview에 사용되는 컴포저블들은 해당 파일 내에서만 사용됩니다. 외부에서 접근하지 못하도록 접근 제한자를 private으로 설정하는 건 어떨까요?

@Preview(showBackground = true)
@Composable
private fun EmptyInviteDialogPreview() {
    DefaultInviteDialog(
        selectedMembers = emptyMembers,
        searchedMembers = emptyMembersUiModel,
    )
}

@Preview(showBackground = true)
@Composable
private fun EmptySelectedMemberPreview() {
    DefaultInviteDialog(
        selectedMembers = emptyMembers,
        searchedMembers = dummyMembersUiModel,
    )
}

@Preview(showBackground = true)
@Composable
private fun EmptySearchedMemberPreview() {
    DefaultInviteDialog(
        selectedMembers = dummyMembers,
        searchedMembers = emptyMembersUiModel,
    )
}

@Preview(showBackground = true)
@Composable
private fun InviteDialogPreview() {
    DefaultInviteDialog(
        selectedMembers = dummyMembers,
        searchedMembers = dummyMembersUiModel,
    )
}

// 기본 InviteDialog를 생성
@Composable
private fun DefaultInviteDialog(
    selectedMembers: Members,
    searchedMembers: MembersUiModel,
) {
    InviteDialog(
        searchKeyword = "",
        selectedMembers = selectedMembers,
        searchedMembers = searchedMembers,
        onValueChanged = {},
        onInviteConfirmed = {},
        onClose = {},
        onMemberDeselected = {},
        onMemberSelected = {},
    )
}

Copy link
Contributor

Choose a reason for hiding this comment

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

좋은 의견인 것 같아요! 저도 PreviewParameterProvider를 사용하고 있는데, Preview의 이름을 명확하게 나타낼 수 없어서 조금 아쉬웠습니다. 빙티의 의견에 동의합니다!
하나의 Preview 만으로도 충분히 컴포저블에 대한 설명이 가능하되, Preview에 필요한 데이터의 개수가 n개일 때 사용하면 어떨까요?

Copy link
Contributor

@hxeyexn hxeyexn May 29, 2025

Choose a reason for hiding this comment

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

중복 코드와 가독성의 트레이드 오프일 것 같은데, 어떤 상황에서 PreviewParameterProvider를 사용할지 팀 컨벤션으로 정해보면 좋겠네요!

아래와 같은 방식은 어떨까요? 더 좋은 의견이 있다면 마구마구 남겨주세용! @s6m1n , @Junyoung-WON

1. UI 변경이 많은 뷰(e.g. EmptyView, 가시성 처리 등)

컴포저블의 상태가 바뀌어서 UI가 변경되며, 그 상태가 사용자에게 중요한 정보인 경우

  • PreviewParameterProvider 사용 X
  • 명시적인 함수명을 작성하거나 name 파라미터를 설정
  • 미리보기하고자 하는 컴포저블에 파라미터가 많을 경우, 프리뷰용 기본 컴포저블 함수를 구현해 코드 중복 줄이기
  • Preview와 Preview를 위한 컴포저블은 private으로 선언

2. UI 변경이 적은 뷰(e.g. 글자 수 Ellipsis 처리, 단순한 값 변경 등)

  • PreviewParameterProvider 사용 O
  • Preview와 PreviewParameterProvider는 private으로 선언

Copy link
Contributor

Choose a reason for hiding this comment

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

DefaultInviteDialog 를 사용해서 나타내는 것 너무 좋네요! private으로 외부에서 접근 불가능하게 하면, Preview만을 위한 컴포저블로 코드 중복을 줄일 수 있겠군요!

하지만 Default 라는 접두어가 저희가 공동으로 사용하는 컴포저블이라는 의미를 담고 있어서, 컨벤션에 맞지 않다는 생각도 들고, 혹시나 헷갈릴 수 있지 않을까 우려가 됩니다.
Default를 붙인 네이밍 대신, Preview에서만 사용하는 다이얼로그라는 의미를 담아 PreviewInviteDialog라고 하는 것은 어떤가요?

Copy link
Contributor

Choose a reason for hiding this comment

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

Preview에서만 사용하는 다이얼로그라는 의미를 담아 PreviewInviteDialog라고 하는 것은 어떤가요?

좋습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

오옹 너무 좋은 아이디어인 것 같아요~ 반영했습니다!
chore: InviteDialog preview 중복 코드 개선

Copy link
Contributor

Choose a reason for hiding this comment

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

해나가 제안한 기준에 동의합니다!! 깔끔하게 잘 정리해주셨군요!
추가로 제 생각을 덧붙여보자면, 1번과 같은 상황은 "컴포저블의 상태가 바뀌어서 UI가 변경되며, 그 상태가 사용자에게 중요한 정보인 경우"라고 생각하는 것이 좋을 것 같아요.
빙티가 Preview를 여러 개로 구분한 것도 컴포저블에서 나타내는 상태가 중요하다 생각했기 때문이니, "상태"도 그 기준에 포함시키면 좋을 것 같습니다!

Comment on lines 28 to 29
@Composable
fun StaccatoSearchTextField(
Copy link
Contributor

Choose a reason for hiding this comment

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

Staccato라는 Prefix를 붙이신 이유가 있으신가용? 이름만 보면 스타카토를 검색하는 TextField처럼 느껴져서요.
개인적으로는 SearchTextField 정도로도 충분히 명확하지 않을까 싶어요!

Comment on lines 30 to 32
value: String,
modifier: Modifier = Modifier,
placeholderText: String = "",
Copy link
Contributor

Choose a reason for hiding this comment

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

제 기기에서 테스트해보니 placeHolderText와 value의 글자 아랫부분이 살짝 잘리는 현상이 있네요..!

placeHolderText 글자 잘림

image

value 글자 잘림

image

Copy link
Member Author

Choose a reason for hiding this comment

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

Material3 TextField에 기본 설정된 padding이 있어서, 이를 제거하기 위해 BasicTextField로 변경해주었습니다!
ui: Material3 TextField에서 BasicTextField로 변경

Comment on lines 28 to 34
items(members) { item ->
SearchedMemberItem(
item = item,
onSelect = onSelect,
onDeselect = onDeselect,
modifier = Modifier.animateItem(),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Android 공식문서 animateItem() 에 따르면 애니메이션을 활성화하려면 key를 제공해야 한다고 명시되어 있습니다! key 파라미터를 명시해 주면 좋을 것 같아요~

This modifier animates the item appearance (fade in), disappearance (fade out) and placement changes (such as an item reordering).

You should also provide a key via LazyListScope.item/LazyListScope.items for this modifier to enable animations.

Copy link
Member Author

@s6m1n s6m1n May 28, 2025

Choose a reason for hiding this comment

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

오옹 감사합니다 해나 key를 제공해야 적용되는군요?.?
해당 부분은 상의 후 애니메이션을 제거하기로 해서, 그에 맞게 수정해두었습니다!
ui: 사용자 검색 LazyColumn 애니메이션 제거

category.value?.id?.let { categoryId ->
viewModelScope.launch {
invitationRepository.invite(categoryId, ids).onSuccess {
_errorMessage.setValue("${ids.size}명을 초대했어요!") // stringRes로 빼기
Copy link
Contributor

Choose a reason for hiding this comment

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

errorMessage에 초대 완료 문구를 설정하신 이유가 있으신가용? 🤔

그리고 키보드를 내리지 않은 상태로 친구 초대를 하면 초대 완료 토스트 메시지가 화면 중간에 뜨는 이슈가 있습니다. 크게 문제 될 것 같지는 않지만 혹시나 해서 알려 드립니다!

Screen_Recording_20250528_034329_Staccato-DEV.mp4

Copy link
Member Author

Choose a reason for hiding this comment

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

errorMessage에 초대 완료 문구를 설정하신 이유가 있으신가용?

초대 성공 시 다이얼로그가 닫히면서 초대 전과 동일한 카테고리 화면이 보여지기 때문에, 사용자가 초대가 정상적으로 처리되었는지 혼란을 느낄 수 있다고 판단했습니다! 이에 따라 초대 완료 여부를 명확하게 전달하고 시각적 피드백을 제공하기 위해 토스트 메시지를 띄워주었습니다.

비슷한 이유로, 지금은 카테고리 삭제 성공 시 "카테고리를 삭제했습니다."라는 토스트 메시지가 뜨고 있는데요.
삭제 이후 곧바로 타임라인 화면으로 전환하고 해당 카테고리가 사라지는 모습이 노출되기 때문에, 별도의 메시지가 없어도 사용자 입장에서 삭제 결과를 충분히 인지할 수 있다고 판단했습니다. 따라서 이 경우에는 토스트 메시지가 오히려 불필요하다고 생각됩니다!

Copy link
Contributor

@hxeyexn hxeyexn May 28, 2025

Choose a reason for hiding this comment

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

@s6m1n

앗 제가 질문을 조금 모호하게 드렸던 것 같네요. 초대 완료 토스트 메시지를 띄우는 건 저도 좋은 방향이라고 생각합니다!
다만, 초대 완료는 성공에 해당하는 작업인데 errorMessage에 해당 내용을 설정해두신 부분이 살짝 어색하게 느껴져서 질문드렸어요 :)

Copy link
Member Author

@s6m1n s6m1n May 28, 2025

Choose a reason for hiding this comment

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

아하! 그 부분은 저도 동의합니다 ㅋㅋㅋ 토스트 메시지의 처리를 위한 프로퍼티인데, 같은 용도의 SingleLiveData와 백킹프로퍼티를 또 만드는 것은 중복 코드라고 생각했어요.
errorMessage 대신 toastMessage로 이름을 바꾸었습니다!

Comment on lines 64 to 78
Text(
text = item.member.nickname,
style = Title3,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
modifier =
Modifier
.align(Alignment.CenterVertically),
)
Spacer(modifier = Modifier.weight(1f))
InviteStateComposable(
item = item,
onSelect = onSelect,
onDeselect = onDeselect,
)
Copy link
Contributor

@hxeyexn hxeyexn May 27, 2025

Choose a reason for hiding this comment

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

닉네임이 차지하는 너비가 넓어질 경우 버튼 위치가 밀리는 현상이 있어요. 현재 닉네임 글자 수는 최대 10자로 제한되어 큰 문제가 발생하지는 않지만, 화면 크기가 작거나 글씨 크기가 클 경우를 고려해 레이아웃을 조정해두는 것이 좋을 것 같습니다!

image

Copy link
Member Author

Choose a reason for hiding this comment

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

@hxeyexn
Copy link
Contributor

hxeyexn commented May 29, 2025

Screen_Recording_20250529_154621_Staccato-DEV.mp4

네트워크가 불안정할 때 초대 확인 버튼을 누르면, 재시도 요청 스낵바가 키보드 및 다이얼로그에 가려지는 문제가 있습니다..!
요청 시간이 길어지는 상황에서 충분히 발생할 수 있는 이슈인 것 같아 공유드립니다~

Copy link
Contributor

@Junyoung-WON Junyoung-WON left a comment

Choose a reason for hiding this comment

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

야호~~ 빙티🧋 정말 고생 많으셨습니다!! 빠르고 예쁘게, 그리고 큰 문제없이 잘 구현해주신 것 같아요! 🙌
검색 디바운스 등등 사소한 디테일도 챙겨주셔서 더욱 완성도가 있는 것 같습니다 👍

해나가 앞서서 아주 자세하게 리뷰를 해주셔서, 저는 몇 가지 궁금한 부분에 대한 질문과 가볍게 수정하면 좋을 부분만 코멘트 남겨놨습니다! 천천히 확인해주시면 감사하겠습니다 🙇‍♂️
빙티 덕분에 이제 친구를 초대할 수 있게 되었네요~~ 공동 카테고리 기능 폭풍 머지 가봅시다💀

Comment on lines +9 to +15
@Composable
fun Modifier.clickableWithoutRipple(onClick: () -> Unit): Modifier =
this.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = onClick,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Modifier의 확장함수로 깔끔하게 편리하게 만들어주셨네요! 👍
저도 리플 애니메이션 때문에 이것저것 메서드를 달아놨었는데, 빙티의 코드로 한줄로 깔끔하게 리플 애니메이션을 없앨 수 있게 되었어요 🥳
확장함수로 구현해주셨기에 this는 제거해도 좋을 것 같아요!

Comment on lines +16 to +25
@GET(MEMBERS_SEARCH_PATH)
suspend fun getMembersBy(
@Query(NICKNAME) nickname: String,
): ApiResult<MemberSearchResponse>

companion object {
private const val MEMBERS_PATH = "/members"
private const val RECOVERY_CODE = "code"
private const val NICKNAME = "nickname"
private const val MEMBERS_SEARCH_PATH = "$MEMBERS_PATH/search"
Copy link
Contributor

Choose a reason for hiding this comment

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

API Path를 동반객체의 문자열 상수로 깔끔하게 잘 표현해주셨네요! 👍

package com.on.staccato.domain.model

data class Members(val members: List<Member>) {
fun addFirst(member: Member): Members = Members((listOf(member) + members).distinct())
Copy link
Contributor

Choose a reason for hiding this comment

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

distinct 메서드로 중복을 제거해주셨네요!
중복 제거를 보니 Set을 사용하는 방법도 생각났지만, 사용자들을 정렬해야할 필요성도 있다고 한다면 지금 방식이 적절하겠네요!

import android.widget.ImageButton
import android.widget.ImageView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat.getColor
import androidx.core.view.isGone
Copy link
Contributor

Choose a reason for hiding this comment

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

앗 isGone이 사용되지 않고 있어요!
그런데 ktlint는 어떻게 통과한거지?? 🤔 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 isGone을 사용하지 않으신 이유가 따로 있으신가요? (순수 궁금!)

Copy link
Member Author

Choose a reason for hiding this comment

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

오잉 린트가 잠깐 졸았나... 이유는 따로 없습니다! 반영 완!

import androidx.databinding.BindingAdapter
import coil.load
import coil.transform.RoundedCornersTransformation
import com.on.staccato.R
import com.on.staccato.presentation.timeline.model.FilterType
import com.on.staccato.presentation.util.dpToPx

@BindingAdapter("isGone")
fun ImageView.setIsGone(isGone: Boolean) {
visibility = if (isGone) View.GONE else View.VISIBLE
Copy link
Contributor

Choose a reason for hiding this comment

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

빙티가 import 해주신 View의 isGone은 내부코드가 아래처럼 되어있어요!
image

그래서 isGone을 활용한다면 이렇게 둘 수도 있을 것 같습니다.

Suggested change
visibility = if (isGone) View.GONE else View.VISIBLE
this.isGone = isGone

다만 매개변수와 이름이 동일해서 this 키워드를 붙여야하니 읽기가 조금 불편하네요. 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

에구 왜 이걸 생각하지 못했을까요... 감사합니다! 반영했어요~
refactor: isGone 사용

onMemberSelected: (Member) -> Unit,
onMemberDeselected: (Member) -> Unit,
) {
Dialog(
Copy link
Contributor

Choose a reason for hiding this comment

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

오호 빙티는 Dialog를 사용해주셨군요! 👍
Dialog도 여러 종류가 있던데, Dialog를 사용하신 이유가 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

AlertDialog는 Material Design 표준 스타일을 따르기 때문에 디자인 제약이 있어서 컴포즈의 기본 Dialog를 사용했습니다!
그리고 컴포즈의 기본 Dialog는 시스템 다이얼로그라 dismiss, 키보드, 접근성 등의 기능을 자동으로 처리해주어 편리하더라구요~

Comment on lines 85 to 143
@Preview(showBackground = true)
@Composable
fun EmptyInviteDialogPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = emptyMembers,
searchedMembers = emptyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}

@Preview(showBackground = true)
@Composable
fun EmptySelectedMemberPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = emptyMembers,
searchedMembers = dummyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}

@Preview(showBackground = true)
@Composable
fun EmptySearchedMemberPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = dummyMembers,
searchedMembers = emptyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}

@Preview(showBackground = true)
@Composable
fun InviteDialogPreview() {
InviteDialog(
searchKeyword = "",
selectedMembers = dummyMembers,
searchedMembers = dummyMembersUiModel,
onValueChanged = {},
onInviteConfirmed = {},
onClose = {},
onMemberDeselected = {},
onMemberSelected = {},
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

좋은 의견인 것 같아요! 저도 PreviewParameterProvider를 사용하고 있는데, Preview의 이름을 명확하게 나타낼 수 없어서 조금 아쉬웠습니다. 빙티의 의견에 동의합니다!
하나의 Preview 만으로도 충분히 컴포저블에 대한 설명이 가능하되, Preview에 필요한 데이터의 개수가 n개일 때 사용하면 어떨까요?

Comment on lines +79 to +82
Modifier,
{},
{},
0,
Copy link
Contributor

Choose a reason for hiding this comment

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

아아아아주 사소하지만! Preview에서도 parameter name을 명시하면 좋을 것 같아요.
어떤 속성을 넘겨주어야 해당 Preview처럼 나타나는지 다른 개발자가 좀 더 잘 파악할 수 있을 것 같습니다!

Comment on lines +58 to +59
private var _isInviteMode = MutableStateFlow(false)
val isInviteMode: StateFlow<Boolean> get() = _isInviteMode
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 isInviteMode로 검색 Dialog를 띄우도록 분기 처리한 이유가 무엇인가요? (순수 궁금!2)

Copy link
Member Author

Choose a reason for hiding this comment

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

View.visibility = true 이런 식으로 구현했을 수도 있겠지만... 선언형 UI라는 컴포즈의 특징을 몸소 느껴보고 싶었습니다...ㅋㅋㅋ


@Preview
@Composable
private fun ImageComponentPreview() {
Copy link
Contributor

Choose a reason for hiding this comment

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

여기의 Preview 이름은 바뀌지 않았네요!
Preview라서 굳이 안바꿔도 상관은 없지만, 나중에 검색해서 찾거나 할 때 헷갈릴 수도 있을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@Junyoung-WON Junyoung-WON left a comment

Choose a reason for hiding this comment

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

빠르게 피드백도 반영해주셨네요!! 정말 고생하셨습니다! 🥳
바로 승인하겠습니다!!

@s6m1n s6m1n merged commit 24661d6 into develop May 29, 2025
7 checks passed
@s6m1n s6m1n deleted the feature/#783-mate-invite branch May 29, 2025 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
android We are android>< feat 기능 (새로운 기능) ui XML, Compose 작업
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

feat: 공동 카테고리 함께하는 사람들, 친구 초대 구현
3 participants