From e678cd958730103a704f4de5bec4cc5b806c9e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 00:47:12 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[Fix]=20=EB=AF=B8=EC=9A=B4=EC=98=81=20?= =?UTF-8?q?=ED=91=B8=EB=93=9C=20=EC=BD=94=ED=8A=B8=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FirebaseRemoteConfigRepositoryImpl.kt | 8 +++++++- .../domain/usecase/menu/LoadMenusUseCase.kt | 1 - app/src/main/res/values-en/strings.xml | 3 --- app/src/main/res/values-ja/strings.xml | 3 --- app/src/main/res/values-vi/strings.xml | 3 --- app/src/main/res/values/strings.xml | 3 --- app/src/main/res/xml/firebase_remote_config.xml | 9 --------- .../analytics/PostHogAnalyticsTrackerTest.kt | 4 ++-- ...baseRemoteConfigRepositoryImplBehaviorSpec.kt | 16 ++++++++++++++++ .../repository/MenuRepositoryImplBehaviorSpec.kt | 8 ++++---- .../menu/GetMenuListUseCaseBehaviorSpec.kt | 6 +++--- .../cafeteria/menu/MenuViewModelBehaviorSpec.kt | 4 ++-- .../eatssu/common/analytics/AnalyticsEvent.kt | 1 - .../java/com/eatssu/common/enums/Restaurant.kt | 8 -------- core/common/src/main/res/values-en/strings.xml | 4 ---- core/common/src/main/res/values-ja/strings.xml | 4 ---- core/common/src/main/res/values-vi/strings.xml | 4 ---- core/common/src/main/res/values/strings.xml | 4 ---- language.csv | 3 --- 19 files changed, 34 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImpl.kt b/app/src/main/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImpl.kt index 396970415..9484e512e 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImpl.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImpl.kt @@ -9,6 +9,8 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings import kotlinx.coroutines.tasks.await import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonArray import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -62,7 +64,11 @@ class FirebaseRemoteConfigRepositoryImpl @Inject constructor( private fun parseCafeteriaJson(jsonString: String): List { return try { - json.decodeFromString>(jsonString).also { + json.parseToJsonElement(jsonString).jsonArray.mapNotNull { element -> + runCatching { json.decodeFromJsonElement(element) } + .onFailure { Timber.w(it, "지원하지 않는 식당 정보 제외: $element") } + .getOrNull() + }.also { Timber.d("Loaded cafeteria info: $it") } } catch (e: Exception) { diff --git a/app/src/main/java/com/eatssu/android/domain/usecase/menu/LoadMenusUseCase.kt b/app/src/main/java/com/eatssu/android/domain/usecase/menu/LoadMenusUseCase.kt index d6810d81c..0068d36a9 100644 --- a/app/src/main/java/com/eatssu/android/domain/usecase/menu/LoadMenusUseCase.kt +++ b/app/src/main/java/com/eatssu/android/domain/usecase/menu/LoadMenusUseCase.kt @@ -26,7 +26,6 @@ class LoadMenusUseCase @Inject constructor( addAll(Restaurant.getVariableRestaurantList()) if (shouldIncludeFixedRestaurants(date = date, time = time, isPublicHoliday = isPublicHoliday)) { - add(Restaurant.FOOD_COURT) add(Restaurant.SNACK_CORNER) } } diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index c4661465e..46fe14b2f 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -366,9 +366,6 @@ Student Union Building, 3F 11:00~15:30(Lunch) Bunsik(Korean Snacks), Old-school Lunch Box, Cup Rice, etc.\nClosed on weekends - Student Union Building, 2F - Closed - Coming Soon Student Union Building, 2F diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 2c5a5b5a5..649f547c0 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -366,9 +366,6 @@ 学生会館 3階 11:00~15:30(昼食) 粉食類、昔ながらのお弁当、カップご飯など\n週末休業 - 学生会館 2階 - 休業 - 準備中 学生会館 2階 diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 5a5715425..19ec5775d 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -366,9 +366,6 @@ Tòa Student Union, tầng 3 11:00~15:30(Bữa trưa) Bunsik(đồ ăn vặt Hàn Quốc), cơm hộp kiểu xưa, cơm ly, v.v.\nĐóng cửa cuối tuần - Tòa Student Union, tầng 2 - Nghỉ - Đang chuẩn bị Tòa Student Union, tầng 2 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 51711eafd..20e2f2de9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -379,9 +379,6 @@ 11:00~15:30(점심) 분식류, 옛날도시락, 컵밥 등\n주말 휴무 - 학생회관 2층 - 휴무 - 준비 중 학생회관 2층 diff --git a/app/src/main/res/xml/firebase_remote_config.xml b/app/src/main/res/xml/firebase_remote_config.xml index 6dc94af88..e6cdfda22 100644 --- a/app/src/main/res/xml/firebase_remote_config.xml +++ b/app/src/main/res/xml/firebase_remote_config.xml @@ -52,15 +52,6 @@ "https://eatssu-prod-bucket.s3.ap-northeast-2.amazonaws.com/reviewImg/66f13f3e-62ac-4747-a461-aabd26b3999e20240823074330%E1%84%89%E1%85%B3%E1%84%82%E1%85%A2%E1%86%A8%E1%84%8F%E1%85%A9%E1%84%82%E1%85%A5.jpeg" }, { - "enum": "FOOD_COURT", - "name": "푸드 코트", - "location": "학생회관 2층", - "time": "휴무", - "etc": "준비 중", - "image": - "https://eatssu-prod-bucket.s3.ap-northeast-2.amazonaws.com/reviewImg/09b487f5-e431-4ab2-a339-76e472ea8d8320240926041925%E1%84%91%E1%85%AE%E1%84%8F%E1%85%A9IMG_0396%20%E1%84%8F%E1%85%B3%E1%84%80%E1%85%A6.jpeg" - }, - { "enum": "DORMITORY", "name": "기숙사 식당", "location": "레지던스홀 지하 1층", diff --git a/app/src/test/java/com/eatssu/android/analytics/PostHogAnalyticsTrackerTest.kt b/app/src/test/java/com/eatssu/android/analytics/PostHogAnalyticsTrackerTest.kt index 59a99e1d7..c3c2b70f8 100644 --- a/app/src/test/java/com/eatssu/android/analytics/PostHogAnalyticsTrackerTest.kt +++ b/app/src/test/java/com/eatssu/android/analytics/PostHogAnalyticsTrackerTest.kt @@ -102,7 +102,7 @@ class PostHogAnalyticsTrackerTest { @Test fun `restaurant analytics payload omits excluded restaurants`() { - val addPayload = WidgetAnalyticsEvent.Added(Restaurant.FOOD_COURT).toPayload() + val addPayload = WidgetAnalyticsEvent.Added(Restaurant.THE_KITCHEN).toPayload() val removePayload = WidgetAnalyticsEvent.Removed(Restaurant.THE_KITCHEN).toPayload() assertTrue(addPayload.properties.isEmpty()) @@ -129,7 +129,7 @@ class PostHogAnalyticsTrackerTest { @Test fun `widget change payload omits excluded before and after restaurant values`() { val payload = WidgetAnalyticsEvent.Changed( - restaurantBefore = Restaurant.FOOD_COURT, + restaurantBefore = Restaurant.THE_KITCHEN, restaurantAfter = Restaurant.THE_KITCHEN, ).toPayload() diff --git a/app/src/test/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImplBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImplBehaviorSpec.kt index 176e1051e..9961474ea 100644 --- a/app/src/test/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImplBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImplBehaviorSpec.kt @@ -85,6 +85,22 @@ class FirebaseRemoteConfigRepositoryImplBehaviorSpec : AppBehaviorSpec({ } } + `when`("식당 정보 JSON에 지원하지 않는 enum이 섞여 있으면") { + every { remoteConfig.fetchAndActivate() } returns Tasks.forResult(true) + every { remoteConfig.getString("cafeteria_information") } returns """ + [ + {"enum":"FOOD_COURT","name":"푸드 코트","location":"2F","image":"a.png","time":"휴무","etc":"-"}, + {"enum":"HAKSIK","name":"학식당","location":"3F","image":"b.png","time":"11:00-14:00","etc":"-"} + ] + """.trimIndent() + + then("지원하는 식당 정보는 계속 반환한다") { + runTest { + repository.getRestaurantInfo(Restaurant.HAKSIK)?.name shouldBe "학식당" + } + } + } + `when`("식당 정보 JSON 파싱에 실패하면") { every { remoteConfig.fetchAndActivate() } returns Tasks.forResult(true) every { remoteConfig.getString("cafeteria_information") } returns "{invalid-json}" diff --git a/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt index 84d844338..df74ebe5f 100644 --- a/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt @@ -36,11 +36,11 @@ class MenuRepositoryImplBehaviorSpec : AppBehaviorSpec({ ) ) ) - coEvery { menuService.getFixMenu(Restaurant.FOOD_COURT.toString()) } returns ApiResult.Success(response) + coEvery { menuService.getFixMenu(Restaurant.SNACK_CORNER.toString()) } returns ApiResult.Success(response) then("도메인 Menu 리스트로 매핑한다") { runTest { - val result = repository.getFixedMenuList(Restaurant.FOOD_COURT) + val result = repository.getFixedMenuList(Restaurant.SNACK_CORNER) result.size shouldBe 1 result.first().name shouldBe "돈까스" } @@ -49,12 +49,12 @@ class MenuRepositoryImplBehaviorSpec : AppBehaviorSpec({ `when`("고정 메뉴 API가 실패하면") { coEvery { - menuService.getFixMenu(Restaurant.FOOD_COURT.toString()) + menuService.getFixMenu(Restaurant.SNACK_CORNER.toString()) } returns ApiResult.Failure(500, "err") then("빈 리스트를 반환한다") { runTest { - repository.getFixedMenuList(Restaurant.FOOD_COURT) shouldBe emptyList() + repository.getFixedMenuList(Restaurant.SNACK_CORNER) shouldBe emptyList() } } } diff --git a/app/src/test/java/com/eatssu/android/domain/usecase/menu/GetMenuListUseCaseBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/domain/usecase/menu/GetMenuListUseCaseBehaviorSpec.kt index bf317484e..7de84fd0b 100644 --- a/app/src/test/java/com/eatssu/android/domain/usecase/menu/GetMenuListUseCaseBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/domain/usecase/menu/GetMenuListUseCaseBehaviorSpec.kt @@ -23,12 +23,12 @@ class GetMenuListUseCaseBehaviorSpec : AppBehaviorSpec({ `when`("고정식당 메뉴를 조회하면") { val result = listOf(Menu(id = 1, name = "돈까스", price = 5000, rate = 4.0)) - coEvery { menuRepository.getFixedMenuList(Restaurant.FOOD_COURT) } returns result + coEvery { menuRepository.getFixedMenuList(Restaurant.SNACK_CORNER) } returns result then("menuRepository.getFixedMenuList를 사용한다") { runTest { - useCase(Restaurant.FOOD_COURT, "2025-01-01", Time.LUNCH) shouldBe result - coVerify(exactly = 1) { menuRepository.getFixedMenuList(Restaurant.FOOD_COURT) } + useCase(Restaurant.SNACK_CORNER, "2025-01-01", Time.LUNCH) shouldBe result + coVerify(exactly = 1) { menuRepository.getFixedMenuList(Restaurant.SNACK_CORNER) } coVerify(exactly = 0) { mealRepository.getTodayMenuList(any(), any(), any()) } } } diff --git a/app/src/test/java/com/eatssu/android/presentation/cafeteria/menu/MenuViewModelBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/cafeteria/menu/MenuViewModelBehaviorSpec.kt index 89fe53cdf..7bce57985 100644 --- a/app/src/test/java/com/eatssu/android/presentation/cafeteria/menu/MenuViewModelBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/cafeteria/menu/MenuViewModelBehaviorSpec.kt @@ -46,7 +46,7 @@ class MenuViewModelBehaviorSpec : AppBehaviorSpec({ `when`("usecase가 식당별 메뉴 맵을 반환하면") { val viewModel = MenuViewModel(useCase) - val r1 = Restaurant.FOOD_COURT + val r1 = Restaurant.SNACK_CORNER val r2 = Restaurant.HAKSIK val m1 = listOf(Menu(id = 1, name = "A", price = 1000, rate = 4.0)) val m2 = listOf(Menu(id = 2, name = "B", price = 2000, rate = 3.5)) @@ -77,7 +77,7 @@ class MenuViewModelBehaviorSpec : AppBehaviorSpec({ `when`("일부 식당 메뉴가 비어있어도") { val viewModel = MenuViewModel(useCase) - val r1 = Restaurant.FOOD_COURT + val r1 = Restaurant.SNACK_CORNER val r2 = Restaurant.HAKSIK val m1 = emptyList() val m2 = listOf(Menu(id = 2, name = "B", price = 2000, rate = 3.5)) diff --git a/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt b/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt index 97c5ef573..f6bf8351f 100644 --- a/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt +++ b/core/common/src/main/java/com/eatssu/common/analytics/AnalyticsEvent.kt @@ -279,6 +279,5 @@ private fun MutableMap.putRestaurant(key: String, restaurant: Resta } private val analyticsExcludedRestaurants = setOf( - Restaurant.FOOD_COURT, Restaurant.THE_KITCHEN, ) diff --git a/core/common/src/main/java/com/eatssu/common/enums/Restaurant.kt b/core/common/src/main/java/com/eatssu/common/enums/Restaurant.kt index 6ac59c7ab..16345104c 100644 --- a/core/common/src/main/java/com/eatssu/common/enums/Restaurant.kt +++ b/core/common/src/main/java/com/eatssu/common/enums/Restaurant.kt @@ -46,14 +46,6 @@ enum class Restaurant( R.string.cafeteria_faculty_etc, MenuType.VARIABLE ), - FOOD_COURT( - "food_court", - R.string.restaurant_food_court, - R.string.cafeteria_food_court_location, - R.string.cafeteria_food_court_time, - R.string.cafeteria_food_court_etc, - MenuType.FIXED - ), SNACK_CORNER( "snack_corner", R.string.restaurant_snack_corner, diff --git a/core/common/src/main/res/values-en/strings.xml b/core/common/src/main/res/values-en/strings.xml index a1ac80204..bd2b76f5a 100644 --- a/core/common/src/main/res/values-en/strings.xml +++ b/core/common/src/main/res/values-en/strings.xml @@ -9,7 +9,6 @@ Dodam Restaurant Dormitory Restaurant FACULTY (Faculty Only) - 푸드 코트 Snack Corner 더 키친 @@ -36,9 +35,6 @@ 11:00~15:30(Lunch) Bunsik(Korean Snacks), Old-school Lunch Box, Cup Rice, etc.\nClosed on weekends - Student Union Building, 2F - Closed - Coming Soon Student Union Building, 2F diff --git a/core/common/src/main/res/values-ja/strings.xml b/core/common/src/main/res/values-ja/strings.xml index 34589cd73..fb6859fe5 100644 --- a/core/common/src/main/res/values-ja/strings.xml +++ b/core/common/src/main/res/values-ja/strings.xml @@ -9,7 +9,6 @@ ドダム食堂 寮食堂 FACULTY(教職員専用) - 푸드 코트 スナックコーナー 더 키친 @@ -36,9 +35,6 @@ 11:00~15:30(昼食) 粉食類、昔ながらのお弁当、カップご飯など\n週末休業 - 学生会館 2階 - 休業 - 準備中 学生会館 2階 diff --git a/core/common/src/main/res/values-vi/strings.xml b/core/common/src/main/res/values-vi/strings.xml index 062d3922a..e4f75c8ad 100644 --- a/core/common/src/main/res/values-vi/strings.xml +++ b/core/common/src/main/res/values-vi/strings.xml @@ -9,7 +9,6 @@ Nhà ăn Dodam Nhà ăn ký túc xá FACULTY (Chỉ dành cho giảng viên/nhân viên) - 푸드 코트 Quầy đồ ăn nhẹ 더 키친 @@ -36,9 +35,6 @@ 11:00~15:30(Bữa trưa) Bunsik(đồ ăn vặt Hàn Quốc), cơm hộp kiểu xưa, cơm ly, v.v.\nĐóng cửa cuối tuần - Tòa Student Union, tầng 2 - Nghỉ - Đang chuẩn bị Tòa Student Union, tầng 2 diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml index e0d0c1001..7bc68c695 100644 --- a/core/common/src/main/res/values/strings.xml +++ b/core/common/src/main/res/values/strings.xml @@ -6,7 +6,6 @@ 도담 식당 기숙사 식당 FACULTY (교직원 전용) - 푸드 코트 스낵 코너 더 키친 @@ -33,9 +32,6 @@ 11:00~15:30(점심) 분식류, 옛날도시락, 컵밥 등\n주말 휴무 - 학생회관 2층 - 휴무 - 준비 중 학생회관 2층 diff --git a/language.csv b/language.csv index ac9f82a33..d85e34a6a 100644 --- a/language.csv +++ b/language.csv @@ -293,9 +293,6 @@ cafeteria_snack_corner_etc,"분식류, 옛날도시락, 컵밥 등 Closed on weekends","粉食類、昔ながらのお弁当、カップご飯など 週末休業","Bunsik(đồ ăn vặt Hàn Quốc), cơm hộp kiểu xưa, cơm ly, v.v. Đóng cửa cuối tuần" -cafeteria_food_court_location,학생회관 2층,"Student Union Building, 2F",学生会館 2階,"Tòa Student Union, tầng 2" -cafeteria_food_court_time,휴무,Closed,休業,Nghỉ -cafeteria_food_court_etc,준비 중,Coming Soon,準備中,Đang chuẩn bị cafeteria_the_kitchen_location,학생회관 2층,"Student Union Building, 2F",学生会館 2階,"Tòa Student Union, tầng 2" cafeteria_the_kitchen_time,,,, cafeteria_the_kitchen_etc,,,, From c34537937db0cf113ca08e70c6906d16c1fa1f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 00:48:29 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[Fix]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=97=A4=EB=8D=94=20=EB=86=92=EC=9D=B4=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/mypage/MyPageScreen.kt | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt index 89f0f9e9d..c02eeb967 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt @@ -62,17 +62,20 @@ fun MyPageScreen( .verticalScroll(rememberScrollState()) .padding(bottom = dimensionResource(id = R.dimen.bottom_nav_height)), ) { - Spacer(modifier = Modifier.height(24.dp)) - - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.mypage), - style = EatssuTheme.typography.subtitle1, - color = Gray700, - textAlign = TextAlign.Center, - ) - - Spacer(modifier = Modifier.height(28.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 17.dp) + .height(56.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(R.string.mypage), + style = EatssuTheme.typography.subtitle1, + color = Gray700, + textAlign = TextAlign.Center, + ) + } Row( modifier = Modifier From a90becb288944dd7515327439ac1e537bafbb532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 00:49:50 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[Fix]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=9C=EC=9A=B8=EC=8B=9C=20=EB=A1=9C=EA=B3=A0=20=EB=B9=84?= =?UTF-8?q?=EC=9C=A8=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/presentation/login/LoginScreen.kt | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/presentation/login/LoginScreen.kt b/app/src/main/java/com/eatssu/android/presentation/login/LoginScreen.kt index ece83cbe4..eddc6c0f8 100644 --- a/app/src/main/java/com/eatssu/android/presentation/login/LoginScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/login/LoginScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -36,6 +37,7 @@ import com.eatssu.android.presentation.util.TrackScreenViewEvent import com.eatssu.common.enums.ScreenId import com.eatssu.design_system.theme.Black import com.eatssu.design_system.theme.EatssuTheme +import com.eatssu.design_system.theme.Gray200 import com.eatssu.design_system.theme.Primary import com.eatssu.design_system.theme.Secondary import com.eatssu.design_system.theme.White @@ -188,22 +190,46 @@ fun LoginScreen( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .padding(top = 50.dp) - .height(30.dp), + .padding(top = 48.dp) + .width(300.dp) + .height(20.dp), ) { - Image( - painter = painterResource(R.drawable.seoul_logo_kr), - contentDescription = "KOR Seoul Logo", - modifier = Modifier.weight(1f), - ) + Box( + modifier = Modifier + .weight(1f) + .height(20.dp), + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(R.drawable.seoul_logo_kr), + contentDescription = "KOR Seoul Logo", + contentScale = ContentScale.Fit, + modifier = Modifier + .width(89.dp) + .height(17.dp), + ) + } VerticalDivider( - modifier = Modifier.width(1.dp), - ) - Image( - painter = painterResource(R.drawable.seoul_logo_en), - contentDescription = "ENG Seoul Logo", - modifier = Modifier.weight(1f), + modifier = Modifier + .width(1.dp) + .height(20.dp), + color = Gray200, ) + Box( + modifier = Modifier + .weight(1f) + .height(20.dp), + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(R.drawable.seoul_logo_en), + contentDescription = "ENG Seoul Logo", + contentScale = ContentScale.Fit, + modifier = Modifier + .width(145.dp) + .height(17.dp), + ) + } } } } From e04dd7f8bd2ab97270bbc15e60fe0945eb22c783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 00:52:12 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[Fix]=20=EC=A0=9C=ED=9C=B4=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=B0=9C=20UI=20=EB=B9=84=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../favorite/FavoriteDetailScreen.kt | 4 - .../presentation/map/MapFragmentView.kt | 4 - .../map/component/MapRestaurantBottomSheet.kt | 79 +------------------ 3 files changed, 2 insertions(+), 85 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt b/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt index 052417ce4..c8642ef78 100644 --- a/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt @@ -68,7 +68,6 @@ fun FavoriteDetailRoute( is UiState.Success -> FavoriteDetailScreen( restaurant = state.data, onBack = onBack, - onLikeClick = viewModel::toggleLike, onNaverMapClick = { scope.launch { openMap(context, mapExternalNavigator, MapProvider.NAVER, state.data) @@ -88,7 +87,6 @@ fun FavoriteDetailRoute( private fun FavoriteDetailScreen( restaurant: PartnershipRestaurant, onBack: () -> Unit, - onLikeClick: () -> Unit, onNaverMapClick: () -> Unit, onKakaoMapClick: () -> Unit, ) { @@ -138,7 +136,6 @@ private fun FavoriteDetailScreen( MapRestaurantBottomSheet( storeName = restaurant.storeName, storeType = restaurant.storeType, - isLike = restaurant.likedByUser, mapRestaurantList = listOf( RestaurantInfo( collegeName = restaurant.collegeName, @@ -150,7 +147,6 @@ private fun FavoriteDetailScreen( onNaverMapClick = onNaverMapClick, onKakaoMapClick = onKakaoMapClick, onDismiss = onBack, - onLikeClick = onLikeClick, scrimColor = Color.Transparent, ) } diff --git a/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt b/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt index 3cb481cb5..d4aec5324 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt @@ -457,7 +457,6 @@ internal fun MapScreen( MapRestaurantBottomSheet( storeName = info.storeName, storeType = storeType, - isLike = info.likedByUser, mapRestaurantList = mapState.restaurantInfoList, onNaverMapClick = { openMap(MapProvider.NAVER, info) @@ -468,9 +467,6 @@ internal fun MapScreen( onDismiss = { onHidePartnershipSheet() }, - onLikeClick = { - viewModel.likePartnership(info.id) - }, ) } } diff --git a/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt b/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt index cbeb36f26..107152c7f 100644 --- a/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt +++ b/app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt @@ -1,13 +1,7 @@ package com.eatssu.android.presentation.map.component -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -29,18 +23,11 @@ import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Color.Companion.White -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -54,37 +41,25 @@ import com.eatssu.android.presentation.map.model.RestaurantInfo import com.eatssu.android.presentation.util.TrackScreenViewEvent import com.eatssu.common.enums.ScreenId import com.eatssu.common.enums.StoreType -import com.eatssu.design_system.component.EatSsuSnackbar -import com.eatssu.design_system.component.EatSsuSnackbarType import com.eatssu.design_system.theme.EatssuTheme -import com.eatssu.design_system.theme.Error import com.eatssu.design_system.theme.Gray200 import com.eatssu.design_system.theme.Gray400 import com.eatssu.design_system.theme.Gray500 import com.eatssu.design_system.theme.Gray600 import com.eatssu.design_system.theme.Primary -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun MapRestaurantBottomSheet( storeName: String, storeType: StoreType, - isLike: Boolean, mapRestaurantList: List, onNaverMapClick: () -> Unit = {}, onKakaoMapClick: () -> Unit = {}, onDismiss: () -> Unit = {}, - onLikeClick: () -> Unit = {}, scrimColor: Color = BottomSheetDefaults.ScrimColor, ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - val context = LocalContext.current - val scope = rememberCoroutineScope() - var snackbarState by remember { mutableStateOf?>(null) } - var snackbarJob by remember { mutableStateOf(null) } LaunchedEffect(Unit) { sheetState.show() @@ -122,13 +97,12 @@ fun MapRestaurantBottomSheet( Spacer(modifier = Modifier.height(14.dp)) - // 상단 타이틀 라인 (store name + 하트) + // 상단 타이틀 라인 Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween ) { Column { Text( @@ -163,35 +137,7 @@ fun MapRestaurantBottomSheet( } } - // 찜 기능 - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(bottom = 24.dp) - ) { - Image( - painter = painterResource(id = if (isLike) R.drawable.ic_like_filled else R.drawable.ic_like_line), - contentDescription = "좋아요", - colorFilter = if (isLike) ColorFilter.tint(Error) else null, - modifier = Modifier - .size(24.dp) - .clickable { - val wasLiked = isLike - onLikeClick() - snackbarJob?.cancel() - snackbarJob = scope.launch { - snackbarState = if (!wasLiked) { - context.getString(R.string.favorite_added_snackbar) to null - } else { - context.getString(R.string.favorite_deleted_snackbar) to context.getString( - R.string.favorite_undo - ) - } - delay(3000) - snackbarState = null - } - } - ) - } + // TODO 찜 탭 재도입 시 상세 찜 UI 복구 } Spacer(modifier = Modifier.height(20.dp)) @@ -260,24 +206,6 @@ fun MapRestaurantBottomSheet( onNaverMapClick = onNaverMapClick, ) - androidx.compose.animation.AnimatedVisibility( - visible = snackbarState != null, - enter = fadeIn() + slideInVertically { it / 2 }, - exit = fadeOut() + slideOutVertically { it / 2 }, - ) { - snackbarState?.let { (message, actionLabel) -> - EatSsuSnackbar( - message = message, - actionLabel = actionLabel, - onActionClick = { - snackbarJob?.cancel() - snackbarState = null - onLikeClick() - }, - type = EatSsuSnackbarType.Success, - ) - } - } } } } @@ -286,7 +214,6 @@ fun MapRestaurantBottomSheet( @Preview(showBackground = true) @Composable fun MapRestaurantBottomSheetPreview() { - var isLiked by remember { mutableStateOf(false) } val dummyList = listOf( RestaurantInfo("경영대", null, "09.03~12.18","학생증 인증하면 음료수 1개 증정"), RestaurantInfo("IT대", null,"09.01~12.31", "학생증 인증하고 카카오페이 결제 시 10% 할인, 긴내용긴내용긴내용긴내용"), @@ -300,8 +227,6 @@ fun MapRestaurantBottomSheetPreview() { storeName = "현선이네", mapRestaurantList = dummyList, storeType = StoreType.RESTAURANT, - isLike = isLiked, - onLikeClick = { isLiked = !isLiked }, ) } } From 69c2ff3565b49edf1668b30d3a1fe45b000a1fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 00:56:24 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[Fix]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=86=8C=EC=86=8D=20=EB=B0=8F=20=EC=9D=B8?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EA=B7=B8=EB=9E=A8=20=EB=AC=B8=EA=B5=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../eatssu/android/presentation/MainViewModel.kt | 4 ++++ .../presentation/mypage/MyPageFragment.kt | 16 ++++++++++------ .../android/presentation/mypage/MyPageScreen.kt | 6 +++--- app/src/main/res/values-en/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- .../presentation/MainViewModelBehaviorSpec.kt | 6 +++++- 8 files changed, 26 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/presentation/MainViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/MainViewModel.kt index 38609248c..ae1438c0c 100644 --- a/app/src/main/java/com/eatssu/android/presentation/MainViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/MainViewModel.kt @@ -65,6 +65,7 @@ class MainViewModel @Inject constructor( val userInfo = getUserCollegeDepartmentUseCase() _uiState.value = UiState.Success( MainState.DepartmentState( + collegeName = userInfo.userCollege.collegeName, departmentName = userInfo.userDepartment.departmentName ) ) @@ -146,6 +147,7 @@ class MainViewModel @Inject constructor( val userInfo = getUserCollegeDepartmentUseCase() _uiState.value = UiState.Success( MainState.DepartmentState( + collegeName = userInfo.userCollege.collegeName, departmentName = userInfo.userDepartment.departmentName, showUserDepartmentBottomSheet = (userInfo.userCollege.collegeId == -1 || userInfo.userDepartment.departmentId == -1) @@ -169,6 +171,7 @@ class MainViewModel @Inject constructor( _uiState.value = UiState.Success( MainState.DepartmentState( + collegeName = college.collegeName, departmentName = department.departmentName, showUserDepartmentBottomSheet = (college.collegeId == -1 || department.departmentId == -1) @@ -201,6 +204,7 @@ sealed class MainState { object NicknameNull : MainState() object LoggedOut : MainState() data class DepartmentState( + val collegeName: String? = "", val departmentName: String? = "", val showUserDepartmentBottomSheet: Boolean = false ) : MainState() diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt index d58f60cea..06c74e4e7 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageFragment.kt @@ -97,17 +97,21 @@ class MyPageFragment : Fragment() { val uiState by myPageViewModel.uiState.collectAsStateWithLifecycle() val mainUiState by mainViewModel.uiState.collectAsStateWithLifecycle() - val departmentName = when (val state = mainUiState) { + val notFoundText = stringResource(R.string.not_found) + val loadingText = stringResource(R.string.widget_loading) + val affiliationName = when (val state = mainUiState) { is UiState.Success -> { when (val data = state.data) { - is MainState.DepartmentState -> data.departmentName - ?: stringResource(R.string.not_found) + is MainState.DepartmentState -> listOfNotNull( + data.collegeName?.takeIf { it.isNotBlank() }, + data.departmentName?.takeIf { it.isNotBlank() }, + ).joinToString(" ").ifBlank { notFoundText } - else -> stringResource(R.string.widget_loading) + else -> loadingText } } - else -> stringResource(R.string.widget_loading) + else -> loadingText } ProvideAnalyticsTracker(analyticsTracker) { @@ -116,7 +120,7 @@ class MyPageFragment : Fragment() { is UiState.Success -> { MyPageScreen( state = state.data, - departmentName = departmentName, + affiliationName = affiliationName, onAlarmToggle = ::handleAlarmSwitchChange, onMyInfoClick = { mainViewModel.trackMyPageMenu(MENU_MY_INFO) diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt index c02eeb967..bf58d033a 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt @@ -40,7 +40,7 @@ import com.eatssu.design_system.theme.White @Composable fun MyPageScreen( state: MyPageState, - departmentName: String, + affiliationName: String, onAlarmToggle: (Boolean) -> Unit, onMyInfoClick: () -> Unit, onMyReviewClick: () -> Unit, @@ -102,7 +102,7 @@ fun MyPageScreen( ) Text( - text = departmentName, + text = affiliationName, style = EatssuTheme.typography.body3, color = Gray700, ) @@ -323,7 +323,7 @@ private fun MyPageScreenPreview() { isAlarmOn = true, appVersion = "1.0.0 (1)", ), - departmentName = "컴퓨터학부", + affiliationName = "AI대학 정보보호학과", onAlarmToggle = {}, onMyInfoClick = {}, onMyReviewClick = {}, diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 46fe14b2f..2b0736257 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -346,7 +346,7 @@ - EAT-SSU 인스타그램 바로가기 + EAT-SSU 인스타그램 diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 649f547c0..87c413dd2 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -346,7 +346,7 @@ - EAT-SSU 인스타그램 바로가기 + EAT-SSU 인스타그램 diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 19ec5775d..cc1e1d402 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -346,7 +346,7 @@ - EAT-SSU 인스타그램 바로가기 + EAT-SSU 인스타그램 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20e2f2de9..6f5852414 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -349,7 +349,7 @@ https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8 https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80 https://eatssu-coffee.figma.site/ - EAT-SSU 인스타그램 바로가기 + EAT-SSU 인스타그램 eatssu.official https://www.instagram.com/eatssu.official/ https://www.instagram.com/p/DVu1n6SEs5b/ diff --git a/app/src/test/java/com/eatssu/android/presentation/MainViewModelBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/MainViewModelBehaviorSpec.kt index 0c45aa458..50350f4d8 100644 --- a/app/src/test/java/com/eatssu/android/presentation/MainViewModelBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/MainViewModelBehaviorSpec.kt @@ -84,7 +84,10 @@ class MainViewModelBehaviorSpec : AppBehaviorSpec({ viewModel.refreshUserDepartment() eventually(2.seconds) { viewModel.uiState.value shouldBe UiState.Success( - MainState.DepartmentState(departmentName = "컴퓨터학부") + MainState.DepartmentState( + collegeName = "IT", + departmentName = "컴퓨터학부", + ) ) } } @@ -118,6 +121,7 @@ class MainViewModelBehaviorSpec : AppBehaviorSpec({ viewModel.uiState.value shouldBe UiState.Success( MainState.DepartmentState( + collegeName = "IT", departmentName = "컴퓨터학부", showUserDepartmentBottomSheet = false ) From 89009ac12926fdb473292eafc2e2988e3d9a0d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 00:58:51 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[Fix]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=84=A0=ED=83=9D=20=EC=96=B8=EC=96=B4=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/mypage/MyPageScreen.kt | 24 +++++++++++---- .../presentation/mypage/MyPageViewModel.kt | 13 +++++++- .../mypage/MyPageViewModelBehaviorSpec.kt | 30 +++++++++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt index bf58d033a..d66015860 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageScreen.kt @@ -159,6 +159,7 @@ fun MyPageScreen( MyPageMenuItem( title = stringResource(R.string.language_setting), + trailingText = state.selectedLanguage.nativeDisplayName, onClick = onLanguageSettingClick, ) @@ -288,6 +289,7 @@ fun MyPageSectionText(text: String) { fun MyPageMenuItem( title: String, onClick: () -> Unit, + trailingText: String? = null, ) { Column { Row( @@ -303,12 +305,22 @@ fun MyPageMenuItem( style = EatssuTheme.typography.body1, color = Gray700, ) - Icon( - modifier = Modifier.size(18.dp), - painter = painterResource(R.drawable.ic_arrow_right), - contentDescription = null, - tint = Gray300, - ) + Row(verticalAlignment = Alignment.CenterVertically) { + trailingText?.let { text -> + Text( + text = text, + style = EatssuTheme.typography.body3, + color = Gray400, + ) + Spacer(modifier = Modifier.width(8.dp)) + } + Icon( + modifier = Modifier.size(18.dp), + painter = painterResource(R.drawable.ic_arrow_right), + contentDescription = null, + tint = Gray300, + ) + } } } } diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageViewModel.kt index 6ada9e763..950396ab5 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/MyPageViewModel.kt @@ -11,6 +11,7 @@ import com.eatssu.android.domain.usecase.user.GetUserNickNameUseCase import com.eatssu.common.UiEvent import com.eatssu.common.UiState import com.eatssu.common.UiText +import com.eatssu.common.enums.AppLanguage import com.eatssu.common.enums.ToastType import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -55,6 +56,7 @@ class MyPageViewModel @Inject constructor( init { observeNotificationStatus() + observeAppLanguage() fetchMyInfo() } @@ -66,6 +68,14 @@ class MyPageViewModel @Inject constructor( } } + private fun observeAppLanguage() { + viewModelScope.launch { + settingDataStore.appLanguage.collectLatest { language -> + _state.update { it.copy(selectedLanguage = language) } + } + } + } + fun fetchMyInfo() { viewModelScope.launch { val nickname = getUserNickNameUseCase() @@ -104,7 +114,8 @@ data class MyPageState( val nickname: String? = null, val platform: String = "KAKAO", val isAlarmOn: Boolean = false, - val appVersion: String = "0.0.0" + val appVersion: String = "0.0.0", + val selectedLanguage: AppLanguage = AppLanguage.KOREAN, ) { val hasNickname: Boolean get() = !nickname.isNullOrBlank() } diff --git a/app/src/test/java/com/eatssu/android/presentation/mypage/MyPageViewModelBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/mypage/MyPageViewModelBehaviorSpec.kt index d2ebca254..8c8b3f4ff 100644 --- a/app/src/test/java/com/eatssu/android/presentation/mypage/MyPageViewModelBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/mypage/MyPageViewModelBehaviorSpec.kt @@ -10,6 +10,7 @@ import com.eatssu.android.test.AppBehaviorSpec import com.eatssu.android.test.assertToast import com.eatssu.android.test.awaitToastEvent import com.eatssu.common.UiState +import com.eatssu.common.enums.AppLanguage import com.eatssu.common.enums.ToastType import io.kotest.matchers.shouldBe import io.mockk.Runs @@ -32,9 +33,11 @@ class MyPageViewModelBehaviorSpec : AppBehaviorSpec({ val setDailyNotificationStatusUseCase = mockk() val alarmUseCase = mockk() val settingDataStore = mockk() + val appLanguage = MutableStateFlow(AppLanguage.KOREAN) every { alarmUseCase.scheduleAlarm() } just Runs every { alarmUseCase.cancelAlarm() } just Runs + every { settingDataStore.appLanguage } returns appLanguage coEvery { setDailyNotificationStatusUseCase(any()) } returns Unit `when`("닉네임이 비어 있으면") { @@ -146,5 +149,32 @@ class MyPageViewModelBehaviorSpec : AppBehaviorSpec({ } } } + + `when`("앱 언어가 변경되면") { + val dailyStatus = MutableStateFlow(false) + every { settingDataStore.dailyNotificationStatus } returns dailyStatus + coEvery { getUserNickNameUseCase() } returns "eatssu" + + val viewModel = MyPageViewModel( + getUserNickNameUseCase, + setDailyNotificationStatusUseCase, + alarmUseCase, + settingDataStore, + ) + + then("현재 언어를 state에 반영한다") { + runTest { + viewModel.uiState.test { + awaitItem() + appLanguage.value = AppLanguage.JAPANESE + advanceUntilIdle() + + val state = expectMostRecentItem() as UiState.Success + state.data.selectedLanguage shouldBe AppLanguage.JAPANESE + cancelAndIgnoreRemainingEvents() + } + } + } + } } }) From 08ab2bb0c3406396d5169b6f09376b4a5b5de607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 20:11:23 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[Fix]=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EB=AC=B8=EA=B5=AC=20=EB=B0=8F=20=EC=96=B8?= =?UTF-8?q?=EC=96=B4=20=EC=A0=84=ED=99=98=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/GetUserCollegeDepartmentUseCase.kt | 6 +- .../cafeteria/review/ReviewComposeActivity.kt | 10 +-- .../cafeteria/review/list/ReviewListScreen.kt | 6 +- .../cafeteria/review/report/ReportActivity.kt | 5 +- .../event/AnyoneButMeEventDialog.kt | 7 +- .../favorite/FavoriteDetailScreen.kt | 3 +- .../presentation/favorite/FavoriteScreen.kt | 2 +- .../goodprice/GoodPriceMapActivity.kt | 4 +- .../presentation/login/LoginActivity.kt | 4 +- .../mypage/myreview/MyReviewListActivity.kt | 4 +- .../mypage/terms/TermSelectorActivity.kt | 4 +- .../mypage/userinfo/UserInfoActivity.kt | 7 +- .../widget/ui/WidgetSettingActivity.kt | 4 +- app/src/main/res/values-en/strings.xml | 69 ++++++++++--------- app/src/main/res/values-ja/strings.xml | 67 +++++++++--------- app/src/main/res/values-vi/strings.xml | 69 ++++++++++--------- app/src/main/res/values/strings.xml | 5 ++ .../UserDelegatingUseCasesBehaviorSpec.kt | 6 +- 18 files changed, 155 insertions(+), 127 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserCollegeDepartmentUseCase.kt b/app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserCollegeDepartmentUseCase.kt index 0d6859602..ab0a99135 100644 --- a/app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserCollegeDepartmentUseCase.kt +++ b/app/src/main/java/com/eatssu/android/domain/usecase/user/GetUserCollegeDepartmentUseCase.kt @@ -14,12 +14,12 @@ class GetUserCollegeDepartmentUseCase @Inject constructor( val nickname = accountDataStore.name.first() val college = accountDataStore.college.first() ?: College( collegeId = -1, - collegeName = "단과대" + collegeName = "" ) val department = accountDataStore.department.first() ?: Department( departmentId = -1, - departmentName = "학과" + departmentName = "" ) return UserInfo(nickname, department, college) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/ReviewComposeActivity.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/ReviewComposeActivity.kt index 5a1c0f490..045f6f02d 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/ReviewComposeActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/ReviewComposeActivity.kt @@ -1,7 +1,7 @@ package com.eatssu.android.presentation.cafeteria.review import android.os.Bundle -import androidx.activity.ComponentActivity +import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement @@ -16,10 +16,12 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.compose.rememberNavController +import com.eatssu.android.R import com.eatssu.android.analytics.ProvideAnalyticsTracker import com.eatssu.common.analytics.AnalyticsTracker import com.eatssu.common.enums.MenuType @@ -31,7 +33,7 @@ import javax.inject.Inject import kotlin.properties.Delegates @AndroidEntryPoint -class ReviewComposeActivity : ComponentActivity() { +class ReviewComposeActivity : AppCompatActivity() { @Inject lateinit var analyticsTracker: AnalyticsTracker @@ -92,14 +94,14 @@ class ReviewComposeActivity : ComponentActivity() { horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = "메뉴 정보를 불러오는데 실패했습니다.\n다시 시도해주세요.", + text = stringResource(R.string.review_menu_info_load_failed), textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = onBackClick) { - Text(text = "뒤로가기") + Text(text = stringResource(R.string.nav_back)) } } } diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt index ca9c2ec7f..375468809 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt @@ -343,7 +343,7 @@ internal fun ReviewListScreen( ) Spacer(modifier = Modifier.height(8.dp)) EatSsuButton( - text = "재시도", + text = stringResource(R.string.button_retry), onClick = { reviewPagingItems.retry() }, modifier = Modifier.width(100.dp) ) @@ -429,9 +429,9 @@ internal fun ReviewListScreen( .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Text("추가 데이터를 불러오지 못했습니다.") + Text(stringResource(R.string.review_additional_data_load_failed)) EatSsuButton( - text = "재시도", + text = stringResource(R.string.button_retry), onClick = { reviewPagingItems.retry() }, modifier = Modifier.width(100.dp) ) diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/report/ReportActivity.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/report/ReportActivity.kt index 393121420..8dc3e73b1 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/report/ReportActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/report/ReportActivity.kt @@ -5,6 +5,7 @@ import android.text.Editable import android.text.TextWatcher import androidx.activity.viewModels import androidx.lifecycle.lifecycleScope +import com.eatssu.android.R import com.eatssu.android.databinding.ActivityReportBinding import com.eatssu.android.presentation.base.BaseActivity import com.eatssu.android.presentation.util.showToast @@ -33,7 +34,7 @@ class ReportActivity : BaseActivity( //Todo 코드 수정 해야함 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - toolbarTitle.text = "신고하기" // 툴바 제목 설정 + toolbarTitle.text = getString(R.string.title_report) reviewId = intent.getLongExtra("reviewId", -1L) @@ -92,4 +93,4 @@ class ReportActivity : BaseActivity( else -> ReportType.EXTRA // Default to ETC if none selected } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/eatssu/android/presentation/event/AnyoneButMeEventDialog.kt b/app/src/main/java/com/eatssu/android/presentation/event/AnyoneButMeEventDialog.kt index 271e129eb..bf49f56c9 100644 --- a/app/src/main/java/com/eatssu/android/presentation/event/AnyoneButMeEventDialog.kt +++ b/app/src/main/java/com/eatssu/android/presentation/event/AnyoneButMeEventDialog.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -98,11 +99,11 @@ fun AnyoneButMeEventDialog( verticalAlignment = Alignment.CenterVertically ) { FooterActionText( - text = "다시 보지 않기", + text = stringResource(R.string.event_do_not_show_again), onClick = onDismissForever ) FooterActionText( - text = "닫기", + text = stringResource(R.string.button_close), onClick = onDismiss ) } @@ -124,7 +125,7 @@ private fun InstagramButton(onClick: () -> Unit) { verticalAlignment = Alignment.CenterVertically ) { Text( - text = "EAT-SSU 인스타그램 바로가기", + text = stringResource(R.string.eatssu_instagram_link), color = White, style = EatssuTheme.typography.body2 ) diff --git a/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt b/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt index c8642ef78..20fbe7a95 100644 --- a/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteDetailScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.eatssu.android.R @@ -128,7 +129,7 @@ private fun FavoriteDetailScreen( ) { Image( painter = painterResource(R.drawable.ic_arrow_left), - contentDescription = "뒤로가기", + contentDescription = stringResource(R.string.nav_back), modifier = Modifier.size(24.dp), ) } diff --git a/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteScreen.kt b/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteScreen.kt index 1b28e3183..472d7b77a 100644 --- a/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/favorite/FavoriteScreen.kt @@ -177,7 +177,7 @@ internal fun FavoriteScreen( ) { Icon( painter = painterResource(id = R.drawable.ic_arrow_left), - contentDescription = "뒤로가기", + contentDescription = stringResource(R.string.nav_back), tint = Gray500, ) } diff --git a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt index 768a7dc82..cdc25c57d 100644 --- a/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/goodprice/GoodPriceMapActivity.kt @@ -1,7 +1,7 @@ package com.eatssu.android.presentation.goodprice import android.os.Bundle -import androidx.activity.ComponentActivity +import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import com.eatssu.android.analytics.ProvideAnalyticsTracker @@ -15,7 +15,7 @@ import javax.inject.Inject * 착한가격업소 지도 액티비티 (비로그인 사용자도 진입 가능) */ @AndroidEntryPoint -class GoodPriceMapActivity : ComponentActivity() { +class GoodPriceMapActivity : AppCompatActivity() { @Inject lateinit var analyticsTracker: AnalyticsTracker diff --git a/app/src/main/java/com/eatssu/android/presentation/login/LoginActivity.kt b/app/src/main/java/com/eatssu/android/presentation/login/LoginActivity.kt index 3a8838fc0..9b896729e 100644 --- a/app/src/main/java/com/eatssu/android/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/login/LoginActivity.kt @@ -1,7 +1,7 @@ package com.eatssu.android.presentation.login import android.os.Bundle -import androidx.activity.ComponentActivity +import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -30,7 +30,7 @@ import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class LoginActivity : ComponentActivity() { +class LoginActivity : AppCompatActivity() { @Inject lateinit var analyticsTracker: AnalyticsTracker diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListActivity.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListActivity.kt index 03f7bc767..bf43506d9 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/myreview/MyReviewListActivity.kt @@ -1,7 +1,7 @@ package com.eatssu.android.presentation.mypage.myreview import android.os.Bundle -import androidx.activity.ComponentActivity +import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.navigation.compose.rememberNavController @@ -12,7 +12,7 @@ import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint -class MyReviewListComposeActivity : ComponentActivity() { +class MyReviewListComposeActivity : AppCompatActivity() { @Inject lateinit var analyticsTracker: AnalyticsTracker diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/terms/TermSelectorActivity.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/terms/TermSelectorActivity.kt index d7bd27ca6..14edaf575 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/terms/TermSelectorActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/terms/TermSelectorActivity.kt @@ -2,7 +2,7 @@ package com.eatssu.android.presentation.mypage.terms import android.content.Intent import android.os.Bundle -import androidx.activity.ComponentActivity +import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.lifecycle.lifecycleScope @@ -17,7 +17,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint -class TermSelectorActivity : ComponentActivity() { +class TermSelectorActivity : AppCompatActivity() { companion object { private const val MENU_TERMS_OF_USE = "terms_of_use" diff --git a/app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoActivity.kt b/app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoActivity.kt index 618a899fc..d25307782 100644 --- a/app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/mypage/userinfo/UserInfoActivity.kt @@ -20,6 +20,7 @@ import com.eatssu.common.UiEvent import com.eatssu.common.UiState import com.eatssu.common.enums.ScreenId import com.eatssu.common.enums.ToastType +import com.eatssu.common.R as CommonR import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -133,14 +134,16 @@ class UserInfoActivity : private fun updateCollegeDepartmentUI(data: UserInfoData) { with(binding) { - tvCollege.text = data.selectedCollege?.collegeName ?: "단과대" + tvCollege.text = data.selectedCollege?.collegeName + ?: getString(CommonR.string.college_placeholder) tvCollege.setTextColor( getColor( if (data.selectedCollege != null) R.color.gray700 else R.color.gray400 ) ) - tvDepartment.text = data.selectedDepartment?.departmentName ?: "학과" + tvDepartment.text = data.selectedDepartment?.departmentName + ?: getString(CommonR.string.department_placeholder) tvDepartment.setTextColor( getColor( if (data.selectedDepartment != null) R.color.gray700 else R.color.gray400 diff --git a/app/src/main/java/com/eatssu/android/presentation/widget/ui/WidgetSettingActivity.kt b/app/src/main/java/com/eatssu/android/presentation/widget/ui/WidgetSettingActivity.kt index b0ca005c4..9e500201c 100644 --- a/app/src/main/java/com/eatssu/android/presentation/widget/ui/WidgetSettingActivity.kt +++ b/app/src/main/java/com/eatssu/android/presentation/widget/ui/WidgetSettingActivity.kt @@ -3,7 +3,7 @@ package com.eatssu.android.presentation.widget.ui import android.appwidget.AppWidgetManager import android.content.Intent import android.os.Bundle -import androidx.activity.ComponentActivity +import androidx.appcompat.app.AppCompatActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.LaunchedEffect @@ -30,7 +30,7 @@ import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class WidgetSettingActivity : ComponentActivity() { +class WidgetSettingActivity : AppCompatActivity() { @Inject lateinit var saveRestaurantByFileKeyUseCase: SaveRestaurantByFileKeyUseCase diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 2b0736257..007f6eb82 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -53,6 +53,8 @@ Report Cancel Confirm + Close + Retry Update Check @@ -154,20 +156,22 @@ Add Photo (%1$d/%2$d) Review Settings An error occurred. + Could not load the menu information.\nPlease try again. + Failed to load more reviews. Show translation - 번역하기 + Translate Show original Translating... - AI가 한국어에서 번역함 - 번역을 사용할 수 없음 - AI 번역은 항상 정확하지 않을 수 있습니다. - 현재 AI 번역을 사용할 수 없습니다.\n나중에 다시 시도해 주세요. - 번역 안내 - 프로필 이미지 - 리뷰 메뉴 - 리뷰 이미지 + Translated from Korean by AI + Translation unavailable + AI translations may not always be accurate. + AI translation is currently unavailable.\nPlease try again later. + Translation Info + Profile Image + Review Menu + Review Image Failed to translate the review. - 번역 결과가 원문과 동일합니다. + The translation is identical to the original. Write a Review @@ -188,7 +192,7 @@ Today\'s Menu - It\'s outside business hours. + Closed now Price ★ Student Cafeteria @@ -215,9 +219,9 @@ Log Out Delete Account App Version - 알림 및 활동 - 서비스 정보 - 기타 + Notifications & Activity + Service Info + Other Push Notification Settings We\'ll send you a notification every day at 11:00 AM. @@ -282,7 +286,7 @@ Privacy Policy Privacy Policy Terms of Service - 약관 및 정책 + Terms & Policies Credits Open-Source Libraries @@ -320,23 +324,23 @@ Notes Hours Favorite Partners - 찜 - 메뉴 찜 - 제휴 찜 - 전체 - 아직 찜한 제휴가 없어요. - 관심 있는 제휴를 찜하고 빠르게 확인해 보세요! - 제휴 찜 열기 - 제휴 상세보기 - 편집 - 전체 선택 - 삭제 - 삭제 %d - 찜한 제휴에 추가했어요. - 찜한 제휴에서 삭제했어요. - 취소하기 + Favorites + Favorite Menus + Favorite Partners + All + No favorite partners yet. + Save partners you\'re interested in and find them quickly. + Open Favorite Partners + View Partner Details + Edit + Select All + Delete + Delete %d + Added to favorite partners. + Removed from favorite partners. + Cancel My Partners - 축제 + Festival All Restaurant Cafe @@ -346,7 +350,8 @@ - EAT-SSU 인스타그램 + EAT-SSU Instagram + Don\'t show again diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 87c413dd2..79f88ba25 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -53,6 +53,8 @@ 通報 キャンセル 確認 + 閉じる + 再試行 アップデート 確認 @@ -154,20 +156,22 @@ 写真を追加(%1$d/%2$d) レビュー設定 エラーが発生しました。 + メニュー情報を読み込めませんでした。\nもう一度お試しください。 + 追加のレビューを読み込めませんでした。 翻訳を見る - 번역하기 + 翻訳する 原文を見る 翻訳中... - AI가 한국어에서 번역함 - 번역을 사용할 수 없음 - AI 번역은 항상 정확하지 않을 수 있습니다. - 현재 AI 번역을 사용할 수 없습니다.\n나중에 다시 시도해 주세요. - 번역 안내 - 프로필 이미지 - 리뷰 메뉴 - 리뷰 이미지 + AIが韓国語から翻訳 + 翻訳を利用できません + AI翻訳は常に正確とは限りません。 + 現在AI翻訳を利用できません。\n後でもう一度お試しください。 + 翻訳情報 + プロフィール画像 + レビューメニュー + レビュー画像 レビューの翻訳に失敗しました。 - 번역 결과가 원문과 동일합니다. + 翻訳結果が原文と同じです。 レビューを書く @@ -215,9 +219,9 @@ ログアウト アカウント削除 アプリバージョン - 알림 및 활동 - 서비스 정보 - 기타 + 通知と活動 + サービス情報 + その他 プッシュ通知設定 毎日午前11時に通知をお送りします。 @@ -282,7 +286,7 @@ プライバシーポリシー プライバシーポリシー 利用規約 - 약관 및 정책 + 規約およびポリシー 開発者 オープンソースライブラリ @@ -320,23 +324,23 @@ 備考 営業時間 お気に入りの提携 - 찜 - 메뉴 찜 - 제휴 찜 - 전체 - 아직 찜한 제휴가 없어요. - 관심 있는 제휴를 찜하고 빠르게 확인해 보세요! - 제휴 찜 열기 - 제휴 상세보기 - 편집 - 전체 선택 - 삭제 - 삭제 %d - 찜한 제휴에 추가했어요. - 찜한 제휴에서 삭제했어요. - 취소하기 + お気に入り + お気に入りメニュー + お気に入り提携 + すべて + お気に入りの提携はまだありません。 + 気になる提携をお気に入りに追加して、すばやく確認しましょう! + お気に入り提携を開く + 提携詳細を見る + 編集 + すべて選択 + 削除 + %d件削除 + お気に入り提携に追加しました。 + お気に入り提携から削除しました。 + キャンセル 自分の提携 - 축제 + 祭り すべて レストラン カフェ @@ -346,7 +350,8 @@ - EAT-SSU 인스타그램 + EAT-SSU Instagram + 今後表示しない diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index cc1e1d402..131306ad0 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -53,6 +53,8 @@ Báo cáo Hủy Xác nhận + Đóng + Thử lại Cập nhật Kiểm tra @@ -154,20 +156,22 @@ Thêm ảnh (%1$d/%2$d) Cài đặt đánh giá Đã xảy ra lỗi. + Không thể tải thông tin thực đơn.\nVui lòng thử lại. + Không thể tải thêm đánh giá. Xem bản dịch - 번역하기 + Dịch Xem bản gốc Đang dịch... - AI가 한국어에서 번역함 - 번역을 사용할 수 없음 - AI 번역은 항상 정확하지 않을 수 있습니다. - 현재 AI 번역을 사용할 수 없습니다.\n나중에 다시 시도해 주세요. - 번역 안내 - 프로필 이미지 - 리뷰 메뉴 - 리뷰 이미지 + AI dịch từ tiếng Hàn + Bản dịch không khả dụng + Bản dịch AI không phải lúc nào cũng chính xác. + Hiện không thể sử dụng bản dịch AI.\nVui lòng thử lại sau. + Thông tin dịch thuật + Ảnh hồ sơ + Menu đánh giá + Ảnh đánh giá Không thể dịch đánh giá. - 번역 결과가 원문과 동일합니다. + Bản dịch giống với bản gốc. Viết đánh giá @@ -188,7 +192,7 @@ Thực đơn hôm nay - Hiện đang ngoài giờ phục vụ. + Hiện không trong giờ hoạt động. Giá ★ Nhà ăn sinh viên @@ -215,9 +219,9 @@ Đăng xuất Xóa tài khoản Phiên bản ứng dụng - 알림 및 활동 - 서비스 정보 - 기타 + Thông báo & Hoạt động + Thông tin dịch vụ + Khác Cài đặt thông báo đẩy Bạn sẽ nhận được thông báo mỗi ngày lúc 11:00. @@ -282,7 +286,7 @@ Chính sách quyền riêng tư Chính sách quyền riêng tư Điều khoản dịch vụ - 약관 및 정책 + Điều khoản & Chính sách Thông tin đội ngũ Thư viện mã nguồn mở @@ -320,23 +324,23 @@ Ghi chú Giờ mở cửa Ưu đãi yêu thích - 찜 - 메뉴 찜 - 제휴 찜 - 전체 - 아직 찜한 제휴가 없어요. - 관심 있는 제휴를 찜하고 빠르게 확인해 보세요! - 제휴 찜 열기 - 제휴 상세보기 - 편집 - 전체 선택 - 삭제 - 삭제 %d - 찜한 제휴에 추가했어요. - 찜한 제휴에서 삭제했어요. - 취소하기 + Yêu thích + Món yêu thích + Đối tác yêu thích + Tất cả + Chưa có đối tác yêu thích. + Lưu các đối tác bạn quan tâm để xem nhanh. + Mở đối tác yêu thích + Xem chi tiết đối tác + Chỉnh sửa + Chọn tất cả + Xóa + Xóa %d + Đã thêm vào đối tác yêu thích. + Đã xóa khỏi đối tác yêu thích. + Hủy Ưu đãi của tôi - 축제 + Lễ hội Tất cả Nhà hàng Quán cà phê @@ -346,7 +350,8 @@ - EAT-SSU 인스타그램 + Instagram EAT-SSU + Không hiển thị lại diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f5852414..c0af8eebd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,8 @@ 신고하기 취소 확인 + 닫기 + 재시도 업데이트 중복확인 @@ -151,6 +153,8 @@ 사진 추가(%1$d/%2$d) 리뷰 설정 에러가 발생했습니다. + 메뉴 정보를 불러오지 못했습니다.\n다시 시도해주세요. + 추가 리뷰를 불러오지 못했습니다. 번역 보기 번역하기 원문 보기 @@ -350,6 +354,7 @@ https://github.com/EAT-SSU/Docs/wiki/EAT%E2%80%90SSU-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%EC%9A%A9%EC%95%BD%EA%B4%80 https://eatssu-coffee.figma.site/ EAT-SSU 인스타그램 + 다시 보지 않기 eatssu.official https://www.instagram.com/eatssu.official/ https://www.instagram.com/p/DVu1n6SEs5b/ diff --git a/app/src/test/java/com/eatssu/android/domain/usecase/user/UserDelegatingUseCasesBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/domain/usecase/user/UserDelegatingUseCasesBehaviorSpec.kt index 41c15ccbb..e01a637cc 100644 --- a/app/src/test/java/com/eatssu/android/domain/usecase/user/UserDelegatingUseCasesBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/domain/usecase/user/UserDelegatingUseCasesBehaviorSpec.kt @@ -43,13 +43,13 @@ class UserDelegatingUseCasesBehaviorSpec : AppBehaviorSpec({ every { accountDataStore.college } returns flowOf(null) every { accountDataStore.department } returns flowOf(null) - then("기본 placeholder 값으로 채운다") { + then("언어에 종속되지 않는 빈 placeholder 값으로 채운다") { runTest { val result = useCase() result.userCollege.collegeId shouldBe -1 - result.userCollege.collegeName shouldBe "단과대" + result.userCollege.collegeName shouldBe "" result.userDepartment.departmentId shouldBe -1 - result.userDepartment.departmentName shouldBe "학과" + result.userDepartment.departmentName shouldBe "" } } } From 4ce2670d70bbf25e8847e98e8837196949e127eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 20:12:00 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[Feat]=20=EB=B3=80=EB=8F=99=EC=8B=9D?= =?UTF-8?q?=EB=8B=A8=20=EB=8C=80=ED=91=9C=EB=A9=94=EB=89=B4=20=EC=98=81?= =?UTF-8?q?=EB=AC=B8=EB=AA=85=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/remote/dto/response/MealResponse.kt | 34 +++- .../remote/repository/MealRepositoryImpl.kt | 33 +++- .../data/remote/service/MealService.kt | 8 + .../domain/repository/MealRepository.kt | 5 + .../usecase/menu/GetMealMenuNamesUseCase.kt | 11 ++ .../cafeteria/review/list/ReviewListScreen.kt | 9 +- .../review/list/ReviewListViewModel.kt | 41 ++++- .../MealRepositoryImplBehaviorSpec.kt | 148 ++++++++++++++++-- .../list/ReviewListViewModelBehaviorSpec.kt | 70 +++++++++ 9 files changed, 327 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/eatssu/android/domain/usecase/menu/GetMealMenuNamesUseCase.kt diff --git a/app/src/main/java/com/eatssu/android/data/remote/dto/response/MealResponse.kt b/app/src/main/java/com/eatssu/android/data/remote/dto/response/MealResponse.kt index 1d7ce4bf0..1c148527b 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/dto/response/MealResponse.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/dto/response/MealResponse.kt @@ -18,14 +18,25 @@ data class GetMealResponse( data class MenusInformationList( @SerialName("menuId") val menuId: Long? = null, @SerialName("name") val name: String? = null, + @SerialName("isMain") val isMain: Boolean = false, ) -fun List.mapTodayMenuResponseToMenu(): List { +@Serializable +data class GetMealMenusInfoResponse( + @SerialName("briefMenus") val briefMenus: List = emptyList(), +) + +fun List.mapTodayMenuResponseToMenu( + showMainMenusOnly: Boolean = false, +): List { val menuList = mutableListOf() this.forEach { mealResponse -> val menuNames = - mealResponse.briefMenus.mapNotNull { it.name }.joinToString(separator = MENU_SEPARATOR) + mealResponse.briefMenus + .menusForDisplay(showMainMenusOnly) + .mapNotNull { it.name?.takeIf(String::isNotBlank) } + .joinToString(separator = MENU_SEPARATOR) val mealId = mealResponse.mealId ?: -1 val price = mealResponse.price ?: 0 val mainRating = mealResponse.rating ?: 0.0 @@ -39,8 +50,23 @@ fun List.mapTodayMenuResponseToMenu(): List { } -fun List.toDomain(): List> { +fun List.toDomain( + showMainMenusOnly: Boolean = false, +): List> { return this.map { meal -> - meal.briefMenus.mapNotNull { it.name } + meal.briefMenus + .menusForDisplay(showMainMenusOnly) + .mapNotNull { it.name?.takeIf(String::isNotBlank) } } } + +fun GetMealMenusInfoResponse.toMenuNames(): List = + briefMenus.mapNotNull { it.name?.takeIf(String::isNotBlank) } + +private fun List.menusForDisplay( + showMainMenusOnly: Boolean, +): List { + if (!showMainMenusOnly) return this + + return filter { it.isMain }.ifEmpty { this } +} diff --git a/app/src/main/java/com/eatssu/android/data/remote/repository/MealRepositoryImpl.kt b/app/src/main/java/com/eatssu/android/data/remote/repository/MealRepositoryImpl.kt index 8119f5075..f34a04b3e 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/repository/MealRepositoryImpl.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/repository/MealRepositoryImpl.kt @@ -1,18 +1,26 @@ package com.eatssu.android.data.remote.repository +import com.eatssu.android.data.local.SettingDataStore import com.eatssu.android.data.model.map import com.eatssu.android.data.model.orEmptyList +import com.eatssu.android.data.model.orNull import com.eatssu.android.data.remote.dto.response.mapTodayMenuResponseToMenu +import com.eatssu.android.data.remote.dto.response.toMenuNames import com.eatssu.android.data.remote.dto.response.toDomain import com.eatssu.android.data.remote.service.MealService import com.eatssu.android.domain.model.Menu import com.eatssu.android.domain.repository.MealRepository +import com.eatssu.common.enums.AppLanguage import com.eatssu.common.enums.Restaurant import com.eatssu.common.enums.Time +import kotlinx.coroutines.flow.first import javax.inject.Inject +private const val ENGLISH_MEAL_LANGUAGE = "EN" + class MealRepositoryImpl @Inject constructor( private val mealService: MealService, + private val settingDataStore: SettingDataStore, ) : MealRepository { override suspend fun getTodayMeal( @@ -20,7 +28,10 @@ class MealRepositoryImpl @Inject constructor( restaurant: String, time: String ): List> { - return mealService.getTodayMeal(date, restaurant, time).orEmptyList().toDomain() + val language = getMealLanguage() + return mealService.getTodayMeal(date, restaurant, time, language) + .orEmptyList() + .toDomain(showMainMenusOnly = language != null) } override suspend fun getTodayMenuList( @@ -28,9 +39,25 @@ class MealRepositoryImpl @Inject constructor( restaurant: Restaurant, time: Time ): List { - return mealService.getTodayMeal(date, restaurant.toString(), time.toString()) - .map { it.mapTodayMenuResponseToMenu() } + val language = getMealLanguage() + return mealService.getTodayMeal(date, restaurant.toString(), time.toString(), language) + .map { it.mapTodayMenuResponseToMenu(showMainMenusOnly = language != null) } .orEmptyList() } + override suspend fun getMealMenuNames(mealId: Long): List { + return mealService.getMealMenusInfo(mealId, getMealLanguage()) + .orNull() + ?.toMenuNames() + .orEmpty() + } + + private suspend fun getMealLanguage(): String? { + return when (settingDataStore.appLanguage.first()) { + AppLanguage.KOREAN -> null + AppLanguage.ENGLISH, + AppLanguage.JAPANESE, + AppLanguage.VIETNAMESE -> ENGLISH_MEAL_LANGUAGE + } + } } diff --git a/app/src/main/java/com/eatssu/android/data/remote/service/MealService.kt b/app/src/main/java/com/eatssu/android/data/remote/service/MealService.kt index e75e2c3a3..01b18eef0 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/service/MealService.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/service/MealService.kt @@ -1,8 +1,10 @@ package com.eatssu.android.data.remote.service import com.eatssu.android.data.model.ApiResult +import com.eatssu.android.data.remote.dto.response.GetMealMenusInfoResponse import com.eatssu.android.data.remote.dto.response.GetMealResponse import retrofit2.http.GET +import retrofit2.http.Path import retrofit2.http.Query interface MealService { @@ -14,6 +16,12 @@ interface MealService { @Query("date") date: String, @Query("restaurant") restaurant: String, @Query("time") time: String, + @Query("language") language: String? = null, ): ApiResult> + @GET("meals/{mealId}/menus-info") + suspend fun getMealMenusInfo( + @Path("mealId") mealId: Long, + @Query("language") language: String? = null, + ): ApiResult } diff --git a/app/src/main/java/com/eatssu/android/domain/repository/MealRepository.kt b/app/src/main/java/com/eatssu/android/domain/repository/MealRepository.kt index d34c623c5..7667f7d76 100644 --- a/app/src/main/java/com/eatssu/android/domain/repository/MealRepository.kt +++ b/app/src/main/java/com/eatssu/android/domain/repository/MealRepository.kt @@ -23,4 +23,9 @@ interface MealRepository { restaurant: Restaurant, time: Time, ): List + + /** + * 변동 식단 상세 화면에 표시할 전체 메뉴 이름을 가져온다. + */ + suspend fun getMealMenuNames(mealId: Long): List } diff --git a/app/src/main/java/com/eatssu/android/domain/usecase/menu/GetMealMenuNamesUseCase.kt b/app/src/main/java/com/eatssu/android/domain/usecase/menu/GetMealMenuNamesUseCase.kt new file mode 100644 index 000000000..80e384178 --- /dev/null +++ b/app/src/main/java/com/eatssu/android/domain/usecase/menu/GetMealMenuNamesUseCase.kt @@ -0,0 +1,11 @@ +package com.eatssu.android.domain.usecase.menu + +import com.eatssu.android.domain.repository.MealRepository +import javax.inject.Inject + +class GetMealMenuNamesUseCase @Inject constructor( + private val mealRepository: MealRepository, +) { + suspend operator fun invoke(mealId: Long): List = + mealRepository.getMealMenuNames(mealId) +} diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt index 375468809..04d95642c 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListScreen.kt @@ -91,8 +91,8 @@ fun ReviewListScreen( ) { val context = LocalContext.current - LaunchedEffect(key1 = menuType, key2 = id) { - viewModel.getReview(menuType, id) + LaunchedEffect(key1 = menuType, key2 = id, key3 = menuName) { + viewModel.getReview(menuType, id, menuName) } // Screen View 로깅 @@ -107,7 +107,7 @@ fun ReviewListScreen( LaunchedEffect(refreshNonce) { if (refreshNonce == 0L) return@LaunchedEffect // 리뷰 작성 화면에서 돌아온 경우, 리스트/정보를 강제로 갱신한다. - viewModel.getReview(menuType, id) + viewModel.getReview(menuType, id, menuName) reviewPagingItems.refresh() } @@ -279,6 +279,7 @@ internal fun ReviewListScreen( is UiState.Success -> { val info = uiState.data.reviewInfo + val displayMenuName = uiState.data.menuName ?: menuName val loadState = reviewPagingItems.loadState val isInitialLoading = loadState.refresh is LoadState.Loading @@ -289,7 +290,7 @@ internal fun ReviewListScreen( state = reviewListScrollState, ) { item { - ReviewInfoContent(menuName, info) + ReviewInfoContent(displayMenuName, info) } item { diff --git a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt index dc6c6ae80..15e7b2420 100644 --- a/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt +++ b/app/src/main/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModel.kt @@ -8,6 +8,7 @@ import com.eatssu.android.R import com.eatssu.android.domain.model.Review import com.eatssu.android.domain.model.ReviewInfo import com.eatssu.android.domain.usecase.auth.GetAccessTokenUseCase +import com.eatssu.android.domain.usecase.menu.GetMealMenuNamesUseCase import com.eatssu.android.domain.usecase.review.DeleteReviewUseCase import com.eatssu.android.domain.usecase.review.GetReviewInfoUseCase import com.eatssu.android.domain.usecase.review.GetReviewListPagedUseCase @@ -38,6 +39,7 @@ class ReviewListViewModel @Inject constructor( private val deleteReviewUseCase: DeleteReviewUseCase, private val getReviewTranslationUseCase: GetReviewTranslationUseCase, private val getAccessTokenUseCase: GetAccessTokenUseCase, + private val getMealMenuNamesUseCase: GetMealMenuNamesUseCase, ) : ViewModel() { val isLoggedIn: Boolean @@ -58,6 +60,7 @@ class ReviewListViewModel @Inject constructor( extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, ) + private var fallbackMenuName: String = "" @OptIn(ExperimentalCoroutinesApi::class) val reviewPagingData: Flow> = _loadParams @@ -66,21 +69,42 @@ class ReviewListViewModel @Inject constructor( } .cachedIn(viewModelScope) - fun getReview(menuType: MenuType, itemId: Long) { + fun getReview( + menuType: MenuType, + itemId: Long, + menuName: String = "", + ) { + fallbackMenuName = menuName + // 동일 파라미터로 다시 진입(작성/수정 후 popBackStack)해도 // 항상 페이징 소스를 새로 만들 수 있도록 SharedFlow로 트리거한다. _loadParams.tryEmit(menuType to itemId) viewModelScope.launch { - loadReviewInfo(menuType, itemId) + loadReviewInfo(menuType, itemId, menuName) } } - private suspend fun loadReviewInfo(menuType: MenuType, itemId: Long) { + private suspend fun loadReviewInfo( + menuType: MenuType, + itemId: Long, + fallbackMenuName: String, + ) { _uiState.value = UiState.Loading try { val reviewInfo = getReviewInfoUseCase(menuType, itemId) - _uiState.value = UiState.Success(ReviewListState(reviewInfo)) + val detailedMenuName = when (menuType) { + MenuType.FIXED -> fallbackMenuName + MenuType.VARIABLE -> getMealMenuNamesUseCase(itemId) + .joinToString(separator = ", ") + .ifBlank { fallbackMenuName } + } + _uiState.value = UiState.Success( + ReviewListState( + reviewInfo = reviewInfo, + menuName = detailedMenuName.takeIf { it.isNotBlank() }, + ) + ) } catch (e: Exception) { _uiState.value = UiState.Error _uiEvent.emit( @@ -111,7 +135,13 @@ class ReviewListViewModel @Inject constructor( // 정보 갱신 val currentParams = _loadParams.replayCache.lastOrNull() - if (currentParams != null) loadReviewInfo(currentParams.first, currentParams.second) + if (currentParams != null) { + loadReviewInfo( + menuType = currentParams.first, + itemId = currentParams.second, + fallbackMenuName = fallbackMenuName, + ) + } } } @@ -161,6 +191,7 @@ class ReviewListViewModel @Inject constructor( data class ReviewListState( val reviewInfo: ReviewInfo? = null, + val menuName: String? = null, ) diff --git a/app/src/test/java/com/eatssu/android/data/remote/repository/MealRepositoryImplBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/data/remote/repository/MealRepositoryImplBehaviorSpec.kt index e282acf51..8e816455a 100644 --- a/app/src/test/java/com/eatssu/android/data/remote/repository/MealRepositoryImplBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/data/remote/repository/MealRepositoryImplBehaviorSpec.kt @@ -1,16 +1,22 @@ package com.eatssu.android.data.remote.repository +import com.eatssu.android.data.local.SettingDataStore import com.eatssu.android.data.model.ApiResult +import com.eatssu.android.data.remote.dto.response.GetMealMenusInfoResponse import com.eatssu.android.data.remote.dto.response.GetMealResponse import com.eatssu.android.data.remote.dto.response.MenusInformationList import com.eatssu.android.data.remote.service.MealService import com.eatssu.android.test.AppBehaviorSpec +import com.eatssu.common.enums.AppLanguage import com.eatssu.common.enums.Restaurant import com.eatssu.common.enums.Time import io.kotest.matchers.shouldBe import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @OptIn(ExperimentalCoroutinesApi::class) @@ -18,7 +24,11 @@ class MealRepositoryImplBehaviorSpec : AppBehaviorSpec({ given("MealRepositoryImpl") { val mealService = mockk() - val repository = MealRepositoryImpl(mealService) + val settingDataStore = mockk() + val appLanguage = MutableStateFlow(AppLanguage.KOREAN) + val repository = MealRepositoryImpl(mealService, settingDataStore) + + every { settingDataStore.appLanguage } returns appLanguage val mealResponse = listOf( GetMealResponse( @@ -26,17 +36,21 @@ class MealRepositoryImplBehaviorSpec : AppBehaviorSpec({ price = 5000, rating = 4.0, briefMenus = listOf( - MenusInformationList(menuId = 1L, name = "제육"), + MenusInformationList(menuId = 1L, name = "제육", isMain = true), MenusInformationList(menuId = 2L, name = "계란찜"), ), ) ) - `when`("getTodayMeal API가 성공하면") { - coEvery { mealService.getTodayMeal("2025-01-01", "HAKSIK", "LUNCH") } returns ApiResult.Success(mealResponse) + `when`("한국어로 getTodayMeal API가 성공하면") { + coEvery { + mealService.getTodayMeal("2025-01-01", "HAKSIK", "LUNCH", null) + } returns ApiResult.Success(mealResponse) - then("메뉴 이름 리스트 리스트로 변환한다") { + then("language를 보내지 않고 모든 메뉴를 반환한다") { runTest { + appLanguage.value = AppLanguage.KOREAN + repository.getTodayMeal("2025-01-01", "HAKSIK", "LUNCH") shouldBe listOf( listOf("제육", "계란찜") ) @@ -45,37 +59,139 @@ class MealRepositoryImplBehaviorSpec : AppBehaviorSpec({ } `when`("getTodayMeal API가 실패하면") { - coEvery { mealService.getTodayMeal("2025-01-01", "HAKSIK", "LUNCH") } returns ApiResult.Failure(500, "err") + coEvery { + mealService.getTodayMeal("2025-01-02", "HAKSIK", "LUNCH", null) + } returns ApiResult.Failure(500, "err") then("빈 리스트를 반환한다") { runTest { - repository.getTodayMeal("2025-01-01", "HAKSIK", "LUNCH") shouldBe emptyList() + appLanguage.value = AppLanguage.KOREAN + + repository.getTodayMeal("2025-01-02", "HAKSIK", "LUNCH") shouldBe emptyList() } } } - `when`("getTodayMenuList API가 성공하면") { + `when`("한국어로 getTodayMenuList API가 성공하면") { coEvery { - mealService.getTodayMeal("2025-01-01", Restaurant.HAKSIK.toString(), Time.LUNCH.toString()) + mealService.getTodayMeal( + "2025-01-03", + Restaurant.HAKSIK.toString(), + Time.LUNCH.toString(), + null, + ) } returns ApiResult.Success(mealResponse) - then("Menu 도메인 리스트로 변환한다") { + then("모든 메뉴를 Menu 도메인으로 변환한다") { runTest { - val result = repository.getTodayMenuList("2025-01-01", Restaurant.HAKSIK, Time.LUNCH) + appLanguage.value = AppLanguage.KOREAN + + val result = repository.getTodayMenuList( + "2025-01-03", + Restaurant.HAKSIK, + Time.LUNCH, + ) result.size shouldBe 1 result.first().name shouldBe "제육, 계란찜" } } } - `when`("getTodayMenuList API가 실패하면") { + `when`("영어로 식단을 조회하면") { + val translatedResponse = listOf( + GetMealResponse( + mealId = 11L, + price = 5000, + rating = 4.0, + briefMenus = listOf( + MenusInformationList(menuId = 1L, name = "Spicy Pork", isMain = true), + MenusInformationList(menuId = 2L, name = "계란찜"), + ), + ) + ) coEvery { - mealService.getTodayMeal("2025-01-01", Restaurant.HAKSIK.toString(), Time.LUNCH.toString()) - } returns ApiResult.UnknownError(IllegalStateException("boom")) + mealService.getTodayMeal("2025-01-04", "HAKSIK", "LUNCH", "EN") + } returns ApiResult.Success(translatedResponse) - then("빈 리스트를 반환한다") { + then("language=EN을 보내고 대표메뉴만 표시한다") { + runTest { + appLanguage.value = AppLanguage.ENGLISH + + repository.getTodayMeal("2025-01-04", "HAKSIK", "LUNCH") shouldBe listOf( + listOf("Spicy Pork") + ) + } + } + } + + `when`("대표메뉴 번역 데이터가 없는 식단을 영어로 조회하면") { + val untranslatedResponse = listOf( + GetMealResponse( + mealId = 12L, + briefMenus = listOf( + MenusInformationList(menuId = 1L, name = "제육"), + MenusInformationList(menuId = 2L, name = "계란찜"), + ), + ) + ) + coEvery { + mealService.getTodayMeal("2025-01-05", "HAKSIK", "LUNCH", "EN") + } returns ApiResult.Success(untranslatedResponse) + + then("기존 한국어 전체 메뉴를 표시한다") { + runTest { + appLanguage.value = AppLanguage.ENGLISH + + repository.getTodayMeal("2025-01-05", "HAKSIK", "LUNCH") shouldBe listOf( + listOf("제육", "계란찜") + ) + } + } + } + + `when`("일본어와 베트남어로 식단을 조회하면") { + coEvery { + mealService.getTodayMeal(any(), "HAKSIK", "LUNCH", "EN") + } returns ApiResult.Success(mealResponse) + + then("두 언어 모두 임시로 language=EN을 보낸다") { + runTest { + appLanguage.value = AppLanguage.JAPANESE + repository.getTodayMeal("2025-01-06", "HAKSIK", "LUNCH") + + appLanguage.value = AppLanguage.VIETNAMESE + repository.getTodayMeal("2025-01-07", "HAKSIK", "LUNCH") + + coVerify { + mealService.getTodayMeal("2025-01-06", "HAKSIK", "LUNCH", "EN") + mealService.getTodayMeal("2025-01-07", "HAKSIK", "LUNCH", "EN") + } + } + } + } + + `when`("변동식단 상세 메뉴를 영어로 조회하면") { + coEvery { + mealService.getMealMenusInfo(5637L, "EN") + } returns ApiResult.Success( + GetMealMenusInfoResponse( + briefMenus = listOf( + MenusInformationList(name = "Beef Shabu Rice Noodles", isMain = true), + MenusInformationList(name = "팔춘권튀김"), + MenusInformationList(name = "미니밥"), + ) + ) + ) + + then("대표메뉴 영문과 나머지 메뉴 한국어를 함께 반환한다") { runTest { - repository.getTodayMenuList("2025-01-01", Restaurant.HAKSIK, Time.LUNCH) shouldBe emptyList() + appLanguage.value = AppLanguage.ENGLISH + + repository.getMealMenuNames(5637L) shouldBe listOf( + "Beef Shabu Rice Noodles", + "팔춘권튀김", + "미니밥", + ) } } } diff --git a/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt index 211237bfb..9812d3cfe 100644 --- a/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/presentation/cafeteria/review/list/ReviewListViewModelBehaviorSpec.kt @@ -5,6 +5,7 @@ import app.cash.turbine.test import com.eatssu.android.R import com.eatssu.android.domain.model.ReviewTranslation import com.eatssu.android.domain.usecase.auth.GetAccessTokenUseCase +import com.eatssu.android.domain.usecase.menu.GetMealMenuNamesUseCase import com.eatssu.android.domain.usecase.review.DeleteReviewUseCase import com.eatssu.android.domain.usecase.review.GetReviewInfoUseCase import com.eatssu.android.domain.usecase.review.GetReviewListPagedUseCase @@ -35,9 +36,11 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ val deleteReviewUseCase = mockk() val getReviewTranslationUseCase = mockk() val getAccessTokenUseCase = mockk() + val getMealMenuNamesUseCase = mockk() every { getReviewListPagedUseCase(any(), any()) } returns flowOf(PagingData.empty()) every { getAccessTokenUseCase() } returns "access-token" + coEvery { getMealMenuNamesUseCase(any()) } returns emptyList() `when`("리뷰 정보를 정상 조회하면") { val viewModel = ReviewListViewModel( @@ -46,6 +49,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) val info = sampleReviewInfo() coEvery { getReviewInfoUseCase(MenuType.FIXED, 100L) } returns info @@ -60,6 +64,66 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ } } + `when`("변동식단 리뷰 정보를 영어로 조회하면") { + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + getAccessTokenUseCase, + getMealMenuNamesUseCase, + ) + val info = sampleReviewInfo() + coEvery { getReviewInfoUseCase(MenuType.VARIABLE, 5637L) } returns info + coEvery { getMealMenuNamesUseCase(5637L) } returns listOf( + "Beef Shabu Rice Noodles", + "팔춘권튀김", + "미니밥", + ) + + then("상세 API의 영문 대표메뉴와 한국어 나머지 메뉴를 함께 표시한다") { + runTest { + viewModel.getReview(MenuType.VARIABLE, 5637L, "Beef Shabu Rice Noodles") + advanceUntilIdle() + + viewModel.uiState.value shouldBe UiState.Success( + ReviewListState( + reviewInfo = info, + menuName = "Beef Shabu Rice Noodles, 팔춘권튀김, 미니밥", + ) + ) + } + } + } + + `when`("변동식단 상세 메뉴 조회 결과가 비어 있으면") { + val viewModel = ReviewListViewModel( + getReviewInfoUseCase, + getReviewListPagedUseCase, + deleteReviewUseCase, + getReviewTranslationUseCase, + getAccessTokenUseCase, + getMealMenuNamesUseCase, + ) + val info = sampleReviewInfo() + coEvery { getReviewInfoUseCase(MenuType.VARIABLE, 5638L) } returns info + coEvery { getMealMenuNamesUseCase(5638L) } returns emptyList() + + then("목록 화면에서 전달된 기존 메뉴명을 유지한다") { + runTest { + viewModel.getReview(MenuType.VARIABLE, 5638L, "제육, 계란찜") + advanceUntilIdle() + + viewModel.uiState.value shouldBe UiState.Success( + ReviewListState( + reviewInfo = info, + menuName = "제육, 계란찜", + ) + ) + } + } + } + `when`("리뷰 정보 조회에서 예외가 발생하면") { val viewModel = ReviewListViewModel( getReviewInfoUseCase, @@ -67,6 +131,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) coEvery { getReviewInfoUseCase(MenuType.VARIABLE, 101L) } throws IllegalStateException("boom") @@ -91,6 +156,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) coEvery { deleteReviewUseCase(55L) } returns false @@ -114,6 +180,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) coEvery { getReviewInfoUseCase(MenuType.FIXED, 300L) } returns sampleReviewInfo(count = 3) coEvery { deleteReviewUseCase(56L) } returns true @@ -142,6 +209,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) coEvery { deleteReviewUseCase(77L) } returns true @@ -167,6 +235,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) coEvery { getReviewTranslationUseCase(255L, "EN") } returns ReviewTranslation( reviewId = 255L, @@ -199,6 +268,7 @@ class ReviewListViewModelBehaviorSpec : AppBehaviorSpec({ deleteReviewUseCase, getReviewTranslationUseCase, getAccessTokenUseCase, + getMealMenuNamesUseCase, ) coEvery { getReviewTranslationUseCase(256L, "EN") } returns ReviewTranslation( reviewId = 256L, From 53ef4b69a7885e427204c4ab47ea98cb8576203d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 20:12:57 +0900 Subject: [PATCH 09/11] release: 3.2.14 QA build 69 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 353842666..c4655a922 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,8 +30,8 @@ android { applicationId = "com.eatssu.android" minSdk = 28 targetSdk = 37 - versionCode = 66 - versionName = "3.2.13" + versionCode = 69 + versionName = "3.2.14" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } From 287e029304ba3e027d1ef88770f2040906cdf398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 23:16:38 +0900 Subject: [PATCH 10/11] fix: request localized fixed menus --- .../remote/repository/MenuRepositoryImpl.kt | 16 +++++++- .../data/remote/service/MenuService.kt | 3 +- .../MenuRepositoryImplBehaviorSpec.kt | 39 +++++++++++++++++-- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/eatssu/android/data/remote/repository/MenuRepositoryImpl.kt b/app/src/main/java/com/eatssu/android/data/remote/repository/MenuRepositoryImpl.kt index 0f87682f4..f839036fb 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/repository/MenuRepositoryImpl.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/repository/MenuRepositoryImpl.kt @@ -1,19 +1,31 @@ package com.eatssu.android.data.remote.repository +import com.eatssu.android.data.local.SettingDataStore import com.eatssu.android.data.model.map import com.eatssu.android.data.model.orEmptyList import com.eatssu.android.data.remote.dto.response.mapFixedMenuResponseToMenu import com.eatssu.android.data.remote.service.MenuService import com.eatssu.android.domain.model.Menu import com.eatssu.android.domain.repository.MenuRepository +import com.eatssu.common.enums.AppLanguage import com.eatssu.common.enums.Restaurant +import kotlinx.coroutines.flow.first import javax.inject.Inject +private const val ENGLISH_FIXED_MENU_LANGUAGE = "EN" + class MenuRepositoryImpl @Inject constructor( - private val menuService: MenuService + private val menuService: MenuService, + private val settingDataStore: SettingDataStore, ) : MenuRepository { override suspend fun getFixedMenuList(restaurant: Restaurant): List { - return menuService.getFixMenu(restaurant.toString()) + val language = when (settingDataStore.appLanguage.first()) { + AppLanguage.KOREAN -> null + AppLanguage.ENGLISH, + AppLanguage.JAPANESE, + AppLanguage.VIETNAMESE -> ENGLISH_FIXED_MENU_LANGUAGE + } + return menuService.getFixMenu(restaurant.toString(), language) .map { it.mapFixedMenuResponseToMenu() } .orEmptyList() } diff --git a/app/src/main/java/com/eatssu/android/data/remote/service/MenuService.kt b/app/src/main/java/com/eatssu/android/data/remote/service/MenuService.kt index 4a24d73cc..50f208e79 100644 --- a/app/src/main/java/com/eatssu/android/data/remote/service/MenuService.kt +++ b/app/src/main/java/com/eatssu/android/data/remote/service/MenuService.kt @@ -13,6 +13,7 @@ interface MenuService { @GET("menus") suspend fun getFixMenu( @Query("restaurant") restaurant: String, + @Query("language") language: String? = null, ): ApiResult -} \ No newline at end of file +} diff --git a/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt index df74ebe5f..bc3a0eb2a 100644 --- a/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt @@ -1,16 +1,20 @@ package com.eatssu.android.data.remote.repository +import com.eatssu.android.data.local.SettingDataStore import com.eatssu.android.data.model.ApiResult import com.eatssu.android.data.remote.dto.response.CategoryMenuListCollection import com.eatssu.android.data.remote.dto.response.GetFixedMenuResponse import com.eatssu.android.data.remote.dto.response.MenuInformationList import com.eatssu.android.data.remote.service.MenuService import com.eatssu.android.test.AppBehaviorSpec +import com.eatssu.common.enums.AppLanguage import com.eatssu.common.enums.Restaurant import io.kotest.matchers.shouldBe import io.mockk.coEvery +import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @OptIn(ExperimentalCoroutinesApi::class) @@ -18,7 +22,10 @@ class MenuRepositoryImplBehaviorSpec : AppBehaviorSpec({ given("MenuRepositoryImpl") { val menuService = mockk() - val repository = MenuRepositoryImpl(menuService) + val settingDataStore = mockk() + val languageFlow = MutableStateFlow(AppLanguage.KOREAN) + every { settingDataStore.appLanguage } returns languageFlow + val repository = MenuRepositoryImpl(menuService, settingDataStore) `when`("고정 메뉴 API가 성공하면") { val response = GetFixedMenuResponse( @@ -36,7 +43,9 @@ class MenuRepositoryImplBehaviorSpec : AppBehaviorSpec({ ) ) ) - coEvery { menuService.getFixMenu(Restaurant.SNACK_CORNER.toString()) } returns ApiResult.Success(response) + coEvery { + menuService.getFixMenu(Restaurant.SNACK_CORNER.toString(), null) + } returns ApiResult.Success(response) then("도메인 Menu 리스트로 매핑한다") { runTest { @@ -45,11 +54,35 @@ class MenuRepositoryImplBehaviorSpec : AppBehaviorSpec({ result.first().name shouldBe "돈까스" } } + + then("영어 설정이면 language=EN을 전달한다") { + runTest { + languageFlow.value = AppLanguage.ENGLISH + coEvery { + menuService.getFixMenu(Restaurant.SNACK_CORNER.toString(), "EN") + } returns ApiResult.Success(response) + + repository.getFixedMenuList(Restaurant.SNACK_CORNER).first().name shouldBe "돈까스" + } + } + + then("일본어와 베트남어 설정도 임시로 language=EN을 전달한다") { + runTest { + listOf(AppLanguage.JAPANESE, AppLanguage.VIETNAMESE).forEach { language -> + languageFlow.value = language + coEvery { + menuService.getFixMenu(Restaurant.SNACK_CORNER.toString(), "EN") + } returns ApiResult.Success(response) + + repository.getFixedMenuList(Restaurant.SNACK_CORNER).first().name shouldBe "돈까스" + } + } + } } `when`("고정 메뉴 API가 실패하면") { coEvery { - menuService.getFixMenu(Restaurant.SNACK_CORNER.toString()) + menuService.getFixMenu(Restaurant.SNACK_CORNER.toString(), null) } returns ApiResult.Failure(500, "err") then("빈 리스트를 반환한다") { From 3467eacc18b54baa15fd7e80de01b75ad7d9c524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=EC=9E=AC=EC=B0=AC?= Date: Wed, 2 Sep 2026 23:43:51 +0900 Subject: [PATCH 11/11] test: isolate fixed menu language cases --- .../data/remote/repository/MenuRepositoryImplBehaviorSpec.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt b/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt index bc3a0eb2a..80de9a373 100644 --- a/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt +++ b/app/src/test/java/com/eatssu/android/data/remote/repository/MenuRepositoryImplBehaviorSpec.kt @@ -87,6 +87,7 @@ class MenuRepositoryImplBehaviorSpec : AppBehaviorSpec({ then("빈 리스트를 반환한다") { runTest { + languageFlow.value = AppLanguage.KOREAN repository.getFixedMenuList(Restaurant.SNACK_CORNER) shouldBe emptyList() } }