Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.runnect.runnect.data.dto.response

import com.runnect.runnect.domain.entity.CourseRanking
import com.runnect.runnect.domain.entity.CourseRankingEntry
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseGetCourseRanking(
@SerialName("totalCount")
val totalCount: Long,
@SerialName("entries")
val entries: List<Entry>
) {
@Serializable
data class Entry(
@SerialName("rank")
val rank: Int,
@SerialName("userId")
val userId: Long,
@SerialName("nickname")
val nickname: String,
@SerialName("recordId")
val recordId: Long,
@SerialName("time")
val time: String,
@SerialName("pace")
val pace: String,
)

fun toCourseRanking() = CourseRanking(
totalCount = totalCount.toInt(),
entries = entries.map {
CourseRankingEntry(
rank = it.rank,
userId = it.userId.toInt(),
nickname = it.nickname,
recordId = it.recordId.toInt(),
time = it.time,
pace = it.pace,
)
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.runnect.runnect.data.dto.response

import com.runnect.runnect.domain.entity.MyCourseRanking
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseGetMyCourseRanking(
@SerialName("hasRecord")
val hasRecord: Boolean,
@SerialName("rank")
val rank: Int? = null,
@SerialName("userId")
val userId: Long,
@SerialName("nickname")
val nickname: String? = null,
@SerialName("recordId")
val recordId: Long? = null,
@SerialName("time")
val time: String? = null,
@SerialName("pace")
val pace: String? = null,
) {
fun toMyCourseRanking() = MyCourseRanking(
hasRecord = hasRecord,
rank = rank,
userId = userId.toInt(),
nickname = nickname,
time = time,
pace = pace,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import com.runnect.runnect.data.dto.request.RequestPutMyDrawCourse
import com.runnect.runnect.data.network.mapToFlowResult
import com.runnect.runnect.data.source.remote.RemoteCourseDataSource
import com.runnect.runnect.domain.entity.CourseDetail
import com.runnect.runnect.domain.entity.CourseRanking
import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse
import com.runnect.runnect.domain.entity.DiscoverSearchCourse
import com.runnect.runnect.domain.entity.DiscoverUploadCourse
import com.runnect.runnect.domain.entity.EditableCourseDetail
import com.runnect.runnect.domain.entity.EditableMyDrawCourseDetail
import com.runnect.runnect.domain.entity.MyCourseRanking
import com.runnect.runnect.domain.entity.MyDrawCourseDetail
import com.runnect.runnect.domain.entity.PostScrap
import com.runnect.runnect.domain.entity.RecommendCoursePagingData
Expand Down Expand Up @@ -55,6 +57,16 @@ class CourseRepositoryImpl @Inject constructor(
it.toCourseDetail()
}

override suspend fun getCourseRanking(courseId: Int, limit: Int): Flow<Result<CourseRanking>> =
remoteCourseDataSource.getCourseRanking(courseId = courseId, limit = limit).mapToFlowResult {
it.toCourseRanking()
}

override suspend fun getMyCourseRanking(courseId: Int): Flow<Result<MyCourseRanking>> =
remoteCourseDataSource.getMyCourseRanking(courseId = courseId).mapToFlowResult {
it.toMyCourseRanking()
}

override suspend fun getMyCourseLoad(): Flow<Result<List<DiscoverUploadCourse>>> {
return remoteCourseDataSource.getMyCourseLoad().mapToFlowResult {
it.toUploadCourses()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ 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.response.ResponseGetCourseDetail
import com.runnect.runnect.data.dto.response.ResponseGetCourseRanking
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverRecommend
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverSearch
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverUploadCourse
import com.runnect.runnect.data.dto.response.ResponseGetMyCourseRanking
import com.runnect.runnect.data.dto.response.ResponseGetMyDrawCourse
import com.runnect.runnect.data.dto.response.ResponseGetMyDrawDetail
import com.runnect.runnect.data.dto.response.ResponseGetMyScrapCourse
Expand Down Expand Up @@ -49,6 +51,17 @@ interface CourseService {
@GET("/api/public-course/marathon")
suspend fun getMarathonCourse(): Result<ResponseGetDiscoverMarathon>

@GET("/api/course/{courseId}/ranking")
suspend fun getCourseRanking(
@Path("courseId") courseId: Int,
@Query("limit") limit: Int,
): Result<ResponseGetCourseRanking>

@GET("/api/course/{courseId}/ranking/me")
suspend fun getMyCourseRanking(
@Path("courseId") courseId: Int,
): Result<ResponseGetMyCourseRanking>

@GET("/api/public-course")
suspend fun getRecommendCourse(
@Query("pageNo") pageNo: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ 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.response.ResponseGetCourseDetail
import com.runnect.runnect.data.dto.response.ResponseGetCourseRanking
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon
import com.runnect.runnect.data.dto.response.ResponseGetDiscoverRecommend
import com.runnect.runnect.data.dto.response.ResponseGetMyCourseRanking
import com.runnect.runnect.data.dto.response.ResponsePatchMyDrawCourseTitle
import com.runnect.runnect.data.dto.response.ResponsePatchPublicCourse
import com.runnect.runnect.data.dto.response.ResponsePostScrap
Expand Down Expand Up @@ -37,6 +39,12 @@ class RemoteCourseDataSource @Inject constructor(
suspend fun getCourseDetail(publicCourseId: Int): Result<ResponseGetCourseDetail> =
courseService.getCourseDetail(publicCourseId)

suspend fun getCourseRanking(courseId: Int, limit: Int): Result<ResponseGetCourseRanking> =
courseService.getCourseRanking(courseId, limit)

suspend fun getMyCourseRanking(courseId: Int): Result<ResponseGetMyCourseRanking> =
courseService.getMyCourseRanking(courseId)

suspend fun getMyCourseLoad() = courseService.getMyCourseLoad()

suspend fun postUploadMyCourse(requestPostPublicCourse: RequestPostPublicCourse) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.runnect.runnect.domain.entity

data class CourseRankingEntry(
val rank: Int,
val userId: Int,
val nickname: String,
val recordId: Int,
val time: String,
val pace: String,
)

data class CourseRanking(
val totalCount: Int,
val entries: List<CourseRankingEntry>,
)

data class MyCourseRanking(
val hasRecord: Boolean,
val rank: Int?,
val userId: Int,
val nickname: String?,
val time: String?,
val pace: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ 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.domain.entity.CourseDetail
import com.runnect.runnect.domain.entity.CourseRanking
import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse
import com.runnect.runnect.domain.entity.DiscoverSearchCourse
import com.runnect.runnect.domain.entity.DiscoverUploadCourse
import com.runnect.runnect.domain.entity.EditableCourseDetail
import com.runnect.runnect.domain.entity.EditableMyDrawCourseDetail
import com.runnect.runnect.domain.entity.MyCourseRanking
import com.runnect.runnect.domain.entity.MyDrawCourseDetail
import com.runnect.runnect.domain.entity.PostScrap
import com.runnect.runnect.domain.entity.RecommendCoursePagingData
Expand All @@ -32,6 +34,10 @@ interface CourseRepository {

suspend fun getCourseDetail(publicCourseId: Int): Flow<Result<CourseDetail>>

suspend fun getCourseRanking(courseId: Int, limit: Int): Flow<Result<CourseRanking>>

suspend fun getMyCourseRanking(courseId: Int): Flow<Result<MyCourseRanking>>

suspend fun getMyCourseLoad(): Flow<Result<List<DiscoverUploadCourse>>>

suspend fun getMyDrawDetail(courseId: Int): Flow<Result<MyDrawCourseDetail>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import android.view.View
import android.widget.EditText
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import coil3.load
Expand All @@ -32,9 +35,11 @@ import com.runnect.runnect.presentation.discover.DiscoverFragment.Companion.EXTR
import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse
import com.runnect.runnect.presentation.discover.search.DiscoverSearchActivity
import com.runnect.runnect.presentation.mypage.upload.MyUploadActivity
import com.runnect.runnect.presentation.detail.ranking.RecordRankingSection
import com.runnect.runnect.presentation.profile.ProfileActivity
import com.runnect.runnect.presentation.scheme.SchemeActivity
import com.runnect.runnect.presentation.state.UiStateV2
import com.runnect.runnect.presentation.ui.theme.RunnectTheme
import com.runnect.runnect.util.analytics.Analytics
import com.runnect.runnect.util.analytics.EventName
import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_SHARE
Expand Down Expand Up @@ -92,12 +97,34 @@ class CourseDetailActivity :

initCourseIdExtra()
initRootScreenExtra()
initRankingComposeView()
getCourseDetail()
addListener()
addObserver()
registerBackPressedCallback()
}

private fun initRankingComposeView() {
binding.composeCourseDetailRanking.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
RunnectTheme {
val rankingState by viewModel.courseRankingState.observeAsState()
val myRankingState by viewModel.myCourseRankingState.observeAsState()

val ranking = (rankingState as? UiStateV2.Success)?.data
val myRanking = (myRankingState as? UiStateV2.Success)?.data

RecordRankingSection(
ranking = ranking,
myRanking = myRanking,
onUserClick = { userId -> navigateToUserProfile(userId) },
)
}
}
}
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent?.let { newIntent ->
Expand Down Expand Up @@ -133,6 +160,13 @@ class CourseDetailActivity :
viewModel.getCourseDetail(publicCourseId)
}

private fun getCourseRanking() {
viewModel.getCourseRanking(publicCourseId)
if (!isVisitorMode) {
viewModel.getMyCourseRanking(publicCourseId)
}
}

private fun addListener() {
initScrapButtonClickListener()
initStartRunButtonClickListener()
Expand Down Expand Up @@ -220,7 +254,7 @@ class CourseDetailActivity :
private fun initUserInfoClickListener() {
binding.constCourseDetailUserInfo.setOnClickListener {
if (courseDetail.userId != -1) {
navigateToUserProfile()
navigateToUserProfile(courseDetail.userId)
}
}
}
Expand Down Expand Up @@ -407,9 +441,9 @@ class CourseDetailActivity :
applyScreenExitAnimation()
}

private fun navigateToUserProfile() {
private fun navigateToUserProfile(userId: Int) {
Intent(this@CourseDetailActivity, ProfileActivity::class.java).apply {
putExtra(EXTRA_COURSE_USER_ID, courseDetail.userId)
putExtra(EXTRA_COURSE_USER_ID, userId)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivity(this)
}
Expand All @@ -436,6 +470,8 @@ class CourseDetailActivity :

initDepartureLatLng()
initConnectedSpots()

getCourseRanking()
}

is UiStateV2.Failure -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import com.runnect.runnect.data.dto.request.RequestPostCourseScrap
import com.runnect.runnect.data.dto.response.ResponseDeleteUploadCourse
import com.runnect.runnect.domain.common.toLog
import com.runnect.runnect.domain.entity.CourseDetail
import com.runnect.runnect.domain.entity.CourseRanking
import com.runnect.runnect.domain.entity.EditableCourseDetail
import com.runnect.runnect.domain.entity.MyCourseRanking
import com.runnect.runnect.domain.entity.PostScrap
import com.runnect.runnect.domain.repository.CourseRepository
import com.runnect.runnect.domain.repository.UserRepository
Expand Down Expand Up @@ -42,6 +44,14 @@ class CourseDetailViewModel @Inject constructor(
val courseScrapState: LiveData<UiStateV2<PostScrap>>
get() = _courseScrapState

private val _courseRankingState = MutableLiveData<UiStateV2<CourseRanking>>()
val courseRankingState: LiveData<UiStateV2<CourseRanking>>
get() = _courseRankingState

private val _myCourseRankingState = MutableLiveData<UiStateV2<MyCourseRanking>>()
val myCourseRankingState: LiveData<UiStateV2<MyCourseRanking>>
get() = _myCourseRankingState

// 사용자가 수정할 수 있는 부분 (제목, 내용)
val _title = MutableLiveData<String>()
val title: String get() = _title.value ?: ""
Expand All @@ -58,6 +68,11 @@ class CourseDetailViewModel @Inject constructor(

private var savedCourseDetail = EditableCourseDetail("", "")

// CourseDetailActivity가 FLAG_ACTIVITY_REORDER_TO_FRONT로 재사용될 때(ProfileActivity 등)
// onNewIntent로 다른 courseId가 들어올 수 있다. 이전 courseId 요청이 늦게 응답으로 돌아와
// 최신 courseId의 결과를 덮어쓰지 않도록, 응답을 반영하기 전에 여전히 최신 요청인지 확인한다.
private var latestRankingCourseId: Int? = null

fun updateCourseDetailEditText(course: EditableCourseDetail) {
_title.value = course.title
_description.value = course.description
Expand Down Expand Up @@ -140,7 +155,46 @@ class CourseDetailViewModel @Inject constructor(
)
}

fun getCourseRanking(courseId: Int) = launchWithHandler {
latestRankingCourseId = courseId
_courseRankingState.value = UiStateV2.Loading

courseRepository.getCourseRanking(courseId = courseId, limit = RANKING_LIST_LIMIT)
.collectResult(
onSuccess = {
if (latestRankingCourseId == courseId) {
_courseRankingState.value = UiStateV2.Success(it)
}
},
onFailure = {
if (latestRankingCourseId == courseId) {
_courseRankingState.value = UiStateV2.Failure(it.toLog())
}
}
)
}

fun getMyCourseRanking(courseId: Int) = launchWithHandler {
latestRankingCourseId = courseId
_myCourseRankingState.value = UiStateV2.Loading

courseRepository.getMyCourseRanking(courseId = courseId)
.collectResult(
onSuccess = {
if (latestRankingCourseId == courseId) {
_myCourseRankingState.value = UiStateV2.Success(it)
}
},
onFailure = {
if (latestRankingCourseId == courseId) {
_myCourseRankingState.value = UiStateV2.Failure(it.toLog())
}
}
)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

companion object {
private const val CODE_AUTHORIZATION_ERROR = 401
private const val RANKING_LIST_LIMIT = 10
}
}
Loading
Loading