From ecc7d5c49dc2f4166251f3510ff7380bcb350d58 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Wed, 25 Mar 2026 23:58:30 +0900 Subject: [PATCH 01/15] =?UTF-8?q?[feat/#163]=20mypageDto=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/mypage/MypageRequestDto.kt | 21 ++++++++++++ .../dto/response/mypage/MypageResponseDto.kt | 34 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 app/src/main/java/com/paw/key/data/dto/request/mypage/MypageRequestDto.kt create mode 100644 app/src/main/java/com/paw/key/data/dto/response/mypage/MypageResponseDto.kt diff --git a/app/src/main/java/com/paw/key/data/dto/request/mypage/MypageRequestDto.kt b/app/src/main/java/com/paw/key/data/dto/request/mypage/MypageRequestDto.kt new file mode 100644 index 00000000..51b72208 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/request/mypage/MypageRequestDto.kt @@ -0,0 +1,21 @@ +package com.paw.key.data.dto.request.mypage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateUserRequestDto( + @SerialName("name") val name: String, + @SerialName("birth") val birth: String, + @SerialName("gender") val gender: String, +) + +@Serializable +data class UpdatePetRequestDto( + @SerialName("name") val name: String, + @SerialName("birth") val birth: String, + @SerialName("gender") val gender: String, + @SerialName("isNeutered") val isNeutered: Boolean, + @SerialName("breedId") val breedId: Int, + @SerialName("imageId") val imageId: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/dto/response/mypage/MypageResponseDto.kt b/app/src/main/java/com/paw/key/data/dto/response/mypage/MypageResponseDto.kt new file mode 100644 index 00000000..bf6e6563 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/dto/response/mypage/MypageResponseDto.kt @@ -0,0 +1,34 @@ +package com.paw.key.data.dto.response.mypage + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RoutePostListResponseDto( + @SerialName("posts") val posts: List, +) + +@Serializable +data class RoutePostDto( + @SerialName("postId") val postId: Int, + @SerialName("regionName") val regionName: String, + @SerialName("title") val title: String, + @SerialName("date") val date: String, + @SerialName("durationMinutes") val durationMinutes: Int, + @SerialName("isLiked") val isLiked: Boolean, + @SerialName("imageUrl") val imageUrl: String, +) + +@Serializable +data class ReviewPostListResponseDto( + @SerialName("posts") val posts: List, +) + +@Serializable +data class ReviewPostDto( + @SerialName("postId") val postId: Int, + @SerialName("title") val title: String, + @SerialName("regionName") val regionName: String, + @SerialName("date") val date: String, + @SerialName("categoryOptionSummary") val categoryOptionSummary: List, +) \ No newline at end of file From a41738fbbc24173ca38fd237b2decb7a85c10c33 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Wed, 25 Mar 2026 23:58:52 +0900 Subject: [PATCH 02/15] =?UTF-8?q?[feat/#163]=20mypageRepository=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/MypageRepositoryImpl.kt | 56 +++++++++++++++++++ .../repository/mypage/MypageRepository.kt | 12 ++++ 2 files changed, 68 insertions(+) create mode 100644 app/src/main/java/com/paw/key/data/repositoryimpl/mypage/MypageRepositoryImpl.kt create mode 100644 app/src/main/java/com/paw/key/domain/repository/mypage/MypageRepository.kt diff --git a/app/src/main/java/com/paw/key/data/repositoryimpl/mypage/MypageRepositoryImpl.kt b/app/src/main/java/com/paw/key/data/repositoryimpl/mypage/MypageRepositoryImpl.kt new file mode 100644 index 00000000..21f4818d --- /dev/null +++ b/app/src/main/java/com/paw/key/data/repositoryimpl/mypage/MypageRepositoryImpl.kt @@ -0,0 +1,56 @@ +package com.paw.key.data.repository.mypage + +import com.paw.key.core.util.suspendRunCatching +import com.paw.key.data.dto.request.mypage.UpdatePetRequestDto +import com.paw.key.data.dto.request.mypage.UpdateUserRequestDto +import com.paw.key.data.remote.datasource.mypage.MypageDataSource +import com.paw.key.domain.entity.mypage.ReviewPostEntity +import com.paw.key.domain.entity.mypage.RoutePostEntity +import com.paw.key.domain.entity.mypage.toEntity +import com.paw.key.domain.repository.mypage.MypageRepository +import javax.inject.Inject + +class MypageRepositoryImpl @Inject constructor( + private val dataSource: MypageDataSource, +) : MypageRepository { + + override suspend fun updateUser(name: String, birth: String, gender: String): Result = + suspendRunCatching { + dataSource.updateUser(UpdateUserRequestDto(name = name, birth = birth, gender = gender)) + } + + override suspend fun updatePet( + name: String, + birth: String, + gender: String, + isNeutered: Boolean, + breedId: Int, + imageId: Int, + ): Result = suspendRunCatching { + dataSource.updatePet( + UpdatePetRequestDto( + name = name, + birth = birth, + gender = gender, + isNeutered = isNeutered, + breedId = breedId, + imageId = imageId, + ) + ) + } + + override suspend fun getMyRoutes(): Result> = + suspendRunCatching { + dataSource.getMyRoutes().data?.posts?.map { it.toEntity() } ?: emptyList() + } + + override suspend fun getLikedPosts(): Result> = + suspendRunCatching { + dataSource.getLikedPosts().data?.posts?.map { it.toEntity() } ?: emptyList() + } + + override suspend fun getMyReviews(): Result> = + suspendRunCatching { + dataSource.getMyReviews().data?.posts?.map { it.toEntity() } ?: emptyList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/mypage/MypageRepository.kt b/app/src/main/java/com/paw/key/domain/repository/mypage/MypageRepository.kt new file mode 100644 index 00000000..a469f027 --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/repository/mypage/MypageRepository.kt @@ -0,0 +1,12 @@ +package com.paw.key.domain.repository.mypage + +import com.paw.key.domain.entity.mypage.ReviewPostEntity +import com.paw.key.domain.entity.mypage.RoutePostEntity + +interface MypageRepository { + suspend fun updateUser(name: String, birth: String, gender: String): Result + suspend fun updatePet(name: String, birth: String, gender: String, isNeutered: Boolean, breedId: Int, imageId: Int): Result + suspend fun getMyRoutes(): Result> + suspend fun getLikedPosts(): Result> + suspend fun getMyReviews(): Result> +} \ No newline at end of file From 733e6f3d74e2d578b86a0b5b0c8e30eb89af8b9f Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Wed, 25 Mar 2026 23:59:10 +0900 Subject: [PATCH 03/15] =?UTF-8?q?[feat/#163]=20mypagedatasource=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datasourceimpl/MypageDataSourceImpl.kt | 29 +++++++++++++++++++ .../datasource/mypage/MypageDataSource.kt | 15 ++++++++++ 2 files changed, 44 insertions(+) create mode 100644 app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/MypageDataSourceImpl.kt create mode 100644 app/src/main/java/com/paw/key/data/remote/datasource/mypage/MypageDataSource.kt diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/MypageDataSourceImpl.kt b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/MypageDataSourceImpl.kt new file mode 100644 index 00000000..41713a30 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/datasourceimpl/MypageDataSourceImpl.kt @@ -0,0 +1,29 @@ +package com.paw.key.data.remote.datasource.mypage + +import com.paw.key.data.dto.request.mypage.UpdatePetRequestDto +import com.paw.key.data.dto.request.mypage.UpdateUserRequestDto +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.mypage.ReviewPostListResponseDto +import com.paw.key.data.dto.response.mypage.RoutePostListResponseDto +import com.paw.key.data.service.mypage.MypageService +import javax.inject.Inject + +class MypageDataSourceImpl @Inject constructor( + private val mypageService: MypageService, +) : MypageDataSource { + + override suspend fun updateUser(body: UpdateUserRequestDto): BaseResponse = + mypageService.updateUser(body) + + override suspend fun updatePet(body: UpdatePetRequestDto): BaseResponse = + mypageService.updatePet(body) + + override suspend fun getMyRoutes(): BaseResponse = + mypageService.getMyRoutes() + + override suspend fun getLikedPosts(): BaseResponse = + mypageService.getLikedPosts() + + override suspend fun getMyReviews(): BaseResponse = + mypageService.getMyReviews() +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/remote/datasource/mypage/MypageDataSource.kt b/app/src/main/java/com/paw/key/data/remote/datasource/mypage/MypageDataSource.kt new file mode 100644 index 00000000..6acd5c9d --- /dev/null +++ b/app/src/main/java/com/paw/key/data/remote/datasource/mypage/MypageDataSource.kt @@ -0,0 +1,15 @@ +package com.paw.key.data.remote.datasource.mypage + +import com.paw.key.data.dto.request.mypage.UpdatePetRequestDto +import com.paw.key.data.dto.request.mypage.UpdateUserRequestDto +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.mypage.ReviewPostListResponseDto +import com.paw.key.data.dto.response.mypage.RoutePostListResponseDto + +interface MypageDataSource { + suspend fun updateUser(body: UpdateUserRequestDto): BaseResponse + suspend fun updatePet(body: UpdatePetRequestDto): BaseResponse + suspend fun getMyRoutes(): BaseResponse + suspend fun getLikedPosts(): BaseResponse + suspend fun getMyReviews(): BaseResponse +} \ No newline at end of file From c02e7a5a292aeb744254fe14b1958f8ab5f4c703 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Wed, 25 Mar 2026 23:59:25 +0900 Subject: [PATCH 04/15] [feat/#163] mypage entity --- .../key/domain/entity/mypage/MypageEntity.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/src/main/java/com/paw/key/domain/entity/mypage/MypageEntity.kt diff --git a/app/src/main/java/com/paw/key/domain/entity/mypage/MypageEntity.kt b/app/src/main/java/com/paw/key/domain/entity/mypage/MypageEntity.kt new file mode 100644 index 00000000..8770aebb --- /dev/null +++ b/app/src/main/java/com/paw/key/domain/entity/mypage/MypageEntity.kt @@ -0,0 +1,40 @@ +package com.paw.key.domain.entity.mypage + +import com.paw.key.data.dto.response.mypage.ReviewPostDto +import com.paw.key.data.dto.response.mypage.RoutePostDto + +data class RoutePostEntity( + val postId: Int, + val regionName: String, + val title: String, + val date: String, + val durationMinutes: Int, + val isLiked: Boolean, + val imageUrl: String, +) + +data class ReviewPostEntity( + val postId: Int, + val title: String, + val regionName: String, + val date: String, + val categoryOptionSummary: List, +) + +fun RoutePostDto.toEntity() = RoutePostEntity( + postId = postId, + regionName = regionName, + title = title, + date = date, + durationMinutes = durationMinutes, + isLiked = isLiked, + imageUrl = imageUrl, +) + +fun ReviewPostDto.toEntity() = ReviewPostEntity( + postId = postId, + title = title, + regionName = regionName, + date = date, + categoryOptionSummary = categoryOptionSummary, +) \ No newline at end of file From a74d9926956f12b8a9db1dc76da2209d214ea616 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:00:06 +0900 Subject: [PATCH 05/15] =?UTF-8?q?[feat/#163]=20mypage=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20module=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/paw/key/data/di/NetworkModule.kt | 2 +- .../java/com/paw/key/data/di/RepositoryModule.kt | 16 ++++++++++++++++ .../java/com/paw/key/data/di/ServiceModule.kt | 7 +++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/paw/key/data/di/NetworkModule.kt b/app/src/main/java/com/paw/key/data/di/NetworkModule.kt index 7a79f1ed..3f64e456 100644 --- a/app/src/main/java/com/paw/key/data/di/NetworkModule.kt +++ b/app/src/main/java/com/paw/key/data/di/NetworkModule.kt @@ -51,7 +51,7 @@ object NetworkModule { client: OkHttpClient, converterFactory: Converter.Factory ): Retrofit = Retrofit.Builder() - .baseUrl(BuildConfig.BASE_URL) + .baseUrl(if (BuildConfig.DEBUG) BuildConfig.DEBUG_BASE_URL else BuildConfig.BASE_URL) //BuildConfig.BASE_URL) .addConverterFactory(converterFactory) .client(client) .build() diff --git a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt index f7115e10..bf4caa4f 100644 --- a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt @@ -8,6 +8,9 @@ import com.paw.key.data.remote.datasource.datasourceimpl.KakaoAuthDataSourceImpl import com.paw.key.data.remote.datasource.login.AuthRemoteDataSource import com.paw.key.data.remote.datasource.login.GoogleAuthDataSource import com.paw.key.data.remote.datasource.login.KakaoAuthDataSource +import com.paw.key.data.remote.datasource.mypage.MypageDataSource +import com.paw.key.data.remote.datasource.mypage.MypageDataSourceImpl +import com.paw.key.data.repository.mypage.MypageRepositoryImpl import com.paw.key.data.repositoryimpl.ArchivedListRepositoryImpl import com.paw.key.data.repositoryimpl.LikeRepositoryImpl import com.paw.key.data.repositoryimpl.PetProfileRepositoryImpl @@ -37,6 +40,7 @@ import com.paw.key.domain.repository.home.RegionCurrentRepository import com.paw.key.domain.repository.image.ImageRepository import com.paw.key.domain.repository.list.PostsListRepository import com.paw.key.domain.repository.login.AuthRepository +import com.paw.key.domain.repository.mypage.MypageRepository import com.paw.key.domain.repository.petprofile.PetProfileRepository import com.paw.key.domain.repository.sharedwalk.SharedWalkRepository import com.paw.key.domain.repository.user.UserRepository @@ -66,6 +70,12 @@ interface RepositoryModule { impl: GoogleAuthDataSourceImpl, ): GoogleAuthDataSource + @Binds + @Singleton + fun bindMypageDataSource( + impl: MypageDataSourceImpl + ): MypageDataSource + @Binds abstract fun bindKakaoAuthDataSource( impl: KakaoAuthDataSourceImpl @@ -189,4 +199,10 @@ interface RepositoryModule { abstract fun bindLocalStorageRepository( impl: LocalStorageRepositoryImpl ): LocalStorageRepository + + @Binds + @Singleton + fun bindMypageRepository( + impl: MypageRepositoryImpl + ): MypageRepository } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt index f65ecd71..dd7806dc 100644 --- a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt +++ b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt @@ -11,6 +11,7 @@ import com.paw.key.data.service.home.HomeRegionService import com.paw.key.data.service.image.ImageService import com.paw.key.data.service.list.PostsListService import com.paw.key.data.service.login.LoginService +import com.paw.key.data.service.mypage.MypageService import com.paw.key.data.service.sharedwalk.SharedWalkService import com.paw.key.data.service.user.UserService import com.paw.key.data.service.walkcourse.WalkCourseService @@ -109,4 +110,10 @@ object ServiceModule { @Singleton fun provideImageService(retrofit: Retrofit): ImageService = retrofit.create() + + + @Provides + @Singleton + fun provideMypageService(retrofit: Retrofit): MypageService = + retrofit.create() } \ No newline at end of file From c5d48b3d453f24cd73c34ce258308edc4826094c Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:00:41 +0900 Subject: [PATCH 06/15] [feat/#163] conflict resolve --- .../java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt index 851ec501..3f92e902 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/dbti/test/TestScreen.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.material3.Text -import com.paw.key.core.designsystem.component.DogkyButton +import com.paw.key.core.designsystem.component.DokiButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.dbti.component.SelectCard @@ -77,7 +77,7 @@ fun TestScreen( Spacer(modifier = Modifier.weight(1f)) - DogkyButton( + DokiButton( text = "다음으로", onClick = onNextClick, enabled = selectedOptionId != null, From 5f8ef6bfcf68c3a2e9101ddb239f635dc7f78b7c Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:01:13 +0900 Subject: [PATCH 07/15] =?UTF-8?q?[feat/#163]=20buildConfigField=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eacc6bf6..1f67bd3f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,11 +26,12 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "BASE_URL", properties["base.url"].toString()) + buildConfigField("String", "DEBUG_BASE_URL", properties["debug.base.url"].toString()) // ✅ 여기로 buildConfigField("String", "KAKAO_NATIVE_KEY", properties["kakao.native.key"].toString()) buildConfigField("String", "KAKAO_REST_API_KEY", properties["kakao.rest.api"].toString()) buildConfigField("String", "NAVERMAP_CLIENT_SECRET", properties["NAVERMAP_CLIENT_SECRET"].toString()) buildConfigField("String", "NAVERMAP_CLIENT_ID", properties["NAVERMAP_CLIENT_ID"].toString()) - buildConfigField("String","GOOGLE_WEB_CLIENT_ID",properties["google.client.id"].toString()) + buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", properties["google.client.id"].toString()) manifestPlaceholders["KAKAO_NATIVE_KEY"] = properties["kakao.native.key"].toString() } @@ -52,9 +53,6 @@ android { kotlinOptions { jvmTarget = "11" } - buildFeatures { - compose = true - } buildFeatures { compose = true buildConfig = true @@ -63,14 +61,13 @@ android { getByName("debug") { keyAlias = "androiddebugkey" keyPassword = "android" - storeFile = File("${project.rootDir.absolutePath}/keystore/debug.keystore")//project.rootProject.file("debug.keystore") + storeFile = File("${project.rootDir.absolutePath}/keystore/debug.keystore") storePassword = "android" } } } dependencies { - testImplementation(libs.junit) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.bundles.test) @@ -91,32 +88,22 @@ dependencies { ksp(libs.hilt.compiler) implementation(libs.coil.compose) - implementation(libs.timber) - implementation(libs.accompanist.systemuicontroller) - implementation(libs.androidx.datastore.preferences) - //카카오 implementation(libs.kakaoMaps) implementation(libs.v2.all) - //실시간 위치 implementation(libs.play.services.location) - - //로띠 - 애니메이션 implementation(libs.lottie.compose) - // 네이버 implementation(libs.bundles.naverMaps) - //구글 implementation(libs.androidx.credentials) implementation(libs.googleid) implementation(libs.androidx.credentials.play.services.auth) coreLibraryDesugaring(libs.desugar.jdk.libs) - // 암호화 implementation(libs.androidx.security) } \ No newline at end of file From 1575612480303cfe6ca659fb397dcf817ac035e8 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:01:46 +0900 Subject: [PATCH 08/15] =?UTF-8?q?[feat/#163]=20mypage=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../key/presentation/ui/main/MainNavigator.kt | 4 ++-- .../navigation/CourseInfoNavigation.kt | 23 ++++++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt b/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt index 89df215a..7ebd6031 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt @@ -17,7 +17,7 @@ import com.paw.key.presentation.ui.home.navigation.navigateHome import com.paw.key.presentation.ui.home.navigation.navigateHomeLocationSetting import com.paw.key.presentation.ui.login.navigation.navigateLogin import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType -import com.paw.key.presentation.ui.mypage.courseinfo.navigation.navigateCourseInfo +import com.paw.key.presentation.ui.mypage.courseinfo.navigation.navigateToCourseInfo import com.paw.key.presentation.ui.mypage.main.navigation.navigateMyPage import com.paw.key.presentation.ui.mypage.petinfo.navigation.navigatePetProfile import com.paw.key.presentation.ui.mypage.petinfo.navigation.navigatePetProfileList @@ -98,7 +98,7 @@ class MainNavigator( courseType: CourseType, navOptions: NavOptions? = null, ) { - navController.navigateCourseInfo(courseType = courseType, navOptions = navOptions) + navController.navigateToCourseInfo(courseType = courseType, navOptions = navOptions) } // Todo : 나중에 로직 플로우 확인하고 수정예정 diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt index c5a73b21..ee8bc226 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/navigation/CourseInfoNavigation.kt @@ -4,32 +4,27 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable +import androidx.navigation.toRoute import com.paw.key.presentation.ui.mypage.courseinfo.CourseInfoRoute import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType import kotlinx.serialization.Serializable +@Serializable +data class CourseInfoNavRoute(val courseType: CourseType) -fun NavController.navigateCourseInfo( +fun NavController.navigateToCourseInfo( courseType: CourseType, navOptions: NavOptions? = null, -) { - navigate( - CourseInfo(courseType = courseType.name), - navOptions - ) -} +) = navigate(CourseInfoNavRoute(courseType), navOptions) fun NavGraphBuilder.courseInfoNavGraph( navigateUp: () -> Unit, ) { - composable { + composable { backStackEntry -> + val route = backStackEntry.toRoute() CourseInfoRoute( navigateUp = navigateUp, + courseType = route.courseType, ) } -} - -@Serializable -data class CourseInfo( - val courseType: String, -) \ No newline at end of file +} \ No newline at end of file From 50be51641641ef63eedabb9a3b99d9f9598a2fcf Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:02:06 +0900 Subject: [PATCH 09/15] [feat/#163] mypagservice --- .../key/data/service/mypage/MypageService.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 app/src/main/java/com/paw/key/data/service/mypage/MypageService.kt diff --git a/app/src/main/java/com/paw/key/data/service/mypage/MypageService.kt b/app/src/main/java/com/paw/key/data/service/mypage/MypageService.kt new file mode 100644 index 00000000..2b3e6aa0 --- /dev/null +++ b/app/src/main/java/com/paw/key/data/service/mypage/MypageService.kt @@ -0,0 +1,32 @@ +package com.paw.key.data.service.mypage + +import com.paw.key.data.dto.request.mypage.UpdatePetRequestDto +import com.paw.key.data.dto.request.mypage.UpdateUserRequestDto +import com.paw.key.data.dto.response.BaseResponse +import com.paw.key.data.dto.response.mypage.ReviewPostListResponseDto +import com.paw.key.data.dto.response.mypage.RoutePostListResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PATCH + +interface MypageService { + + @PATCH("users/me") + suspend fun updateUser( + @Body body: UpdateUserRequestDto, + ): BaseResponse + + @PATCH("pets/me") + suspend fun updatePet( + @Body body: UpdatePetRequestDto, + ): BaseResponse + + @GET("users/me/routes") + suspend fun getMyRoutes(): BaseResponse + + @GET("users/me/likes") + suspend fun getLikedPosts(): BaseResponse + + @GET("users/me/reviews") + suspend fun getMyReviews(): BaseResponse +} \ No newline at end of file From a239b705754d97e8766820824a17d5f19bf72784 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:06:25 +0900 Subject: [PATCH 10/15] =?UTF-8?q?[feat/#163]=20mypage=20=EC=BD=94=EC=8A=A4?= =?UTF-8?q?=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/mypage/courseinfo/CourseInfoScreen.kt | 142 ++++++++++++------ .../ui/mypage/courseinfo/model/CourseType.kt | 48 +++++- .../viewmodel/CourseInfoViewModel.kt | 50 ++++-- 3 files changed, 176 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt index 27d1cdfa..7302b45e 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/CourseInfoScreen.kt @@ -2,97 +2,147 @@ package com.paw.key.presentation.ui.mypage.courseinfo import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.component.routeitem.RouteItem import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.util.UiState +import com.paw.key.presentation.ui.mypage.courseinfo.component.MyReviewCard import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseData +import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseInfoSideEffect +import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType import com.paw.key.presentation.ui.mypage.courseinfo.viewmodel.CourseInfoViewModel - @Composable fun CourseInfoRoute( navigateUp: () -> Unit, + courseType: CourseType, + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, viewModel: CourseInfoViewModel = hiltViewModel(), ) { - val courseType = viewModel.courseType + val state by viewModel.state.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.sideEffect.collect { effect -> + when (effect) { + is CourseInfoSideEffect.ShowSnackBar -> snackbarHostState.showSnackbar(effect.message) + } + } + } CourseInfoScreen( - title = courseType.courseType, - courses = emptyList(), + title = viewModel.courseType.courseType, + uiState = state.courses, + courseType = viewModel.courseType, navigateUp = navigateUp, ) } - @Composable fun CourseInfoScreen( title: String, - courses: List, + courseType: CourseType, + uiState: UiState>, navigateUp: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier .fillMaxSize() - .background( - color = PawKeyTheme.colors.background - ) + .background(PawKeyTheme.colors.background), ) { - TopBar( - title = title, - onBackClick = navigateUp, - ) + TopBar(title = title, onBackClick = navigateUp) HorizontalDivider( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), thickness = 2.dp, - color = PawKeyTheme.colors.defaultButton + color = PawKeyTheme.colors.defaultButton, ) - LazyVerticalGrid( - modifier = Modifier.fillMaxSize(), - columns = GridCells.Fixed(2), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(20.dp) - ) { - items(courses.size) { index -> - val course = courses[index] - RouteItem( - location = course.location, - routeTitle = course.title, - routeImage = course.imageUrl, - routeTime = course.time, - routeDate = course.date, - onClick = {}, - onClickHeart = {} - ) + when (uiState) { + is UiState.Loading -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } } - } - } -} + is UiState.Empty -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + text = "저장된 산책이 없어요", + style = PawKeyTheme.typography.body14M, + color = PawKeyTheme.colors.gray50, + ) + } + } -@Preview(showBackground = true) -@Composable -private fun CourseInfoScreenPreview() { - PawKeyTheme { - CourseInfoScreen( - title = "", - courses = emptyList(), - navigateUp = { }, - ) + is UiState.Failure -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + // TODO: 뭘로 만들지 + } + } + + is UiState.Success -> { + when (courseType) { + CourseType.ReviewCourse -> { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(20.dp), + ) { + items(items = uiState.data, key = { it.postId }) { course -> + MyReviewCard( + cardTitle = course.title, + ) + } + } + } + else -> { + LazyVerticalGrid( + modifier = Modifier.fillMaxSize(), + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(20.dp), + ) { + items(items = uiState.data, key = { it.postId }) { course -> + RouteItem( + location = course.location, + routeTitle = course.title, + routeImage = course.imageUrl, + routeTime = course.time, + routeDate = course.date, + onClick = {}, + onClickHeart = {}, + ) + } + } + } + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt index 3e7d8080..182553d2 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/CourseType.kt @@ -1,19 +1,53 @@ package com.paw.key.presentation.ui.mypage.courseinfo.model -enum class CourseType( - val courseType: String, -) { +import androidx.compose.runtime.Immutable +import com.paw.key.core.util.UiState +import com.paw.key.domain.entity.mypage.ReviewPostEntity +import com.paw.key.domain.entity.mypage.RoutePostEntity + +enum class CourseType(val courseType: String) { MyCourse(courseType = "내가 기록한 산책"), AllCourse(courseType = "저장 목록"), - ReviewCourse(courseType = "내가 남긴 후기") + ReviewCourse(courseType = "내가 남긴 후기"), } - data class CourseData( + val postId: Int, val location: String, val title: String, val imageUrl: String, - val distance: String, val time: String, val date: String, -) \ No newline at end of file + val isLiked: Boolean = false, + val categoryOptionSummary: List = emptyList(), +) + +@Immutable +data class CourseInfoState( + val courses: UiState> = UiState.Loading, +) + +sealed interface CourseInfoSideEffect { + data class ShowSnackBar(val message: String) : CourseInfoSideEffect +} + + +fun RoutePostEntity.toCourseData() = CourseData( + postId = postId, + location = regionName, + title = title, + imageUrl = imageUrl, + time = "${durationMinutes}분", + date = date.take(10), + isLiked = isLiked, +) + +fun ReviewPostEntity.toCourseData() = CourseData( + postId = postId, + location = regionName, + title = title, + imageUrl = "", + time = "", + date = date.take(10), + categoryOptionSummary = categoryOptionSummary, +) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt index 6d732138..53682b31 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/viewmodel/CourseInfoViewModel.kt @@ -2,35 +2,63 @@ package com.paw.key.presentation.ui.mypage.courseinfo.viewmodel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import com.paw.key.core.util.UiState +import com.paw.key.domain.repository.mypage.MypageRepository +import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseInfoSideEffect +import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseInfoSideEffect.ShowSnackBar +import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseInfoState import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType -import com.paw.key.presentation.ui.mypage.courseinfo.navigation.CourseInfo +import com.paw.key.presentation.ui.mypage.courseinfo.model.toCourseData +import com.paw.key.presentation.ui.mypage.courseinfo.navigation.CourseInfoNavRoute import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class CourseInfoViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + private val mypageRepository: MypageRepository, ) : ViewModel() { - private val args = savedStateHandle.toRoute() + val courseType: CourseType = savedStateHandle.toRoute().courseType - val courseType: CourseType = CourseType.valueOf(args.courseType) + private val _state = MutableStateFlow(CourseInfoState()) + val state = _state.asStateFlow() + + private val _sideEffect = MutableSharedFlow() + val sideEffect = _sideEffect.asSharedFlow() init { - fetchCourseData() + fetchCourses() } - private fun fetchCourseData() { - when (courseType) { - CourseType.MyCourse -> { /* 내가 기록한 산책 로드 */ - } + fun fetchCourses() { + viewModelScope.launch { + _state.update { it.copy(courses = UiState.Loading) } - CourseType.AllCourse -> { /* 저장 목록 로드 */ + val result = when (courseType) { + CourseType.MyCourse -> mypageRepository.getMyRoutes().map { list -> list.map { it.toCourseData() } } + CourseType.AllCourse -> mypageRepository.getLikedPosts().map { list -> list.map { it.toCourseData() } } + CourseType.ReviewCourse -> mypageRepository.getMyReviews().map { list -> list.map { it.toCourseData() } } } - CourseType.ReviewCourse -> { /* 후기 로드 */ - } + result + .onSuccess { courses -> + _state.update { + it.copy(courses = if (courses.isEmpty()) UiState.Empty else UiState.Success(courses)) + } + } + .onFailure { e -> + _state.update { it.copy(courses = UiState.Failure(e.message ?: "불러오기 실패")) } + _sideEffect.emit(ShowSnackBar(e.message ?: "불러오기 실패")) + } } } } \ No newline at end of file From bf562e3101ed8ecc3f33c8946cbe425286bf5742 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:06:39 +0900 Subject: [PATCH 11/15] =?UTF-8?q?[feat/#163]=20mypage=20=EB=B0=98=EB=A0=A4?= =?UTF-8?q?=EA=B2=AC=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/mypage/petinfo/PetProfileScreen.kt | 240 ++++++++---------- .../petinfo/model/PetProfileContract.kt | 28 +- .../petinfo/viewmodel/PetProfileViewModel.kt | 94 +++++-- 3 files changed, 192 insertions(+), 170 deletions(-) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt index afdbf9eb..7a92f089 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/PetProfileScreen.kt @@ -20,8 +20,10 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.SnackbarHostState 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 @@ -46,6 +48,7 @@ import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.extension.noRippleClickable +import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileSideEffect import com.paw.key.presentation.ui.mypage.petinfo.viewmodel.PetProfileViewModel import com.paw.key.presentation.ui.signup.component.FormField import com.paw.key.presentation.ui.signup.component.GenderSelector @@ -61,26 +64,37 @@ import kotlinx.coroutines.launch @Composable fun PetProfileRoute( navigateUp: () -> Unit, + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, viewModel: PetProfileViewModel = hiltViewModel(), ) { - val state = viewModel.state.collectAsStateWithLifecycle() + val state by viewModel.state.collectAsStateWithLifecycle() + LaunchedEffect(Unit) { + viewModel.sideEffect.collect { effect -> + when (effect) { + is PetProfileSideEffect.ShowSnackBar -> snackbarHostState.showSnackbar(effect.message) + PetProfileSideEffect.NavigateUp -> navigateUp() + } + } + } PetProfileScreen( - petName = state.value.name, - petBirthDate = state.value.birthday, - petGender = Gender.MALE, - petNeutered = state.value.isNeutered, - petBreed = state.value.breed, - selectedImageUri = state.value.imageUrl, - navigateUp = navigateUp, - deniedPermission = {}, - onPetNameChanged = {}, - onPetBirthDateChanged = {}, - onPetGenderChanged = {}, - onPetNeuteredChanged = {}, - onPetBreedChanged = {}, - onSelectedImage = {} + petName = state.name, + petBirthDate = state.birthday, + petGender = state.gender, + petNeutered = state.isNeutered, + petBreed = state.breed, + selectedImageUri = state.imageUrl, + isLoading = state.isLoading, + navigateUp = navigateUp, + deniedPermission = {}, + onPetNameChanged = viewModel::onNameChange, + onPetBirthDateChanged = viewModel::onBirthChange, + onPetGenderChanged = viewModel::onGenderChange, + onPetNeuteredChanged = viewModel::onNeuteredChange, + onPetBreedChanged = { viewModel.onBreedChange(it.name, it.id) }, + onSelectedImage = viewModel::onImageChange, + onSaveClick = viewModel::updatePet, ) } @@ -93,6 +107,7 @@ fun PetProfileScreen( petNeutered: Boolean, petBreed: String, selectedImageUri: Uri?, + isLoading: Boolean, navigateUp: () -> Unit, deniedPermission: () -> Unit, onPetNameChanged: (String) -> Unit, @@ -101,6 +116,7 @@ fun PetProfileScreen( onPetNeuteredChanged: (Boolean) -> Unit, onPetBreedChanged: (PetInfoItemModel) -> Unit, onSelectedImage: (Uri?) -> Unit, + onSaveClick: () -> Unit, modifier: Modifier = Modifier, ) { var isSheetOpen by remember { mutableStateOf(false) } @@ -112,70 +128,52 @@ fun PetProfileScreen( val photoPickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia(), - onResult = { uri -> - onSelectedImage(uri) - } + onResult = onSelectedImage, ) - // 구버전 권한 요청용 val legacyGalleryLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.GetContent(), - onResult = { uri -> - onSelectedImage(uri) - } + onResult = onSelectedImage, ) val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), onResult = { isGranted -> - if (isGranted) { - legacyGalleryLauncher.launch("image/*") - } else { - deniedPermission - } - } + if (isGranted) legacyGalleryLauncher.launch("image/*") + else deniedPermission() + }, ) - - Column( modifier = modifier .fillMaxSize() - .background(color = PawKeyTheme.colors.background) + .background(PawKeyTheme.colors.background), ) { - TopBar( - title = " 반려견 정보 수정", - onBackClick = navigateUp, - modifier = Modifier, - ) + TopBar(title = "반려견 정보 수정", onBackClick = navigateUp) HorizontalDivider( - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), thickness = 2.dp, - color = PawKeyTheme.colors.defaultButton + color = PawKeyTheme.colors.defaultButton, ) LazyColumn( - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 18.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + modifier = Modifier.padding(horizontal = 16.dp, vertical = 18.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { item { SignUpPetImageHolder( uri = selectedImageUri, - modifier = Modifier - .noRippleClickable { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - photoPickerLauncher.launch( - PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) - ) - } else { - permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) - } + modifier = Modifier.noRippleClickable { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + photoPickerLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + } else { + permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) } + }, ) - } item { @@ -183,23 +181,15 @@ fun PetProfileScreen( label = "이름", content = { SignUpTextField( - value = petName, - onValueChange = { - if (it.length <= 8) { - onPetNameChanged(it) - } - }, - placeholder = "최대 8글자 이내로 입력해주세요", - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next - ), + value = petName, + onValueChange = { if (it.length <= 8) onPetNameChanged(it) }, + placeholder = "최대 8글자 이내로 입력해주세요", + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardActions = KeyboardActions( - onDone = { - petBirthDateFocusRequester.requestFocus() - } + onNext = { petBirthDateFocusRequester.requestFocus() } ), ) - } + }, ) } @@ -208,99 +198,81 @@ fun PetProfileScreen( label = "생년월일", content = { SignUpTextField( - modifier = Modifier - .focusRequester(petBirthDateFocusRequester), - value = petBirthDate, - onValueChange = { - if (it.length <= 8) { - onPetBirthDateChanged(it) - } - }, - placeholder = "YYYYMMDD", + modifier = Modifier.focusRequester(petBirthDateFocusRequester), + value = petBirthDate, + onValueChange = { if (it.length <= 8) onPetBirthDateChanged(it) }, + placeholder = "YYYYMMDD", keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number, - imeAction = ImeAction.Done + imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions( - onDone = { - focusManager.clearFocus() - } + onDone = { focusManager.clearFocus() } ), -// visualTransformation = DateVisualTransformation() ) - } + }, ) - } + item { FormField( label = "성별", content = { GenderSelector( - selectedGender = petGender, + selectedGender = petGender, onGenderSelected = onPetGenderChanged, - type = "반려 동물" + type = "반려 동물", ) - } + }, ) - } item { SignUpNeuteringCheckRadio( isNeutered = petNeutered, - onToggle = { onPetNeuteredChanged(!petNeutered) }, - modifier = Modifier - .padding(top = 8.dp) + onToggle = { onPetNeuteredChanged(!petNeutered) }, + modifier = Modifier.padding(top = 8.dp), ) - } item { - FormField( label = "견종", content = { SignUpTextField( - value = petBreed, + value = petBreed, onValueChange = {}, - enabled = false, - placeholder = "견종을 검색해보세요", + enabled = false, + placeholder = "견종을 검색해보세요", suffix = { Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_signup_search), + imageVector = ImageVector.vectorResource(R.drawable.ic_signup_search), contentDescription = "breed search", - tint = Color.Unspecified + tint = Color.Unspecified, ) }, - modifier = Modifier - .noRippleClickable { - scope.launch { - isSheetOpen = true - } - } + modifier = Modifier.noRippleClickable { + scope.launch { isSheetOpen = true } + }, ) - } + }, ) + if (isSheetOpen) { PawKeyBottomSheet( onDismissRequest = { isSheetOpen = false }, - sheetState = sheetState, - //sheetGesturesEnabled = false, - ) { sheetState -> + sheetState = sheetState, + ) { state -> PetBreedSearchContent( - petBreedList = persistentListOf(), - sheetState = sheetState, + petBreedList = persistentListOf(), + sheetState = state, selectedBreed = petBreed, - onBreedSelected = { - onPetBreedChanged(it) + onBreedSelected = { breed -> + onPetBreedChanged(breed) scope.launch { - - sheetState.hide() + state.hide() }.invokeOnCompletion { - if (!sheetState.isVisible) { - isSheetOpen = false - } + if (!state.isVisible) isSheetOpen = false } }, ) @@ -309,18 +281,16 @@ fun PetProfileScreen( } } - Spacer(modifier = Modifier.weight(1F)) + Spacer(modifier = Modifier.weight(1f)) PawkeyButton( - text = "저장하기", - enabled = true, - onClick = { }, - modifier = Modifier - .padding(horizontal = 16.dp) + text = "저장하기", + enabled = !isLoading, + onClick = onSaveClick, + modifier = Modifier.padding(horizontal = 16.dp), ) Spacer(modifier = Modifier.height(34.dp)) - } } @@ -329,20 +299,22 @@ fun PetProfileScreen( private fun PetProfileScreenPreview() { PawKeyTheme { PetProfileScreen( - petName = "꾸꾸", - petBirthDate = "꾸꾸", - petGender = Gender.MALE, - petNeutered = true, - petBreed = "꾸꾸", - selectedImageUri = null, - navigateUp = {}, - deniedPermission = {}, - onPetNameChanged = {}, + petName = "꾸꾸", + petBirthDate = "20220101", + petGender = Gender.MALE, + petNeutered = true, + petBreed = "말티즈", + selectedImageUri = null, + isLoading = false, + navigateUp = {}, + deniedPermission = {}, + onPetNameChanged = {}, onPetBirthDateChanged = {}, - onPetGenderChanged = {}, + onPetGenderChanged = {}, onPetNeuteredChanged = {}, - onPetBreedChanged = {}, - onSelectedImage = {} + onPetBreedChanged = {}, + onSelectedImage = {}, + onSaveClick = {}, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt index 89d23be0..9d529124 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/model/PetProfileContract.kt @@ -2,21 +2,25 @@ package com.paw.key.presentation.ui.mypage.petinfo.model import android.net.Uri import androidx.compose.runtime.Immutable +import com.paw.key.presentation.ui.signup.state.Gender @Immutable data class PetProfileState( + val name: String = "", + val birthday: String = "", + val gender: Gender = Gender.MALE, + val isNeutered: Boolean = false, + val breed: String = "", + val breedId: Int = 0, val imageUrl: Uri? = null, - val name: String = "까루", - val gender: String = "남아", - val birthday : String = "2020/01/01", - val breed: String = "코리안 숏헤어", - val age: String = "4세", - val isNeutered: Boolean = true, - val energyLevel: String = "활동적이에요", - val socialLevel: String = "불편해해요" + val imageId: Int = 0, + val age: String = "", + val energyLevel: String = "", + val socialLevel: String = "", + val isLoading: Boolean = false, ) -sealed class PetProfileSideEffect { - data class ShowSnackBar(val message: String) : PetProfileSideEffect() - data object NavigateUp : PetProfileSideEffect() - data object NavigateNext : PetProfileSideEffect() + +sealed interface PetProfileSideEffect { + data class ShowSnackBar(val message: String) : PetProfileSideEffect + data object NavigateUp : PetProfileSideEffect } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt index afa14118..12eaea6f 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt @@ -1,16 +1,19 @@ package com.paw.key.presentation.ui.mypage.petinfo.viewmodel -import android.util.Log +import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.domain.repository.mypage.MypageRepository import com.paw.key.domain.repository.petprofile.PetProfileRepository import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileSideEffect import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileState +import com.paw.key.presentation.ui.signup.state.Gender import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -19,46 +22,89 @@ import javax.inject.Inject @HiltViewModel class PetProfileViewModel @Inject constructor( private val petProfileRepository: PetProfileRepository, - private val localRepository: LocalStorageRepository + private val mypageRepository: MypageRepository, + private val localRepository: LocalStorageRepository, ) : ViewModel() { private val _state = MutableStateFlow(PetProfileState()) val state: StateFlow = _state.asStateFlow() - private val _sideEffect = MutableSharedFlow() // 필요 시 따로 Contract로 분리 가능 - val sideEffect: MutableSharedFlow = _sideEffect + private val _sideEffect = MutableSharedFlow() + val sideEffect = _sideEffect.asSharedFlow() init { viewModelScope.launch { - val userId = localRepository.getUserId() - getPetProfiles(userId) + getPetProfiles(localRepository.getUserId()) } } - fun getPetProfiles(userId: Int) { + fun onNameChange(value: String) = _state.update { it.copy(name = value) } + fun onBirthChange(value: String) = _state.update { it.copy(birthday = value) } + fun onGenderChange(value: Gender) = _state.update { it.copy(gender = value) } + fun onNeuteredChange(value: Boolean) = _state.update { it.copy(isNeutered = value) } + fun onBreedChange(breedName: String, breedId: Int) = _state.update { it.copy(breed = breedName, breedId = breedId) } + fun onImageChange(uri: Uri?) = _state.update { it.copy(imageUrl = uri) } + + private fun getPetProfiles(userId: Int) { viewModelScope.launch { petProfileRepository.getPetProfiles(userId) .onSuccess { result -> - Log.d("PetProfileViewModel", "펫 프로필 불러오기 성공: ${result.size}마리") - _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("펫 프로필 불러오기 성공 (${result.size}마리)")) - // 필요하면 내부 상태 저장 - // _state.update { it.copy(profiles = result) } - _state.update { it -> - it.copy( - imageUrl = result.first().imageUrl, - name = result.first().name, - gender = result.first().gender, - breed = result.first().breed, - age = result.first().age.toString(), - energyLevel = result.first().traits.first().option, - socialLevel = result.first().traits.first().option, - isNeutered = result.first().isNeutered - ) + result.firstOrNull()?.let { pet -> + _state.update { + it.copy( + name = pet.name, + gender = if (pet.gender == "M") Gender.MALE else Gender.FEMALE, + breed = pet.breed, + age = pet.age.toString(), + isNeutered = pet.isNeutered, + energyLevel = pet.traits.firstOrNull()?.option.orEmpty(), + socialLevel = pet.traits.getOrNull(1)?.option.orEmpty(), + ) + } } } .onFailure { e -> - Log.e("PetProfileViewModel", "펫 프로필 불러오기 실패", e) - _sideEffect.emit(PetProfileSideEffect.ShowSnackBar(e.message ?: "알 수 없는 오류")) + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar(e.message ?: "프로필을 불러오지 못했습니다")) + } + } + } + + fun updatePet() { + if (_state.value.isLoading) return + val s = _state.value + + if (s.name.isBlank() || s.birthday.isBlank() || s.breedId == 0) { + viewModelScope.launch { + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("필수 정보를 모두 입력해주세요")) + } + return + } + + val formattedBirth = if (s.birthday.length == 8 && !s.birthday.contains("-")) { + "${s.birthday.substring(0, 4)}-${s.birthday.substring(4, 6)}-${s.birthday.substring(6, 8)}" + } else { + s.birthday.replace(".", "-").replace("/", "-") + } + + viewModelScope.launch { + _state.update { it.copy(isLoading = true) } + + mypageRepository.updatePet( + name = s.name, + birth = formattedBirth, + gender = if (s.gender == Gender.MALE) "M" else "F", + isNeutered = s.isNeutered, + breedId = s.breedId, + imageId = s.imageId + ) + .onSuccess { + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("반려견 정보가 수정되었습니다")) + _sideEffect.emit(PetProfileSideEffect.NavigateUp) + _state.update { it.copy(isLoading = false) } + } + .onFailure { e -> + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar(e.message ?: "수정에 실패했습니다")) + _state.update { it.copy(isLoading = false) } } } } From 75777e2cd9bf6ba9b7946ce6ea63e88b674b2890 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:06:49 +0900 Subject: [PATCH 12/15] =?UTF-8?q?[feat/#163]=20mypage=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/mypage/userinfo/UserProfileScreen.kt | 75 +++++++++++++------ .../userinfo/model/UserProfileContract.kt | 18 +++-- .../viewmodel/UserProfileViewModel.kt | 72 ++++++++++++------ 3 files changed, 110 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt index e97fd315..0afdfb8e 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/UserProfileScreen.kt @@ -1,5 +1,7 @@ package com.paw.key.presentation.ui.mypage.userinfo +import android.widget.Toast +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -9,7 +11,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -20,6 +24,7 @@ import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.presentation.ui.mypage.userinfo.component.UserEditTextField import com.paw.key.presentation.ui.mypage.userinfo.component.UserGenderButton import com.paw.key.presentation.ui.mypage.userinfo.component.UserProfileItem +import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileSideEffect import com.paw.key.presentation.ui.mypage.userinfo.viewmodel.UserProfileViewModel @Composable @@ -29,12 +34,31 @@ fun UserProfileRoute( viewModel: UserProfileViewModel = hiltViewModel(), ) { val state = viewModel.state.collectAsStateWithLifecycle() + val context = LocalContext.current + + LaunchedEffect(viewModel.sideEffect) { + viewModel.sideEffect.collect { effect -> + when (effect) { + is UserProfileSideEffect.ShowSnackBar -> { + Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() + } + is UserProfileSideEffect.NavigateUp -> { + navigateUp() + } + else -> Unit + } + } + } UserProfileScreen( name = state.value.name, gender = state.value.gender, - birth = state.value.name, + birth = state.value.birth, navigateUp = navigateUp, + onNameChange = viewModel::onNameChange, + onBirthChange = viewModel::onBirthChange, + onGenderChange = viewModel::onGenderChange, + onSaveClick = viewModel::updateUser, modifier = modifier ) } @@ -45,11 +69,16 @@ private fun UserProfileScreen( gender: String, birth: String, navigateUp: () -> Unit, + onNameChange: (String) -> Unit, + onBirthChange: (String) -> Unit, + onGenderChange: (String) -> Unit, + onSaveClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier - .fillMaxSize(), + .fillMaxSize() + .background(color = PawKeyTheme.colors.white1), verticalArrangement = Arrangement.spacedBy(16.dp) ) { TopBar( @@ -57,7 +86,6 @@ private fun UserProfileScreen( onBackClick = navigateUp ) - Spacer(modifier = Modifier.height(4.dp)) UserProfileItem( @@ -65,7 +93,7 @@ private fun UserProfileScreen( profileItem = { UserEditTextField( value = name, - onValueChange = {}, + onValueChange = onNameChange, placeholder = "닉네임을 입력해주세요", modifier = Modifier.fillMaxWidth(), enabled = true, @@ -75,12 +103,12 @@ private fun UserProfileScreen( ) UserProfileItem( - label = "생년원일", + label = "생년월일", profileItem = { UserEditTextField( value = birth, - onValueChange = {}, - placeholder = "닉네임을 입력해주세요", + onValueChange = onBirthChange, + placeholder = "YYYY-MM-DD", modifier = Modifier.fillMaxWidth(), enabled = true, singleLine = true @@ -93,21 +121,19 @@ private fun UserProfileScreen( profileItem = { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth() ) { UserGenderButton( user = "남성", - isSelect = gender == "남성", - onClick = { }, - modifier = Modifier - .weight(1f) + isSelect = gender == "M", + onClick = { onGenderChange("M") }, + modifier = Modifier.weight(1f) ) UserGenderButton( user = "여성", - isSelect = gender == "여성", - onClick = { }, - modifier = Modifier - .weight(1f) + isSelect = gender == "F", + onClick = { onGenderChange("F") }, + modifier = Modifier.weight(1f) ) } } @@ -117,10 +143,9 @@ private fun UserProfileScreen( PawkeyButton( text = "저장하기", - enabled = true, - onClick = { }, - modifier = Modifier - .padding(horizontal = 16.dp) + enabled = name.isNotBlank() && birth.isNotBlank() && gender.isNotBlank(), + onClick = onSaveClick, + modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(34.dp)) @@ -133,9 +158,13 @@ private fun UserProfileScreenPreview() { PawKeyTheme { UserProfileScreen( name = "김도기", - gender = "여성", - birth = "2002/06/21", - navigateUp = {} + gender = "F", + birth = "2002-06-21", + navigateUp = {}, + onNameChange = {}, + onBirthChange = {}, + onGenderChange = {}, + onSaveClick = {} ) } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt index 310f9234..8356e31a 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/model/UserProfileContract.kt @@ -4,14 +4,16 @@ import androidx.compose.runtime.Immutable @Immutable data class UserProfileState( - val name: String = "김도기", - val gender: String = "여성", - val age: Int = 24, - val activeRegion: String = "강남구 역삼동" + val name: String = "", + val birth: String = "", + val gender: String = "", + val age: Int = 0, + val activeRegion: String = "", + val isLoading: Boolean = false, ) -sealed class UserProfileSideEffect{ - data class ShowSnackBar(val message: String) : UserProfileSideEffect() - data object NavigateUp : UserProfileSideEffect() - data object NavigateNext : UserProfileSideEffect() +sealed interface UserProfileSideEffect { + data class ShowSnackBar(val message: String) : UserProfileSideEffect + data object NavigateUp : UserProfileSideEffect + data object NavigateNext : UserProfileSideEffect } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt index 01b5d7a3..2870cfff 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/userinfo/viewmodel/UserProfileViewModel.kt @@ -1,9 +1,9 @@ package com.paw.key.presentation.ui.mypage.userinfo.viewmodel -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.domain.repository.mypage.MypageRepository import com.paw.key.domain.repository.userprofile.UserProfileRepository import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileSideEffect import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileState @@ -11,6 +11,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -19,49 +20,72 @@ import javax.inject.Inject @HiltViewModel class UserProfileViewModel @Inject constructor( private val userProfileRepository: UserProfileRepository, - private val localRepository: LocalStorageRepository + private val mypageRepository: MypageRepository, + private val localRepository: LocalStorageRepository, ) : ViewModel() { private val _state = MutableStateFlow(UserProfileState()) val state: StateFlow = _state.asStateFlow() private val _sideEffect = MutableSharedFlow() - val sideEffect: MutableSharedFlow = _sideEffect + val sideEffect = _sideEffect.asSharedFlow() init { viewModelScope.launch { - val userId = localRepository.getUserId() - getUserProfiles(userId) + getUserProfiles(localRepository.getUserId()) } } - fun getUserProfiles(userId: Int) { + fun onNameChange(value: String) = _state.update { it.copy(name = value) } + fun onBirthChange(value: String) = _state.update { it.copy(birth = value) } + fun onGenderChange(value: String) = _state.update { it.copy(gender = value) } + + private fun getUserProfiles(userId: Int) { viewModelScope.launch { userProfileRepository.getUserProfiles(userId) .onSuccess { result -> - Log.d("UserProfileViewModel", "유저 프로필 불러오기 성공: $result") - _sideEffect.emit(UserProfileSideEffect.ShowSnackBar("유저 프로필 불러오기 성공")) - - _state.update { state -> - state.copy( - name = result.name, - gender = result.gender, - age = result.age, - activeRegion = result.activeRegion + _state.update { + it.copy( + name = result.name, + gender = result.gender, + age = result.age, + activeRegion = result.activeRegion, ) } - - try { - //PreferenceDataStore.saveActiveRegion(result.activeRegion) - Log.d("UserProfileViewModel", "activeRegion 저장 완료: ${result.activeRegion}") - } catch (e: Exception) { - Log.e("UserProfileViewModel", "activeRegion 저장 실패: ${e.message}") - } } .onFailure { e -> - Log.e("UserProfileViewModel", "유저 프로필 불러오기 실패", e) - _sideEffect.emit(UserProfileSideEffect.ShowSnackBar(e.message ?: "알 수 없는 오류")) + _sideEffect.emit(UserProfileSideEffect.ShowSnackBar(e.message ?: "프로필을 불러오지 못했습니다")) } } } + + fun updateUser() { + if (_state.value.isLoading) return + val s = _state.value + + if (s.name.isBlank() || s.birth.isBlank() || s.gender.isBlank()) { + viewModelScope.launch { + _sideEffect.emit(UserProfileSideEffect.ShowSnackBar("모든 정보를 입력해주세요.")) + } + return + } + + val formattedBirth = s.birth.replace(".", "-").replace("/", "-") + + viewModelScope.launch { + _state.update { it.copy(isLoading = true) } + mypageRepository.updateUser( + name = s.name, + birth = formattedBirth, + gender = s.gender + ).onSuccess { + _sideEffect.emit(UserProfileSideEffect.ShowSnackBar("프로필이 수정되었습니다")) + _sideEffect.emit(UserProfileSideEffect.NavigateUp) + _state.update { it.copy(isLoading = false) } + }.onFailure { e -> + _sideEffect.emit(UserProfileSideEffect.ShowSnackBar(e.message ?: "수정에 실패했습니다")) + _state.update { it.copy(isLoading = false) } + } + } + } } \ No newline at end of file From 14f5e6db7f23e17c962f9ea7f672e3de7bf165c3 Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Thu, 26 Mar 2026 00:07:01 +0900 Subject: [PATCH 13/15] =?UTF-8?q?[feat/#163]=20mypage=20=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EB=B7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../courseinfo/model/MypageEditState.kt | 27 +++++++++++++++++++ .../ui/mypage/main/MyPageScreen.kt | 8 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/MypageEditState.kt diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/MypageEditState.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/MypageEditState.kt new file mode 100644 index 00000000..c95d799b --- /dev/null +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/courseinfo/model/MypageEditState.kt @@ -0,0 +1,27 @@ +package com.paw.key.presentation.ui.mypage.courseinfo.model + +import androidx.compose.runtime.Immutable + +@Immutable +data class EditUserState( + val name: String = "", + val birth: String = "", + val gender: String = "", + val isLoading: Boolean = false, +) + +@Immutable +data class EditPetState( + val name: String = "", + val birth: String = "", + val gender: String = "", + val isNeutered: Boolean = false, + val breedId: Int = 0, + val imageId: Int = 0, + val isLoading: Boolean = false, +) + +sealed interface EditSideEffect { + data class ShowSnackBar(val message: String) : EditSideEffect + data object NavigateUp : EditSideEffect +} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt index 898f83ad..91c5e09f 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/main/MyPageScreen.kt @@ -16,6 +16,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme +import com.paw.key.core.extension.noRippleClickable import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType import com.paw.key.presentation.ui.mypage.main.component.MyList import com.paw.key.presentation.ui.mypage.main.component.MyPageCard @@ -91,11 +92,12 @@ fun MyPageScreen( item { MyPageCard( - userName = "단지", - userAge = "6개월", - userGender = "여아", + userName = state.petName.ifBlank { "단지" }, + userAge = state.petAge.ifBlank { "6개월" }, + userGender = state.petGender.ifBlank { "여아" }, dogBreed = "우지", buttonTitle = "DBTI검사하러 가기", + modifier = Modifier.noRippleClickable { navigatePetProfile() } ) } From 2160d12b04a61833156daf1622cdd363d438c3de Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Fri, 27 Mar 2026 00:22:43 +0900 Subject: [PATCH 14/15] [feat/#165] : conflict resolve --- .../com/paw/key/data/di/RepositoryModule.kt | 5 +- .../java/com/paw/key/data/di/ServiceModule.kt | 10 --- .../entity/petprofile/PetProfileEntity.kt | 2 +- .../petprofile/PetProfileRepository.kt | 7 -- .../key/presentation/ui/main/MainNavigator.kt | 4 +- .../petinfo/viewmodel/PetProfileViewModel.kt | 39 +++++------ .../viewmodel/CourseInfoViewModel.kt | 2 +- .../mypage/route/petinfo/PetProfileScreen.kt | 3 +- .../route/petinfo/model/PetProfileContract.kt | 2 + .../petinfo/viewmodel/PetProfileViewModel.kt | 66 ++++++++++++++++++- .../viewmodel/UserProfileViewModel.kt | 8 +-- 11 files changed, 89 insertions(+), 59 deletions(-) delete mode 100644 app/src/main/java/com/paw/key/domain/repository/petprofile/PetProfileRepository.kt diff --git a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt index b24b6fb9..ba7d3b07 100644 --- a/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt +++ b/app/src/main/java/com/paw/key/data/di/RepositoryModule.kt @@ -33,10 +33,8 @@ import com.paw.key.domain.repository.home.RegionCurrentRepository import com.paw.key.domain.repository.image.ImageRepository import com.paw.key.domain.repository.localstorage.LocalStorageRepository import com.paw.key.domain.repository.login.AuthRepository -import com.paw.key.domain.repository.posts.PostsRepository import com.paw.key.domain.repository.mypage.MypageRepository -import com.paw.key.domain.repository.petprofile.PetProfileRepository -import com.paw.key.domain.repository.sharedwalk.SharedWalkRepository +import com.paw.key.domain.repository.posts.PostsRepository import com.paw.key.domain.repository.user.UserRepository import com.paw.key.domain.repository.walk.WalkRepository import com.paw.key.domain.repository.walkpreparation.WalkPreparationRepository @@ -166,4 +164,5 @@ interface RepositoryModule { fun bindWalkRepository( impl: WalkRepositoryImpl ) : WalkRepository + } diff --git a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt index 25c00580..ab66d9b6 100644 --- a/app/src/main/java/com/paw/key/data/di/ServiceModule.kt +++ b/app/src/main/java/com/paw/key/data/di/ServiceModule.kt @@ -48,16 +48,6 @@ object ServiceModule { fun provideHomeRegionService(retrofit: Retrofit): HomeRegionService = retrofit.create() - //마이페이지 - @Provides - @Singleton - fun provideUserProfileService(retrofit: Retrofit): UserProfileService = - retrofit.create() - - @Provides - @Singleton - fun providePetProfileService(retrofit: Retrofit): PetProfileService = - retrofit.create() @Provides @Singleton diff --git a/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt b/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt index 8d68a59b..3047643b 100644 --- a/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt +++ b/app/src/main/java/com/paw/key/domain/entity/petprofile/PetProfileEntity.kt @@ -11,4 +11,4 @@ data class PetProfileEntity( val breed: String, val dbtiName: String?, val dbtiDescription: String?, -) +) \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/domain/repository/petprofile/PetProfileRepository.kt b/app/src/main/java/com/paw/key/domain/repository/petprofile/PetProfileRepository.kt deleted file mode 100644 index 03de6217..00000000 --- a/app/src/main/java/com/paw/key/domain/repository/petprofile/PetProfileRepository.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.paw.key.domain.repository.petprofile - -import com.paw.key.domain.entity.petprofile.PetProfileEntity - -interface PetProfileRepository { - suspend fun getPetProfiles(userId: Int): Result> -} \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt b/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt index def74637..b3384541 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/main/MainNavigator.kt @@ -16,11 +16,9 @@ import com.paw.key.presentation.ui.course.walkreview.navigation.navigateWalkRevi import com.paw.key.presentation.ui.home.navigation.navigateHome import com.paw.key.presentation.ui.home.navigation.navigateHomeLocationSetting import com.paw.key.presentation.ui.login.navigation.navigateLogin -import com.paw.key.presentation.ui.mypage.courseinfo.model.CourseType -import com.paw.key.presentation.ui.mypage.courseinfo.navigation.navigateToCourseInfo import com.paw.key.presentation.ui.mypage.main.navigation.navigateMyPage import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType -import com.paw.key.presentation.ui.mypage.route.courseinfo.navigation.navigateCourseInfo +import com.paw.key.presentation.ui.mypage.route.courseinfo.navigation.navigateToCourseInfo import com.paw.key.presentation.ui.mypage.route.petinfo.navigation.navigatePetProfile import com.paw.key.presentation.ui.mypage.route.petinfo.navigation.navigatePetProfileList import com.paw.key.presentation.ui.mypage.route.userinfo.navigation.navigateUserProfile diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt index 12eaea6f..bc9ebf10 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/petinfo/viewmodel/PetProfileViewModel.kt @@ -5,9 +5,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.paw.key.domain.repository.localstorage.LocalStorageRepository import com.paw.key.domain.repository.mypage.MypageRepository -import com.paw.key.domain.repository.petprofile.PetProfileRepository -import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileSideEffect -import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileState +import com.paw.key.domain.repository.user.UserRepository +import com.paw.key.presentation.ui.mypage.model.toUiModel +import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileSideEffect +import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileState import com.paw.key.presentation.ui.signup.state.Gender import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -21,8 +22,9 @@ import javax.inject.Inject @HiltViewModel class PetProfileViewModel @Inject constructor( - private val petProfileRepository: PetProfileRepository, private val mypageRepository: MypageRepository, + + private val userRepository: UserRepository, private val localRepository: LocalStorageRepository, ) : ViewModel() { @@ -34,7 +36,7 @@ class PetProfileViewModel @Inject constructor( init { viewModelScope.launch { - getPetProfiles(localRepository.getUserId()) + getPetProfiles() } } @@ -45,26 +47,19 @@ class PetProfileViewModel @Inject constructor( fun onBreedChange(breedName: String, breedId: Int) = _state.update { it.copy(breed = breedName, breedId = breedId) } fun onImageChange(uri: Uri?) = _state.update { it.copy(imageUrl = uri) } - private fun getPetProfiles(userId: Int) { + fun getPetProfiles() { viewModelScope.launch { - petProfileRepository.getPetProfiles(userId) + val petId = localRepository.getPetId() + + userRepository.getPetProfiles(petId) .onSuccess { result -> - result.firstOrNull()?.let { pet -> - _state.update { - it.copy( - name = pet.name, - gender = if (pet.gender == "M") Gender.MALE else Gender.FEMALE, - breed = pet.breed, - age = pet.age.toString(), - isNeutered = pet.isNeutered, - energyLevel = pet.traits.firstOrNull()?.option.orEmpty(), - socialLevel = pet.traits.getOrNull(1)?.option.orEmpty(), - ) - } + _state.update { currentState -> + currentState.copy( + petInfo = result.toUiModel() + ) } - } - .onFailure { e -> - _sideEffect.emit(PetProfileSideEffect.ShowSnackBar(e.message ?: "프로필을 불러오지 못했습니다")) + }.onFailure { + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("펫 프로필 불러오기 실패")) } } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt index 50f5106d..e7f8b4b5 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/viewmodel/CourseInfoViewModel.kt @@ -9,6 +9,7 @@ import com.paw.key.domain.repository.mypage.MypageRepository import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseInfoSideEffect import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseInfoState import com.paw.key.presentation.ui.mypage.route.courseinfo.model.CourseType +import com.paw.key.presentation.ui.mypage.route.courseinfo.model.toCourseData import com.paw.key.presentation.ui.mypage.route.courseinfo.navigation.CourseInfoNavRoute import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -55,7 +56,6 @@ class CourseInfoViewModel @Inject constructor( } .onFailure { e -> _state.update { it.copy(courses = UiState.Failure(e.message ?: "불러오기 실패")) } - _sideEffect.emit(ShowSnackBar(e.message ?: "불러오기 실패")) } } } diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt index 2ffe7407..b63b5a56 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/PetProfileScreen.kt @@ -48,8 +48,7 @@ import com.paw.key.core.designsystem.component.PawkeyButton import com.paw.key.core.designsystem.component.TopBar import com.paw.key.core.designsystem.theme.PawKeyTheme import com.paw.key.core.extension.noRippleClickable -import com.paw.key.presentation.ui.mypage.petinfo.model.PetProfileSideEffect -import com.paw.key.presentation.ui.mypage.petinfo.viewmodel.PetProfileViewModel +import com.paw.key.presentation.ui.mypage.route.petinfo.viewmodel.PetProfileViewModel import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileSideEffect import com.paw.key.presentation.ui.signup.component.FormField import com.paw.key.presentation.ui.signup.component.GenderSelector diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt index 28d52d74..131b3ae2 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/model/PetProfileContract.kt @@ -2,6 +2,7 @@ package com.paw.key.presentation.ui.mypage.route.petinfo.model import android.net.Uri import androidx.compose.runtime.Immutable +import com.paw.key.presentation.ui.mypage.model.PetInfoModel import com.paw.key.presentation.ui.signup.state.Gender @Immutable @@ -18,6 +19,7 @@ data class PetProfileState( val energyLevel: String = "", val socialLevel: String = "", val isLoading: Boolean = false, + val petInfo: PetInfoModel = PetInfoModel() ) sealed interface PetProfileSideEffect { diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt index 2583596d..d6f72542 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/petinfo/viewmodel/PetProfileViewModel.kt @@ -1,12 +1,15 @@ package com.paw.key.presentation.ui.mypage.route.petinfo.viewmodel +import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.paw.key.domain.repository.localstorage.LocalStorageRepository +import com.paw.key.domain.repository.mypage.MypageRepository import com.paw.key.domain.repository.user.UserRepository import com.paw.key.presentation.ui.mypage.model.toUiModel import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileSideEffect import com.paw.key.presentation.ui.mypage.route.petinfo.model.PetProfileState +import com.paw.key.presentation.ui.signup.state.Gender import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -19,20 +22,32 @@ import javax.inject.Inject @HiltViewModel class PetProfileViewModel @Inject constructor( + private val mypageRepository: MypageRepository, + private val localRepository: LocalStorageRepository, private val userRepository: UserRepository, - private val localRepository: LocalStorageRepository ) : ViewModel() { private val _state = MutableStateFlow(PetProfileState()) val state: StateFlow = _state.asStateFlow() - private val _sideEffect = MutableSharedFlow() // 필요 시 따로 Contract로 분리 가능 + private val _sideEffect = MutableSharedFlow() val sideEffect = _sideEffect.asSharedFlow() init { - getPetProfiles() + viewModelScope.launch { + getPetProfiles() + } } + fun onNameChange(value: String) = _state.update { it.copy(name = value) } + fun onBirthChange(value: String) = _state.update { it.copy(birthday = value) } + fun onGenderChange(value: Gender) = _state.update { it.copy(gender = value) } + fun onNeuteredChange(value: Boolean) = _state.update { it.copy(isNeutered = value) } + fun onBreedChange(breedName: String, breedId: Int) = + _state.update { it.copy(breed = breedName, breedId = breedId) } + + fun onImageChange(uri: Uri?) = _state.update { it.copy(imageUrl = uri) } + fun getPetProfiles() { viewModelScope.launch { val petId = localRepository.getPetId() @@ -49,4 +64,49 @@ class PetProfileViewModel @Inject constructor( } } } + + fun updatePet() { + if (_state.value.isLoading) return + val s = _state.value + + if (s.name.isBlank() || s.birthday.isBlank() || s.breedId == 0) { + viewModelScope.launch { + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("필수 정보를 모두 입력해주세요")) + } + return + } + + val formattedBirth = if (s.birthday.length == 8 && !s.birthday.contains("-")) { + "${s.birthday.substring(0, 4)}-${s.birthday.substring(4, 6)}-${ + s.birthday.substring( + 6, + 8 + ) + }" + } else { + s.birthday.replace(".", "-").replace("/", "-") + } + + viewModelScope.launch { + _state.update { it.copy(isLoading = true) } + + mypageRepository.updatePet( + name = s.name, + birth = formattedBirth, + gender = if (s.gender == Gender.MALE) "M" else "F", + isNeutered = s.isNeutered, + breedId = s.breedId, + imageId = s.imageId + ) + .onSuccess { + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar("반려견 정보가 수정되었습니다")) + _sideEffect.emit(PetProfileSideEffect.NavigateUp) + _state.update { it.copy(isLoading = false) } + } + .onFailure { e -> + _sideEffect.emit(PetProfileSideEffect.ShowSnackBar(e.message ?: "수정에 실패했습니다")) + _state.update { it.copy(isLoading = false) } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt index 529f4d91..64ead0f6 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/userinfo/viewmodel/UserProfileViewModel.kt @@ -4,9 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.paw.key.domain.repository.localstorage.LocalStorageRepository import com.paw.key.domain.repository.mypage.MypageRepository -import com.paw.key.domain.repository.userprofile.UserProfileRepository -import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileSideEffect -import com.paw.key.presentation.ui.mypage.userinfo.model.UserProfileState import com.paw.key.domain.repository.user.UserRepository import com.paw.key.presentation.ui.mypage.route.userinfo.model.UserProfileSideEffect import com.paw.key.presentation.ui.mypage.route.userinfo.model.UserProfileState @@ -14,7 +11,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -24,9 +20,7 @@ import javax.inject.Inject class UserProfileViewModel @Inject constructor( private val userRepository: UserRepository, - private val userProfileRepository: UserProfileRepository, private val mypageRepository: MypageRepository, - private val localRepository: LocalStorageRepository, ) : ViewModel() { private val _state = MutableStateFlow(UserProfileState()) @@ -53,7 +47,7 @@ class UserProfileViewModel @Inject constructor( name = result.name, gender = result.gender, birth = result.birth.orEmpty(), - email = result.email +// email = result.email ) } } From 151727effc9bd91fad3c025fc6b6a177ec1a13be Mon Sep 17 00:00:00 2001 From: Son Juwan Date: Tue, 31 Mar 2026 19:13:05 +0900 Subject: [PATCH 15/15] [feat/#165] : conflict resolve --- .../presentation/ui/mypage/route/courseinfo/model/CourseType.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt index cbc9417b..edca478c 100644 --- a/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt +++ b/app/src/main/java/com/paw/key/presentation/ui/mypage/route/courseinfo/model/CourseType.kt @@ -32,7 +32,7 @@ sealed interface CourseInfoSideEffect { } -fun RoutePostEntity.toCourseData() = CourseData( +fun RoutePostEntity.toUiModel() = CourseData( postId = postId, location = regionName, title = title,