diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0c0a6718e..337e58fb3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -134,6 +134,9 @@
+
+) {
+ @Serializable
+ data class User(
+ @SerialName("nickname")
+ val nickname: String,
+ @SerialName("level")
+ val level: Int,
+ @SerialName("levelPercent")
+ val levelPercent: Int,
+ @SerialName("latestStamp")
+ val latestStamp: String,
+ @SerialName("userId")
+ val userId: Int
+ )
+
+ @Serializable
+ data class CourseData(
+ @SerialName("publicCourseId")
+ val publicCourseId: Int,
+ @SerialName("courseId")
+ val courseId: Int,
+ @SerialName("title")
+ val title: String,
+ @SerialName("image")
+ val image: String,
+ @SerialName("departure")
+ val departure: Departure,
+ @SerialName("scrapTF")
+ val scrapTF: Boolean,
+ ) {
+ @Serializable
+ data class Departure(
+ @SerialName("region")
+ val region: String,
+ @SerialName("city")
+ val city: String,
+ @SerialName("town")
+ val town: String,
+ @SerialName("name")
+ val name: String?,
+ @SerialName("detail")
+ val detail: String?
+ )
+ }
+
+ fun toUserProfile(): UserProfile {
+ val userCourseLists: List = courses.map { course ->
+ UserCourse(
+ publicCourseId = course.publicCourseId,
+ courseId = course.courseId,
+ title = course.title,
+ image = course.image,
+ departure = Departure(
+ region = course.departure.region,
+ city = course.departure.city,
+ town = course.departure.town,
+ detail = course.departure.detail,
+ name = course.departure.name ?: ""
+ ),
+ scrapTF = course.scrapTF
+ )
+ }
+
+ return UserProfile(
+ nickname = user.nickname,
+ level = user.level,
+ levelPercent = user.levelPercent,
+ latestStamp = user.latestStamp,
+ courseData = userCourseLists
+ )
+ }
+
+}
diff --git a/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt
new file mode 100644
index 000000000..cd5d3b0ae
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/data/dto/response/ResponsePostScrap.kt
@@ -0,0 +1,14 @@
+package com.runnect.runnect.data.dto.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponsePostScrap(
+ @SerialName("publicCourseId")
+ val publicCourseId: Long,
+ @SerialName("scrapCount")
+ val scrapCount: Long,
+ @SerialName("scrapTF")
+ val scrapTF: Boolean
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt
index c8622db4c..540a03cd1 100644
--- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt
+++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt
@@ -12,6 +12,7 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyDrawCourse
import com.runnect.runnect.data.dto.response.ResponsePostMyHistory
import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse
import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload
+import com.runnect.runnect.data.dto.response.ResponsePostScrap
import com.runnect.runnect.data.source.remote.RemoteCourseDataSource
import com.runnect.runnect.domain.entity.DiscoverSearchCourse
import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.*
@@ -100,7 +101,7 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc
override suspend fun postCourseScrap(
requestPostCourseScrap: RequestPostCourseScrap
- ): Result = runCatching {
+ ): Result = runCatching {
remoteCourseDataSource.postCourseScrap(requestPostCourseScrap = requestPostCourseScrap).data
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt
index 2b6350af5..8c0bf1081 100644
--- a/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt
+++ b/app/src/main/java/com/runnect/runnect/data/repository/UserRepositoryImpl.kt
@@ -1,6 +1,7 @@
package com.runnect.runnect.data.repository
import com.runnect.runnect.data.dto.HistoryInfoDTO
+import com.runnect.runnect.domain.entity.UserProfile
import com.runnect.runnect.data.dto.UserUploadCourseDTO
import com.runnect.runnect.data.dto.request.RequestDeleteHistory
import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse
@@ -9,9 +10,9 @@ import com.runnect.runnect.data.dto.request.RequestPatchNickName
import com.runnect.runnect.data.dto.response.ResponseDeleteHistory
import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse
import com.runnect.runnect.data.dto.response.ResponseDeleteUser
+import com.runnect.runnect.data.dto.response.ResponseGetUser
import com.runnect.runnect.data.dto.response.ResponsePatchHistoryTitle
import com.runnect.runnect.data.dto.response.ResponsePatchUserNickName
-import com.runnect.runnect.data.dto.response.ResponseGetUser
import com.runnect.runnect.data.source.remote.RemoteUserDataSource
import com.runnect.runnect.domain.repository.UserRepository
import com.runnect.runnect.util.extension.toData
@@ -36,6 +37,11 @@ class UserRepositoryImpl @Inject constructor(private val remoteUserDataSource: R
.toMutableList()
}
+ override suspend fun getUserProfile(userId: Int): Result =
+ runCatching {
+ remoteUserDataSource.getUserProfile(userId).data?.toUserProfile()
+ }
+
override suspend fun putDeleteUploadCourse(
requestDeleteUploadCourse: RequestDeleteUploadCourse
): Result = runCatching {
diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt
index 64a8225ad..aa579c61b 100644
--- a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt
+++ b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt
@@ -1,10 +1,10 @@
package com.runnect.runnect.data.service
+import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse
import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
+import com.runnect.runnect.data.dto.request.RequestPostPublicCourse
import com.runnect.runnect.data.dto.request.RequestPostRunningHistory
import com.runnect.runnect.data.dto.request.RequestPutMyDrawCourse
-import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse
-import com.runnect.runnect.data.dto.request.RequestPostPublicCourse
import com.runnect.runnect.data.dto.response.*
import com.runnect.runnect.data.dto.response.base.BaseResponse
import okhttp3.MultipartBody
@@ -25,7 +25,7 @@ interface CourseService {
@POST("/api/scrap")
suspend fun postCourseScrap(
@Body requestPostCourseScrap: RequestPostCourseScrap,
- ): BaseResponse
+ ): BaseResponse
@GET("/api/public-course/search?")
suspend fun getCourseSearch(
diff --git a/app/src/main/java/com/runnect/runnect/data/service/UserService.kt b/app/src/main/java/com/runnect/runnect/data/service/UserService.kt
index 62fb27610..780a22bf9 100644
--- a/app/src/main/java/com/runnect/runnect/data/service/UserService.kt
+++ b/app/src/main/java/com/runnect/runnect/data/service/UserService.kt
@@ -48,4 +48,10 @@ interface UserService {
@DELETE("api/user")
suspend fun deleteUser(): ResponseDeleteUser
+
+ // 유저 프로필 조회
+ @GET("/api/user/{profileUserId}")
+ suspend fun getUserProfile(
+ @Path("profileUserId") userId: Int,
+ ): BaseResponse
}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt
index fecd2ff75..ffb83942d 100644
--- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt
+++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt
@@ -1,16 +1,17 @@
package com.runnect.runnect.data.source.remote
-import com.runnect.runnect.data.service.CourseService
+import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse
import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
+import com.runnect.runnect.data.dto.request.RequestPostPublicCourse
import com.runnect.runnect.data.dto.request.RequestPostRunningHistory
import com.runnect.runnect.data.dto.request.RequestPutMyDrawCourse
-import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse
-import com.runnect.runnect.data.dto.request.RequestPostPublicCourse
import com.runnect.runnect.data.dto.response.ResponseGetCourseDetail
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon
-import com.runnect.runnect.data.dto.response.ResponsePatchPublicCourse
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverRecommend
+import com.runnect.runnect.data.dto.response.ResponsePatchPublicCourse
+import com.runnect.runnect.data.dto.response.ResponsePostScrap
import com.runnect.runnect.data.dto.response.base.BaseResponse
+import com.runnect.runnect.data.service.CourseService
import okhttp3.MultipartBody
import okhttp3.RequestBody
import javax.inject.Inject
@@ -27,7 +28,7 @@ class RemoteCourseDataSource @Inject constructor(
): BaseResponse =
courseService.getRecommendCourse(pageNo = pageNo, ordering = ordering)
- suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): BaseResponse =
+ suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): BaseResponse =
courseService.postCourseScrap(requestPostCourseScrap)
suspend fun getCourseSearch(keyword: String) = courseService.getCourseSearch(keyword)
diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt
index 0964340c5..19d9b64e0 100644
--- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt
+++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteUserDataSource.kt
@@ -1,12 +1,21 @@
package com.runnect.runnect.data.source.remote
-import com.runnect.runnect.data.service.UserService
import com.runnect.runnect.data.dto.request.RequestDeleteHistory
import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse
import com.runnect.runnect.data.dto.request.RequestPatchHistoryTitle
import com.runnect.runnect.data.dto.request.RequestPatchNickName
-import com.runnect.runnect.data.dto.response.*
+import com.runnect.runnect.data.dto.response.ResponseDeleteHistory
+import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse
+import com.runnect.runnect.data.dto.response.ResponseDeleteUser
+import com.runnect.runnect.data.dto.response.ResponseGetMyHistory
+import com.runnect.runnect.data.dto.response.ResponseGetMyStamp
+import com.runnect.runnect.data.dto.response.ResponseGetUser
+import com.runnect.runnect.data.dto.response.ResponseGetUserProfile
+import com.runnect.runnect.data.dto.response.ResponseGetUserUploadCourse
+import com.runnect.runnect.data.dto.response.ResponsePatchHistoryTitle
+import com.runnect.runnect.data.dto.response.ResponsePatchUserNickName
import com.runnect.runnect.data.dto.response.base.BaseResponse
+import com.runnect.runnect.data.service.UserService
import javax.inject.Inject
class RemoteUserDataSource @Inject constructor(private val userService: UserService) {
@@ -16,7 +25,11 @@ class RemoteUserDataSource @Inject constructor(private val userService: UserServ
suspend fun getMyStamp(): ResponseGetMyStamp = userService.getMyStamp()
suspend fun getRecord(): ResponseGetMyHistory = userService.getRecord()
- suspend fun getUserUploadCourse(): ResponseGetUserUploadCourse = userService.getUserUploadCourse()
+ suspend fun getUserUploadCourse(): ResponseGetUserUploadCourse =
+ userService.getUserUploadCourse()
+
+ suspend fun getUserProfile(userId: Int): BaseResponse =
+ userService.getUserProfile(userId)
suspend fun putDeleteUploadCourse(
requestDeleteUploadCourse: RequestDeleteUploadCourse
diff --git a/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt b/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt
index ca0476e67..4fe5a6cb1 100644
--- a/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt
+++ b/app/src/main/java/com/runnect/runnect/domain/entity/CourseDetail.kt
@@ -15,4 +15,5 @@ data class CourseDetail(
val path: List>,
val distance: String,
val departure: String,
+ val userId: Int
)
diff --git a/app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt b/app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt
new file mode 100644
index 000000000..99276137e
--- /dev/null
+++ b/app/src/main/java/com/runnect/runnect/domain/entity/UserProfile.kt
@@ -0,0 +1,26 @@
+package com.runnect.runnect.domain.entity
+
+data class UserProfile(
+ val nickname: String,
+ val level: Int,
+ val levelPercent: Int,
+ val latestStamp: String,
+ val courseData: List
+)
+
+data class UserCourse(
+ val publicCourseId: Int,
+ val courseId: Int,
+ val title: String,
+ val image: String,
+ val departure: Departure,
+ var scrapTF: Boolean,
+)
+
+data class Departure(
+ val region: String,
+ val city: String,
+ val town: String,
+ val detail: String?,
+ val name: String
+)
diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt
index 02665c5e1..ec56b0e9b 100644
--- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt
+++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt
@@ -10,12 +10,13 @@ import com.runnect.runnect.data.dto.response.ResponseGetMyDrawDetail
import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload
import com.runnect.runnect.data.dto.response.ResponsePostMyDrawCourse
import com.runnect.runnect.data.dto.response.ResponsePostMyHistory
+import com.runnect.runnect.data.dto.response.ResponsePostScrap
import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse
import com.runnect.runnect.domain.entity.CourseDetail
+import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse
import com.runnect.runnect.domain.entity.DiscoverSearchCourse
-import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.*
-import com.runnect.runnect.domain.entity.RecommendCoursePagingData
import com.runnect.runnect.domain.entity.EditableCourseDetail
+import com.runnect.runnect.domain.entity.RecommendCoursePagingData
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Response
@@ -23,7 +24,10 @@ import retrofit2.Response
interface CourseRepository {
suspend fun getMarathonCourse(): Result?>
- suspend fun getRecommendCourse(pageNo: String, ordering: String): Result
+ suspend fun getRecommendCourse(
+ pageNo: String,
+ ordering: String
+ ): Result
suspend fun getCourseSearch(keyword: String): Result?>
@@ -40,6 +44,7 @@ interface CourseRepository {
suspend fun uploadCourse(
image: MultipartBody.Part, courseCreateRequestDto: RequestBody
): Response
+
suspend fun getCourseDetail(publicCourseId: Int): Result
suspend fun patchPublicCourse(
@@ -47,5 +52,5 @@ interface CourseRepository {
requestPatchPublicCourse: RequestPatchPublicCourse
): Result
- suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): Result
+ suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): Result
}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt
index 7f8f4db02..bb2713628 100644
--- a/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt
+++ b/app/src/main/java/com/runnect/runnect/domain/repository/UserRepository.kt
@@ -1,12 +1,18 @@
package com.runnect.runnect.domain.repository
import com.runnect.runnect.data.dto.HistoryInfoDTO
+import com.runnect.runnect.domain.entity.UserProfile
import com.runnect.runnect.data.dto.UserUploadCourseDTO
import com.runnect.runnect.data.dto.request.RequestDeleteHistory
import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse
import com.runnect.runnect.data.dto.request.RequestPatchHistoryTitle
import com.runnect.runnect.data.dto.request.RequestPatchNickName
-import com.runnect.runnect.data.dto.response.*
+import com.runnect.runnect.data.dto.response.ResponseDeleteHistory
+import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse
+import com.runnect.runnect.data.dto.response.ResponseDeleteUser
+import com.runnect.runnect.data.dto.response.ResponseGetUser
+import com.runnect.runnect.data.dto.response.ResponsePatchHistoryTitle
+import com.runnect.runnect.data.dto.response.ResponsePatchUserNickName
interface UserRepository {
suspend fun getUserInfo(): ResponseGetUser
@@ -19,6 +25,8 @@ interface UserRepository {
suspend fun getUserUploadCourse(): MutableList
+ suspend fun getUserProfile(userId: Int): Result
+
suspend fun putDeleteUploadCourse(
requestDeleteUploadCourse: RequestDeleteUploadCourse
): Result
diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt
index 1a3a13e38..59c90fde6 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt
@@ -29,21 +29,26 @@ import com.runnect.runnect.data.dto.CourseData
import com.runnect.runnect.databinding.ActivityCourseDetailBinding
import com.runnect.runnect.domain.entity.CourseDetail
import com.runnect.runnect.domain.entity.EditableCourseDetail
-import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse
import com.runnect.runnect.presentation.MainActivity
import com.runnect.runnect.presentation.countdown.CountDownActivity
-import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.*
+import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_DISCOVER
+import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_DISCOVER_SEARCH
+import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.COURSE_STORAGE_SCRAP
+import com.runnect.runnect.presentation.detail.CourseDetailRootScreen.MY_PAGE_UPLOAD_COURSE
import com.runnect.runnect.presentation.discover.DiscoverFragment.Companion.EXTRA_EDITABLE_DISCOVER_COURSE
+import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse
import com.runnect.runnect.presentation.discover.search.DiscoverSearchActivity
import com.runnect.runnect.presentation.login.LoginActivity
import com.runnect.runnect.presentation.mypage.upload.MyUploadActivity
+import com.runnect.runnect.presentation.profile.ProfileActivity
import com.runnect.runnect.presentation.state.UiStateV2
import com.runnect.runnect.util.custom.dialog.CommonDialogFragment
import com.runnect.runnect.util.custom.dialog.CommonDialogText
-import com.runnect.runnect.util.custom.popup.PopupItem
import com.runnect.runnect.util.custom.dialog.RequireLoginDialogFragment
+import com.runnect.runnect.util.custom.popup.PopupItem
import com.runnect.runnect.util.custom.popup.RunnectPopupMenu
import com.runnect.runnect.util.custom.toast.RunnectToast
+import com.runnect.runnect.util.extension.applyScreenEnterAnimation
import com.runnect.runnect.util.extension.applyScreenExitAnimation
import com.runnect.runnect.util.extension.getCompatibleSerializableExtra
import com.runnect.runnect.util.extension.getStampResId
@@ -51,7 +56,8 @@ import com.runnect.runnect.util.extension.hideKeyboard
import com.runnect.runnect.util.extension.showSnackbar
import com.runnect.runnect.util.extension.showToast
import com.runnect.runnect.util.extension.showWebBrowser
-import com.runnect.runnect.util.mode.ScreenMode.*
+import com.runnect.runnect.util.mode.ScreenMode.EditMode
+import com.runnect.runnect.util.mode.ScreenMode.ReadOnlyMode
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@@ -129,6 +135,15 @@ class CourseDetailActivity :
applyScreenExitAnimation()
}
+ private fun navigateToUserProfileWithBundle() {
+ Intent(this@CourseDetailActivity, ProfileActivity::class.java).apply {
+ putExtra(EXTRA_COURSE_USER_ID, courseDetail.userId)
+ addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ startActivity(this)
+ }
+ applyScreenEnterAnimation()
+ }
+
private fun handleBackButtonByCurrentScreenMode() {
when (viewModel.currentScreenMode) {
is ReadOnlyMode -> navigateToPreviousScreen()
@@ -136,6 +151,16 @@ class CourseDetailActivity :
}
}
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ intent?.let { newIntent ->
+ newIntent.getCompatibleSerializableExtra(EXTRA_ROOT_SCREEN)
+ ?.let { rootScreen = it }
+ publicCourseId = newIntent.getIntExtra(EXTRA_PUBLIC_COURSE_ID, 0)
+ getCourseDetail()
+ }
+ }
+
private fun navigateToPreviousScreen() {
if (isFromDeepLink) {
navigateToMainScreenWithBundle()
@@ -178,6 +203,7 @@ class CourseDetailActivity :
initScrapButtonClickListener()
initStartRunButtonClickListener()
initEditFinishButtonClickListener()
+ initUserInfoClickListener()
initShareButtonClickListener()
initShowMoreButtonClickListener()
@@ -215,6 +241,12 @@ class CourseDetailActivity :
}
}
+ private fun initUserInfoClickListener() {
+ binding.constCourseDetailUserInfo.setOnClickListener {
+ navigateToUserProfileWithBundle()
+ }
+ }
+
// todo: 함수를 더 작게 분리하는 게 좋을 거 같아요! @우남
private fun sendKakaoLink(title: String, desc: String, image: String) {
// 메시지 템플릿 만들기 (피드형)
@@ -608,6 +640,7 @@ class CourseDetailActivity :
private const val EXTRA_COURSE_DATA = "CourseData"
private const val EXTRA_FRAGMENT_REPLACEMENT_DIRECTION = "fragmentReplacementDirection"
private const val EXTRA_FROM_COURSE_DETAIL = "fromCourseDetail"
+ private const val EXTRA_COURSE_USER_ID = "courseUserId"
private const val POPUP_MENU_X_OFFSET = 17
private const val POPUP_MENU_Y_OFFSET = -10
diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt
index 400a5ca05..895cd711b 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailViewModel.kt
@@ -5,14 +5,15 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
-import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
import com.runnect.runnect.data.dto.request.RequestDeleteUploadCourse
import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse
+import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse
-import com.runnect.runnect.domain.repository.CourseRepository
-import com.runnect.runnect.domain.repository.UserRepository
+import com.runnect.runnect.data.dto.response.ResponsePostScrap
import com.runnect.runnect.domain.entity.CourseDetail
import com.runnect.runnect.domain.entity.EditableCourseDetail
+import com.runnect.runnect.domain.repository.CourseRepository
+import com.runnect.runnect.domain.repository.UserRepository
import com.runnect.runnect.presentation.state.UiStateV2
import com.runnect.runnect.util.mode.ScreenMode
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -37,8 +38,8 @@ class CourseDetailViewModel @Inject constructor(
val courseDeleteState: LiveData>
get() = _courseDeleteState
- private var _courseScrapState = MutableLiveData>()
- val courseScrapState: LiveData>
+ private var _courseScrapState = MutableLiveData>()
+ val courseScrapState: LiveData>
get() = _courseScrapState
// 플래그 변수
diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt
index f76fe6d8c..e6c5e129f 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
+import com.runnect.runnect.data.dto.response.ResponsePostScrap
import com.runnect.runnect.domain.entity.DiscoverMultiViewItem
import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.*
import com.runnect.runnect.domain.entity.DiscoverBanner
@@ -39,8 +40,8 @@ class DiscoverViewModel @Inject constructor(
val nextPageState: LiveData>>
get() = _nextPageState
- private val _courseScrapState = MutableLiveData>()
- val courseScrapState: LiveData>
+ private val _courseScrapState = MutableLiveData>()
+ val courseScrapState: LiveData>
get() = _courseScrapState
private val _multiViewItems: MutableList> = mutableListOf()
diff --git a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt
index 361bf01dd..21d30c6d7 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileActivity.kt
@@ -1,27 +1,142 @@
package com.runnect.runnect.presentation.profile
+import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
+import androidx.core.view.isVisible
import com.runnect.runnect.R
import com.runnect.runnect.binding.BindingActivity
import com.runnect.runnect.databinding.ActivityProfileBinding
+import com.runnect.runnect.presentation.detail.CourseDetailActivity
+import com.runnect.runnect.presentation.state.UiStateV2
+import com.runnect.runnect.util.extension.applyScreenEnterAnimation
+import com.runnect.runnect.util.extension.showSnackbar
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ProfileActivity : BindingActivity(R.layout.activity_profile) {
private val viewModel: ProfileViewModel by viewModels()
private lateinit var adapter: ProfileCourseAdapter
+ private var userId: Int = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.vm = viewModel
binding.lifecycleOwner = this
initAdapter()
+ addListener()
+ addObserver()
+ getIntentExtra()
+ getUserProfile()
+ }
+
+ private fun getIntentExtra() {
+ userId = intent.getIntExtra(EXTRA_COURSE_USER_ID, -1)
}
private fun initAdapter() {
- adapter = ProfileCourseAdapter().also { adapter ->
+ adapter = ProfileCourseAdapter(onScrapButtonClick = { courseId, scrapTF ->
+ viewModel.postCourseScrap(courseId = courseId, scrapTF = scrapTF)
+ }, onCourseItemClick = { courseId ->
+ navigateToCourseDetail(courseId)
+ }).also { adapter ->
binding.rvProfileUploadCourse.adapter = adapter
- adapter.submitList(viewModel.courseList)
}
}
+
+ private fun addListener() {
+ initBackButtonClickListener()
+ }
+
+ private fun addObserver() {
+ setupUserProfileGetStateObserver()
+ setupCourseScrapPostStateObserver()
+ }
+
+ private fun navigateToCourseDetail(courseId: Int) {
+ Intent(this@ProfileActivity, CourseDetailActivity::class.java).apply {
+ putExtra(EXTRA_PUBLIC_COURSE_ID, courseId)
+ addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ startActivity(this)
+ }
+ applyScreenEnterAnimation()
+ }
+
+ private fun initBackButtonClickListener() {
+ binding.ivProfileBack.setOnClickListener {
+ finish()
+ }
+ }
+
+ private fun getUserProfile() {
+ viewModel.getUserProfile(userId = userId)
+ }
+
+ private fun setupUserProfileGetStateObserver() {
+ viewModel.userProfileState.observe(this) { state ->
+ when (state) {
+ is UiStateV2.Loading -> {
+ activateLoadingProgressBar()
+ }
+
+ is UiStateV2.Success -> {
+ deactivateLoadingProgressBar()
+ binding.data = state.data
+ adapter.submitList(state.data.courseData)
+ }
+
+ is UiStateV2.Failure -> {
+ deactivateLoadingProgressBar()
+ this.showSnackbar(binding.root, state.msg)
+ }
+
+ else -> {
+
+ }
+ }
+
+ }
+ }
+
+ private fun setupCourseScrapPostStateObserver() {
+ viewModel.courseScrapState.observe(this) { state ->
+ when (state) {
+ is UiStateV2.Loading -> {
+
+ }
+
+ is UiStateV2.Success -> {
+ state.data?.let { it ->
+ adapter.updateCourseItem(
+ courseId = it.publicCourseId.toInt(),
+ scrapTF = it.scrapTF
+ )
+ }
+ }
+
+ is UiStateV2.Failure -> {
+ this.showSnackbar(binding.root, state.msg)
+ }
+
+ else -> {
+
+ }
+ }
+
+ }
+ }
+
+ private fun activateLoadingProgressBar() {
+ binding.clProfile.isVisible = false
+ binding.pbProfileIntermediate.isVisible = true
+ }
+
+ private fun deactivateLoadingProgressBar() {
+ binding.clProfile.isVisible = true
+ binding.pbProfileIntermediate.isVisible = false
+ }
+
+ companion object {
+ private const val EXTRA_COURSE_USER_ID = "courseUserId"
+ private const val EXTRA_PUBLIC_COURSE_ID = "publicCourseId"
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt
index 11ddf62e4..8d4966a37 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileCourseAdapter.kt
@@ -4,12 +4,15 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
-import com.runnect.runnect.data.dto.ProfileCourseData
+import com.runnect.runnect.domain.entity.UserCourse
import com.runnect.runnect.databinding.ItemProfileCourseBinding
import com.runnect.runnect.util.callback.diff.ItemDiffCallback
+import com.runnect.runnect.util.extension.setOnSingleClickListener
class ProfileCourseAdapter(
-) : ListAdapter(diffUtil) {
+ private val onScrapButtonClick: (Int, Boolean) -> Unit,
+ private val onCourseItemClick: (Int) -> Unit
+) : ListAdapter(diffUtil) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UploadedCourseViewHolder {
return UploadedCourseViewHolder(
@@ -17,7 +20,9 @@ class ProfileCourseAdapter(
LayoutInflater.from(parent.context),
parent,
false
- )
+ ),
+ onScrapButtonClick = onScrapButtonClick,
+ onCourseItemClick = onCourseItemClick
)
}
@@ -26,17 +31,35 @@ class ProfileCourseAdapter(
}
class UploadedCourseViewHolder(
- private val binding: ItemProfileCourseBinding
+ private val binding: ItemProfileCourseBinding,
+ private val onScrapButtonClick: (Int, Boolean) -> Unit,
+ private val onCourseItemClick: (Int) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
- fun bind(profileCourseData: ProfileCourseData) {
+ fun bind(userCourse: UserCourse) {
with(binding) {
- data = profileCourseData
+ data = userCourse
+ ivItemProfileCourseHeart.setOnSingleClickListener {
+ onScrapButtonClick(userCourse.publicCourseId, !userCourse.scrapTF)
+ }
+
+ clItemProfileCourse.setOnSingleClickListener {
+ onCourseItemClick(userCourse.publicCourseId)
+ }
+ }
+ }
+ }
+
+ fun updateCourseItem(courseId: Int, scrapTF: Boolean) {
+ currentList.forEachIndexed { index, userCourseData ->
+ if (userCourseData.publicCourseId == courseId) {
+ userCourseData.scrapTF = scrapTF
+ notifyItemChanged(index)
}
}
}
companion object {
- private val diffUtil = ItemDiffCallback(
+ private val diffUtil = ItemDiffCallback(
onItemsTheSame = { old, new -> old.courseId == new.courseId },
onContentsTheSame = { old, new -> old == new }
)
diff --git a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt
index e5667de47..b518cc418 100644
--- a/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt
+++ b/app/src/main/java/com/runnect/runnect/presentation/profile/ProfileViewModel.kt
@@ -1,40 +1,72 @@
package com.runnect.runnect.presentation.profile
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.runnect.runnect.data.dto.DepartureData
-import com.runnect.runnect.data.dto.ProfileCourseData
-
-class ProfileViewModel : ViewModel() {
- val courseList: List = generateMockData()
-
- private fun generateMockData(): List {
- val mockDataList = mutableListOf()
-
- val mockData1 = ProfileCourseData(
- publicCourseId = 1,
- courseId = 101,
- title = "제목 1",
- image = "이미지 1",
- departure = DepartureData(
- region = "지역 1",
- city = "도시 1",
- town = "동네 1",
- detail = null,
- name = "출발지 1"
- ),
- scrapTF = true
- )
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- mockDataList.add(mockData1)
- return mockDataList
+import androidx.lifecycle.viewModelScope
+import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
+import com.runnect.runnect.data.dto.response.ResponsePostScrap
+import com.runnect.runnect.domain.entity.UserProfile
+import com.runnect.runnect.domain.repository.CourseRepository
+import com.runnect.runnect.domain.repository.UserRepository
+import com.runnect.runnect.presentation.state.UiStateV2
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+
+@HiltViewModel
+class ProfileViewModel @Inject constructor(
+ private val userRepository: UserRepository,
+ private val courseRepository: CourseRepository
+) :
+ ViewModel() {
+
+ private val _courseScrapState = MutableLiveData>()
+ val courseScrapState: LiveData>
+ get() = _courseScrapState
+
+ private val _userProfileState = MutableLiveData>()
+ val userProfileState: LiveData>
+ get() = _userProfileState
+
+
+ fun getUserProfile(userId: Int) {
+ viewModelScope.launch {
+ _userProfileState.value = UiStateV2.Loading
+
+ userRepository.getUserProfile(userId = userId)
+ .onSuccess { profileData ->
+ if (profileData == null) {
+ _userProfileState.value = UiStateV2.Failure("PROFILE DATA IS NULL")
+ Timber.d("PROFILE DATA IS NULL")
+ return@launch
+ }
+ _userProfileState.value = UiStateV2.Success(profileData)
+ Timber.d("GET PROFILE DATA SUCCESS")
+ }
+ .onFailure { error ->
+ _userProfileState.value = UiStateV2.Failure(error.message.toString())
+ Timber.e("GET PROFILE DATA FAILURE")
+ }
+ }
+ }
+
+ fun postCourseScrap(courseId: Int, scrapTF: Boolean) {
+ viewModelScope.launch {
+ _courseScrapState.value = UiStateV2.Loading
+ courseRepository.postCourseScrap(
+ RequestPostCourseScrap(
+ publicCourseId = courseId, scrapTF = scrapTF.toString()
+ )
+ ).onSuccess { response ->
+ Timber.d("POST COURSE SCRAP SUCCESS")
+ _courseScrapState.value = UiStateV2.Success(response)
+ }.onFailure { exception ->
+ Timber.e("POST COURSE SCRAP FAILURE")
+ _courseScrapState.value = UiStateV2.Failure(exception.message.toString())
+ }
+ }
}
}
+
diff --git a/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt b/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt
index 6f60357be..782755995 100644
--- a/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt
+++ b/app/src/main/java/com/runnect/runnect/util/binding/BindingAdapter.kt
@@ -12,6 +12,22 @@ fun ImageView.setLocalImageByResourceId(resId: Int) {
load(resId)
}
+@BindingAdapter("setStampImageByResourceId")
+fun ImageView.setStampImageByResourceId(stampId: String?) {
+ val resNameParam = "mypage_img_stamp_"
+ val resType = "drawable"
+ val packageName = context.packageName
+
+ var resName = ""
+ resName = if (stampId == "CSPR0") {
+ "${resNameParam}basic"
+ } else {
+ "${resNameParam}$stampId"
+ }
+ val resId = context.resources.getIdentifier(resName, resType, packageName)
+ setImageResource(resId)
+}
+
@BindingAdapter("setImageUrl")
fun ImageView.setImageUrl(url: String?) {
if (url == null) return
diff --git a/app/src/main/res/layout/activity_course_detail.xml b/app/src/main/res/layout/activity_course_detail.xml
index 98a48a8c1..32b5f29bb 100644
--- a/app/src/main/res/layout/activity_course_detail.xml
+++ b/app/src/main/res/layout/activity_course_detail.xml
@@ -178,7 +178,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
- android:layout_marginTop="14dp"
+ android:layout_marginTop="18dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_course_detail_title"
app:srcCompat="@drawable/all_star" />
@@ -200,7 +200,7 @@
android:id="@+id/tv_course_detail_distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="34dp"
+ android:layout_marginStart="36dp"
android:fontFamily="@font/pretendard_regular"
android:text="@{courseDetail.distance}"
android:textColor="@color/G1"
@@ -227,7 +227,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="9dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_course_detail_distance_indicator"
app:srcCompat="@drawable/all_star" />
@@ -249,7 +249,7 @@
android:id="@+id/tv_course_detail_departure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="23dp"
+ android:layout_marginStart="25dp"
android:ellipsize="end"
android:fontFamily="@font/pretendard_regular"
android:maxLength="25"
@@ -299,7 +299,7 @@
android:id="@+id/cl_course_detail_bottom_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:paddingBottom="34dp"
+ android:layout_marginBottom="18dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
@@ -308,8 +308,8 @@
android:id="@+id/iv_course_detail_scrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="26dp"
- android:layout_marginTop="13dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="17dp"
android:layout_marginEnd="17dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -319,11 +319,11 @@
android:id="@+id/tv_course_detail_scrap_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
+ android:layout_marginTop="2dp"
android:fontFamily="@font/pretendard_semibold"
android:text="@{courseDetail.scrapCount}"
android:textColor="@color/G2"
- android:textSize="11sp"
+ android:textSize="10sp"
app:layout_constraintEnd_toEndOf="@id/iv_course_detail_scrap"
app:layout_constraintStart_toStartOf="@id/iv_course_detail_scrap"
app:layout_constraintTop_toBottomOf="@id/iv_course_detail_scrap"
@@ -332,9 +332,10 @@
-
+
@@ -13,6 +15,7 @@
@@ -27,16 +30,20 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/M4"
+ app:contentInsetStart="0dp"
app:layout_constraintTop_toTopOf="@id/cl_profile_toolbar">
+
+ setStampImageByResourceId="@{data.latestStamp}" />
+ android:text="@{data.nickname}" />
-
+ android:text="@{Integer.toString(data.level)}"/>
+ android:progress="@{data.levelPercent}" />
+ android:text="@{Integer.toString(data.levelPercent)}" />
+ type="com.runnect.runnect.domain.entity.UserCourse" />
@@ -19,15 +20,15 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="3dp"
android:layout_marginBottom="20dp"
- app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:scale_base_height="154"
app:scale_base_width="162">