Skip to content

[Koin Project] 식단 화면 compose 로 구현#993

Merged
KYM-P merged 62 commits intodevelopfrom
refactor/#992-dining-compose
Sep 18, 2025
Merged

[Koin Project] 식단 화면 compose 로 구현#993
KYM-P merged 62 commits intodevelopfrom
refactor/#992-dining-compose

Conversation

@KYM-P
Copy link
Copy Markdown
Collaborator

@KYM-P KYM-P commented Aug 19, 2025

PR 개요

이슈 번호: #992

  • 식단 화면 compose 로 구현

PR 체크리스트

  • Code convention을 잘 지켰나요?
  • Lint check를 수행하였나요?
  • Assignees를 추가했나요?

작업사항

  • 버그 수정
  • 신규 기능
  • 코드 스타일 수정 (포맷팅 등)
  • 리팩토링 (기능 수정 X, API 수정 X)
  • 기타

작업사항의 상세한 설명

  • 기존 식단 화면을 compose 로 재구성
    (위젯 진입, 카카오톡 공유 발송,진입 전부 구현 완, AB 테스트 구현 완)
  • scroll 이 이전 club 방식을 사용중 -> 새로운 방식으로 변화시킬 예정
  • 기존 dining ui는 따로 제거해 PR을 올릴 예정
  • 위젯은 그대로 옮길 예정

스크린샷

화면A(old) 화면B(new)
첫화면 bottomsheet 첫 화면 gif

추가

내용

  • develop, sprint 브랜치를 향하고 있습니다
  • production 브랜치를 향하고 있습니다

KYM-P added 6 commits August 12, 2025 17:06
feature: breakfast Dining items
feature: can scrollable
feature: dining compose
fix: fix to strings.xml
feature: dining bottom sheet
fix: feat ktlint
@KYM-P KYM-P requested a review from a team as a code owner August 19, 2025 05:00
…o refactor/dining-compose

# Conflicts:
#	core/navigation/src/main/java/in/koreatech/koin/core/navigation/NavigatorType.kt
#	koin/src/main/java/in/koreatech/koin/navigation/NavigatorImpl.kt
#	koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt
@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Aug 19, 2025

현재 Navigation 이 바뀌면서 기존 NotificationActivity 로 이동하는 방식이 달라져야합니다.
다만 현재 SchemeType 을 주려면 Koin 의존성이 생겨버립니다. 회의 후 결과로 수정하겠습니다.

@KYM-P KYM-P self-assigned this Aug 19, 2025
Copy link
Copy Markdown
Member

@kongwoojin kongwoojin left a comment

Choose a reason for hiding this comment

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

고생하셨습니다.
추가로 비로그인 상태에서 식단 진입 시 /notification API를 호출해 인증 정보 만료가 뜨는데, 비로그인 상태 분기처리가 필요할 것 같습니다.
추가로 nestedScroll 적용과 conflict 수정도 해주세요

@@ -0,0 +1,116 @@
package `in`.koreatech.koin.core.designsystem.component.switch
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

designsystem으로 빼는게 원래는 맞겠지만... 디자인 팀에서 제공한 디자인이 아니기도 하고 디자인 시스템 리팩토링이 진행중이라 일단은 feature/dining 모듈에 넣는게 어떨까 싶습니다.

type: Pair<String, Any?> = Pair("", "")
): Intent

fun navigateToNotification(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

navigateToNotificationSetting이 좀 더 맞을 것 같습니다.
혹은 navigateToSetting으로 만들고 enum 하나 만들어서 세부 화면을 관리해도 될 것 같네요

Comment thread feature/dining/build.gradle.kts Outdated
implementation(libs.coil.gif)

implementation(libs.timber)
implementation(project(":core:onboarding"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

위로 옮겨주세요

modifier: Modifier = Modifier,
onClick: (Date) -> Unit = {}
) {
val currentDate = TimeUtil.getCurrentTime()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

전부 remember 처리 해주세요

@Composable
fun DiningItem(
dining: Dining,
context: Context,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

context를 넘기지 않고 LocalContext.current를 사용해도 되지 않나요

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

뭔가 개별 compose 에서 LocalContext.current 를 가져오는 것 보다 하나의 LocalContext.current 를 가져오는 변수를 screen 에서 만들고 뿌려주는 방식을 생각했습니다. context 가 바뀌어도 쉽게 대응이 가능하니까요.
다만 context 가 바뀔 일이 없고 귀찮게 context를 자꾸 파라미터로 받아야 하는 상황이 되었습니다.
그냥 개별적으로 LocalContext.current 쓰겠습니다

@Composable
fun KoinSwitch(
checked: Boolean,
onCheckedChange: () -> Unit,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

onCheckedChange에서 변경 이후의 Boolean을 넘겨주는 것이 맞을 것 같습니다

navController: NavController
) {
composable(
route = "${DiningNavType.DiningDetail.route}?initDate={${INIT_DATE}}&initTabType={${INIT_TAB_TYPE}}",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

route 형태를 이렇게 만든 이유가 있나요
혹시 레퍼런스가 있다면 알려주세요

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

기존 DiningActivity 로 가는 경로는(위젯, 설정에서의 식단) 아무런 데이터 없이 그냥 DiningActivity 로 이동합니다. 다만 카카오 공유를 통해 들어오면 특정 요일, 특정 시점(아,점,저)의 정보를 넘겨주고 있어서 그에따라 이동해야합니다.
initDate 와 initTabType 를 기본 파라미터로 주고 initDate 를 activity 나 navigation 에서 계산하는 방법이 있는데 그건 좀 아닌거같아 선택적 파라미터로 주고 그 값이 없으면 viewModel 내부에서 계산하도록 했습니다.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

아뇨 ${DiningNavType.DiningDetail.route}/{$INIT_DATE}/{$INIT_TAB_TYPE} 형태로 해도 되는데 굳이 ${DiningNavType.DiningDetail.route}?initDate={${INIT_DATE}}&initTabType={${INIT_TAB_TYPE}}로 작성하신 이유가 있나 해서 여쭤본겁니다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

확실히 ${DiningNavType.DiningDetail.route}/{$INIT_DATE}/{$INIT_TAB_TYPE} 로 하고 {$INIT_DATE}/{$INIT_TAB_TYPE} 값을 기본값으로 넘겨주면 되긴 하겠네요.
저는 그런 부분은 생각 안했고 선택적 파라미터를 통해 route 를 작성하면 ${DiningNavType.DiningDetail.route} 만 route 값을 줘도 자동으로 {$INIT_DATE}/{$INIT_TAB_TYPE} 값을 default 값으로 주고 navigation 이 동작하기에 저렇게 만들었습니다. 그냥 / 로 하면 어쨋든 필수값을 끼워서 route 로 써야하니까요.

인터넷 url에서도 필수값이 아닌 값은 /search?q=ABC 처럼 사용하듯 만들었습니다.

showTooltip: Boolean,
showBottomSheet: Boolean,
experimentGroup: String,
context: Context,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

마찬가지로 context를 넘길 필요는 없을 것 같습니다

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

수정 안된 것 같은데 확인 해주세요


@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun DiningDetailScreenImpl(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

private 처리해도 될 것 같습니다

@@ -0,0 +1,16 @@
package com.example.dining
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

불필요한 테스트 코드 지워주세요

Copy link
Copy Markdown
Contributor

@jusang3057 jusang3057 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다. 👍

Text(
text = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().dayOfMonth.toString(),
style = KoinTheme.typography.medium15,
color = if (isSelected) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when 사용하면 깔끔할 것 같습니다.

Column(
modifier = modifier
.fillMaxWidth()
.shadow(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shadow가 2개 있는데 이거 맞는건지 확인 부탁드립니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

맞는거더라구요. 저도 좀 놀라긴 했습니다만 디자인 쪽에서 shadow는 대부분 2가지를 동시에 입혀서 표현하는 거 같습니다. 명암차이로 더 입체적으로 보이기 위해서 아닐까요?

style = KoinTheme.typography.bold18
)
Spacer(Modifier.height(12.dp))
Column(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이미 수직 방향 column안에 수직 방향 column을 사용하고 있습니다. 이거 없이도 Spacer만 사용하여 구현할 수 잇을 것 같습니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

spacer 만으로 간단하게 구현이 가능하긴 합니다.
다만 개인적으로 비슷한 내용을 포함하는 ui 요소들은 하나의 box 나 column, row 등으로 묶는 편입니다. 이부분도 코드 컨벤션을 통해 맞춰나가는게 좋을 거 같습니다.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

depth가 깊어진다는 문제가 있지만, paddingarrangement 때문에 묶는 것 자체는 괜찮을 것 같긴 합니다.
다만 비슷한 내용을 포함하는 ui 요소들을 묶는 것이 목적이라면 차라리 별도의 Composable 함수로 분리하는 것이 어떨까 싶네요 (component 디렉토리에 분리 X, 같은 파일에 있는 함수)

)
}
}
Column(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

마찬가지 입니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이부분도 마찬가지로 dialog 에 확인, 취소 버튼처럼 버튼 구조의 두가지를 코드적으로 묶기 위해 처리했습니다.

KYM-P added 17 commits August 23, 2025 15:28
fix: fix to navigation
fix: context default
fix: switch onValueChange
fix: fix to private screenimpl
fix: nested if to when
fix: feat ktlint
fix: fix getNotiPermissionInfo work only userState is not anonymous
fix: delete unused type
fix: feature unused import
fix: change to feature DiningActivity
refactor: change scroll system to use nestedScroll
fix: change shaow value more realistic
fix: redesgin
fix: feat ktlint
fix: delete unused value
fix: add loading screen
@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Aug 25, 2025

기존 피드백 모두 수정했습니다.
context 관련 문제는 파라미터는 유지하지만 기본 값을 지정하는 방식으로 수정했습니다.

기존 dining 화면으로 변경된 부분을 다시 feature dining ui 로 바꾸었습니다.
nested scroll 을 통해 collapsing toolbar 구현 했습니다.
일부 디자인을 조금 수정했습니다. (식단 카드 배경 색, 그림자 색 등)
식당 정보 화면에 로딩 화면을 추가했습니다.

@kongwoojin
Copy link
Copy Markdown
Member

고생하셨습니다. 다만 이미지 확대가 안되는 것 같은데 확인 부탁드려요

이미지 클릭시 나오는 다이얼로그의 확대인가요? 현재 더블클릭으로 확대는 안만들긴 했습니다. 아니면 Dining item 에서 사진이 작게 보이는건가요? 우선 후자도 문제가 있어보여서 고쳐보겠습니다.

* image layout 에 맞도록 최대 크기로 확대 시켰습니다.

이미지 클릭 시 다이얼로그에서 확대 가능해야 합니다.

@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Sep 1, 2025

이미지 클릭 시 다이얼로그에서 확대 가능해야 합니다.

현재 다이얼로그에서 더블클릭으로 인한 확대는 안되어도
드래그 동작을 통한 확대는 가능한 상태입니다.

@kongwoojin
Copy link
Copy Markdown
Member

이미지 클릭 시 다이얼로그에서 확대 가능해야 합니다.

현재 다이얼로그에서 더블클릭으로 인한 확대는 안되어도 드래그 동작을 통한 확대는 가능한 상태입니다.

기존에는 더블 탭으로 확대가 가능하지 않았나요 기능은 동일해야 할 것 같습니다

Upgrade imageDialog gestures system and animation scroll system
Delete coerceIn boilerplate
Feat ktlint
@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Sep 2, 2025

최대한 기존 image Dialog 와 비슷하게 만들었습니다.
doubleTap 으로 인한 확대 동작에 animation 을 넣기 위해 doubleTap 용 scale, offset 과 animation 을 추가했습니다.
(그냥 다같이 animation을 쓰기에는 드래그 동작은 속도가 많이 답답해져서 둘로 나누었습니다.)
maxX, maxY 를 통한 최대 Offset 제한을 하여 image 가 화면을 벗어나지 못하도록 만들었습니다. 이는 보일러 플레이트를 줄이기 위해 private 내부 함수로 구현했습니다.

더블탭 확대, 스크롤 확대 모두 지원합니다.
스크롤 화면 제한이 기존 image dialog 와 조금 다릅니다. 넓이 제한은 되었는데 높이 제한이 제 생각대로 잘 안되어서 우선 적당히 제한해봤습니다.

@KYM-P KYM-P linked an issue Sep 4, 2025 that may be closed by this pull request
3 tasks
Copy link
Copy Markdown
Member

@kongwoojin kongwoojin left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Member

@kongwoojin kongwoojin left a comment

Choose a reason for hiding this comment

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

아니다 Toolbar height가 여전히 이상하네요

@kongwoojin
Copy link
Copy Markdown
Member

image image

@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Sep 10, 2025

아하 길다고하신게 collapsing tool bar 가 아니라 top app bar 였군요
확인해보겠습니다.

Dining detail screen consume windowsInsets
Delete consume windowsInsets
Top app bar height is limited 85dp
DiningViewModel extends ViewModel() instead BaseViewModel
Add RefreshToPull Event
Feat Ktlint
@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Sep 11, 2025

TopAppBar 높이 감소

원본 component 가 높이가 고정이 되어있어서... heightIn 을 사용해 85.dp 로 제한했습니다. (수작업으로 찾기)
이제 진짜 딱 맞을겁니다.

BaseViewModel() 상속 을 ViewModel() 상속으로 변경

BaseViewModel 내부 기능도 잘 사용하지 않고 필요도 없어서 제거했습니다. (내부의 LiveData 가 보기 싫었어요)
추가적으로 교체 후 더 격렬하게 NPE 오류가 떠서 기존 init{} 에 들어가는 함수를 initData() 로 모두 묶어서 Screen 에서 호출 중

PullToRefresh 적용

기존 Dining 처럼 pull to refresh 적용했습니다.
기존에도 매끄럽게 refresh 가 작동하지 않아서 부드럽게 바꾸진 않았고,
부드럽게 바꾸면 너무 자주 refresh 가 일어날 거 같아서 따로 바꾸진 않겠습니다.
추가적으로 refresh 하는데 너무 밋밋해보여서 refresh 동안은 dining list 에 요소를 가리면서 로딩 바를 보여줍니다.

Add dining caution text and bottom padding
@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Sep 12, 2025

NavigationBarPadding 추가

windowinsets 를 모두 0 으로 만드니 edge to edge 로 하단 navigationbar 와 겹치는 상황 발생
navigationBarPadding() 을 통해 보정

식단 변경 알림문구 추가

기존 dining 의 요소인 식단 변경 알림 문구 추가

  • 기존 dining 의 요소인 하단 공백도 추가

Add weekend photo text and image dialog logging
@KYM-P
Copy link
Copy Markdown
Collaborator Author

KYM-P commented Sep 12, 2025

누락된 Image Dialog 클릭 logging 추가

주말 사진은 "주말은 식단 이미지를 제공하지 않습니다." 안내 문구가 나오도록 수정

Copy link
Copy Markdown
Member

@kongwoojin kongwoojin left a comment

Choose a reason for hiding this comment

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

고생하셨습니다.
코멘트 확인 해주세요

containerColor = KoinTheme.colors.neutral0,
topBar = {
KoinTopAppBar(
modifier = Modifier.heightIn(max = 85.dp),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

아 늘리신게 아니라 기본값이 애초에 다른거네요
이러면 기본값으로 두는게 맞나... 아...


init {
fun initData() {
getDining(initDate)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

dining은 init으로 처리하고, user 관련된 부분만 snapshotflow로 처리하면 어떨까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

MutableStateFlow 가 많아서 그런지
이제 init 안에 MutatbleStateFlow값이 있으면 null 값이라고 NPE 오류가 뜨고
snapshotFlow 도 init 안에서 제대로 동작을 안하네요 (시점이 빨라서 userState 가 초기값일 때 실행되는 거 일수도?)
왜 인지는 모르겠지만 느리게 생성되나 봅니다. (찾아봐도 잘 안나오고 대부분 launchedEffect 를 추천해줍니다)

그래서 LaunchedEffect 안에는 init 이 모두 들어가야 할 거 같고
snapshotFlow 로 만들면

LaunchedEffect(Unit) { // userState NPE error in viewModel init{}; Flow is null
        viewModel.getDining()
        snapshotFlow { userState }
            .collect { state ->
                if(!state.isAnonymous) {
                    viewModel.getShowBottomSheetValue()
                    viewModel.getNotificationPermissionInfo()
                }
            }
    }

처럼 바뀔 거 같습니다.
근데 LaunchedEffect(userState) 랑 큰 차이는 없을 거 같네요.

Copy link
Copy Markdown
Member

@kongwoojin kongwoojin left a comment

Choose a reason for hiding this comment

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

고생하셨습니다.
toolbar height는... 되돌리셔도 될 것 같고 저것들만 확인 후 머지해주세요

verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${dining.kcal}kcal",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

위에처럼 string resource 사용해주세요

modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

그냥 궁금한건데 이 부분은 컴포넌트화가 힘든 부분일까요?
중복 코드를 줄이는게 가능해보입니다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

해당 Row 부분 자체를 Item 과 ItemOriginal 을 공통으로 사용중이니 DiningItemMenu 로 묶겠습니다.

Componentization Dining Item menu
Change init launchedeffect key is Unit
Feat Ktlint
@KYM-P KYM-P merged commit 9b9ac72 into develop Sep 18, 2025
1 check passed
@KYM-P KYM-P deleted the refactor/#992-dining-compose branch September 18, 2025 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Koin Project] 식단 화면 compose 리팩토링

3 participants