Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TNT-115] 트레이너, 트레이니 연결 기능 구현 #65

Merged
merged 23 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4e552e6
[TNT-115] feat: 트레이너 초대코드 확인 API 호출 구현
SeonJeongk Feb 4, 2025
6324b6e
[TNT-115] feat: 트레이너 초대코드 확인 API 연동
SeonJeongk Feb 4, 2025
47beeb2
[TNT-115] feat: 트레이너 초대코드 재발급 API 호출 구현
SeonJeongk Feb 4, 2025
e81fc46
[TNT-115] feat: 트레이너 초대코드 재발급 API 연동
SeonJeongk Feb 4, 2025
edc4ec2
[TNT-115] feat: 트레이너 초대코드 인증 API 호출 구현
SeonJeongk Feb 4, 2025
6aaebac
[TNT-115] feat: 트레이너 초대코드 인증 API 연동
SeonJeongk Feb 4, 2025
acc2217
[TNT-115] design: Dialog 버튼 색 수정
SeonJeongk Feb 4, 2025
ae0ef96
[TNT-115] fix: OnNextClick으로 통일
SeonJeongk Feb 5, 2025
c4c13f2
[TNT-115] feat: PT 수업정보 에러 문구 추가
SeonJeongk Feb 5, 2025
53f5651
[TNT-115] feat: 트레이너 연결 요청 API 호출 구현
SeonJeongk Feb 6, 2025
71aa9ba
[TNT-115] feat: 트레이너 연결 요청 API 연동
SeonJeongk Feb 6, 2025
6ce837f
[TNT-115] design: 트레이니 프로필 확인 화면 UI 수정
SeonJeongk Feb 6, 2025
2a3cadd
[TNT-115] chore: read, write timeout 시간 연장
SeonJeongk Feb 6, 2025
e4bb950
[TNT-115] feat: UserLocalDataSource 생성
SeonJeongk Feb 6, 2025
e2c69a4
[TNT-115] feat: 연결된 트레이니 정보 최초로 불러오기 API 호출 구현
SeonJeongk Feb 6, 2025
b99dc86
[TNT-115] feat: 연결된 트레이니 정보 최초로 불러오기 API 연동
SeonJeongk Feb 6, 2025
8ed2f4a
[TNT-115] fix: 화면 레이아웃 및 유효 조건 수정
SeonJeongk Feb 6, 2025
b23c0cc
[TNT-115] fix: 스트링 리소스명 겹치지 않도록 수정
SeonJeongk Feb 6, 2025
510b623
[TNT-115] fix: LaunchEffect 의존성 변경
SeonJeongk Feb 6, 2025
b153325
[TNT-115] refactor: 구성 변경 시에도 트레이니 연결 상태가 유지되도록 개선
hoyahozz Feb 6, 2025
8f4d833
[TNT-115] refactor: UserLocalDataSource 삭제 및 apiService 네이밍 수정
SeonJeongk Feb 6, 2025
91f4f14
[TNT-115] refactor: 트레이너 초대 코드 불러오기 화면 trainer:invite 모듈로 분리
SeonJeongk Feb 6, 2025
53f1096
[TNT-115] feat: 트레이니의 트레이너 연결 중단 Dialog 구현
SeonJeongk Feb 7, 2025
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
Expand Up @@ -140,7 +140,7 @@ fun TnTSingleButtonPopupDialog(
)
TnTTextButton(
size = ButtonSize.Medium,
type = ButtonType.Gray,
type = ButtonType.Primary,
text = buttonText,
onClick = onButtonClick,
modifier = Modifier
Expand Down Expand Up @@ -230,6 +230,7 @@ fun TnTIconSingleButtonPopupDialog(
topIcon: Painter,
buttonText: String,
modifier: Modifier = Modifier,
type: ButtonType = ButtonType.Gray,
onButtonClick: () -> Unit,
onDismiss: () -> Unit,
) {
Expand Down Expand Up @@ -273,7 +274,7 @@ fun TnTIconSingleButtonPopupDialog(
)
TnTTextButton(
size = ButtonSize.Medium,
type = ButtonType.Gray,
type = type,
text = buttonText,
onClick = onButtonClick,
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package co.kr.tnt.ui.model
import androidx.annotation.DrawableRes
import co.kr.tnt.core.ui.R
import co.kr.tnt.domain.model.User
import co.kr.tnt.domain.model.UserType

sealed class DefaultUserProfile(
@DrawableRes val image: Int,
) {
data object Trainer : DefaultUserProfile(
image = R.drawable.img_default_profile_trainer,
)

data object Trainee : DefaultUserProfile(
image = R.drawable.img_default_profile_trainee,
)
Expand All @@ -21,5 +23,12 @@ sealed class DefaultUserProfile(
is User.Trainer -> Trainee
}
}

fun fromDomain(type: UserType): DefaultUserProfile {
return when (type) {
UserType.TRAINER -> Trainer
UserType.TRAINEE -> Trainee
}
}
}
}
1 change: 1 addition & 0 deletions core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<string name="ok">확인</string>

<string name="error_server_request_failed">서버 요청에 실패했어요</string>
<string name="entered_wrong_text">잘못된 수치를 입력했어요</string>

<string name="trainee">트레이니</string>
<string name="trainer">트레이너</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object NetworkModule {
private const val TIME_OUT_SECONDS: Long = 10
private const val CONNECT_TIMEOUT_SECONDS: Long = 10
private const val READ_WRITE_TIMEOUT_SECONDS: Long = 20

@Provides
@Singleton
Expand All @@ -43,8 +44,9 @@ internal object NetworkModule {
loggingInterceptor: HttpLoggingInterceptor,
authenticator: Authenticator,
): OkHttpClient = OkHttpClient.Builder()
.connectTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS)
.connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.readTimeout(READ_WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(READ_WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.addInterceptor(sessionInterceptor)
.addInterceptor(loggingInterceptor)
.authenticator(authenticator)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.kr.data.network.model

import kotlinx.serialization.Serializable

@Serializable
data class ConnectRequest(
val invitationCode: String,
val startDate: String,
val totalPtCount: Int,
val finishedPtCount: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package co.kr.data.network.model

import co.kr.tnt.domain.model.ConnectRequestResult
import kotlinx.serialization.Serializable

@Serializable
data class ConnectRequestResponse(
val trainerName: String,
val traineeName: String,
val trainerProfileImageUrl: String,
val traineeProfileImageUrl: String,
)

fun ConnectRequestResponse.toDomain(): ConnectRequestResult =
ConnectRequestResult(
trainerName = trainerName,
traineeName = traineeName,
trainerImage = trainerProfileImageUrl,
traineeImage = traineeProfileImageUrl,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package co.kr.data.network.model

import co.kr.tnt.domain.model.ConnectedResult
import kotlinx.serialization.Serializable

@Serializable
data class ConnectedTraineeResponse(
val trainerName: String,
val traineeName: String,
val trainerProfileImageUrl: String?,
val traineeProfileImageUrl: String?,
val traineeAge: Int,
val height: Double,
val weight: Double,
val ptGoal: String,
val cautionNote: String?,
)

fun ConnectedTraineeResponse.toDomain(): ConnectedResult =
ConnectedResult(
trainerName = trainerName,
traineeName = traineeName,
trainerImage = trainerProfileImageUrl,
traineeImage = traineeProfileImageUrl,
age = traineeAge,
height = height,
weight = weight,
ptGoal = ptGoal,
cautionNote = cautionNote,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package co.kr.data.network.model

import co.kr.tnt.domain.model.InviteCodeResult
import kotlinx.serialization.Serializable

@Serializable
data class InviteCodeResponse(
val invitationCode: String,
)

fun InviteCodeResponse.toDomain(): InviteCodeResult {
return InviteCodeResult(
invitationCode = invitationCode,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.kr.data.network.model

import kotlinx.serialization.Serializable

@Serializable
data class VerifyCodeResponse(
val isVerified: Boolean,
)
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
package co.kr.data.network.service

import co.kr.data.network.model.CheckSessionResponse
import co.kr.data.network.model.ConnectRequest
import co.kr.data.network.model.ConnectRequestResponse
import co.kr.data.network.model.ConnectedTraineeResponse
import co.kr.data.network.model.InviteCodeResponse
import co.kr.data.network.model.LoginRequest
import co.kr.data.network.model.LoginResponse
import co.kr.data.network.model.SignUpResponse
import co.kr.data.network.model.VerifyCodeResponse
import co.kr.data.network.util.WithoutSessionCheckPath.CHECK_SESSION_PATH
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query

interface ApiService {
@GET(CHECK_SESSION_PATH)
suspend fun getCheckSession(): CheckSessionResponse

// Login
@POST("/login")
suspend fun postLogin(
@Body request: LoginRequest,
): LoginResponse

// SignUp
@Multipart
@POST("/members/sign-up")
suspend fun postSignUp(
@Part profileImage: MultipartBody.Part?,
@Part("request") request: RequestBody,
): SignUpResponse

// Connect
@GET("/trainers/invitation-code")
suspend fun getInviteCode(): InviteCodeResponse

@PUT("/trainers/invitation-code/reissue")
suspend fun regenerateInviteCode(): InviteCodeResponse

@GET("/trainers/invitation-code/verify/{code}")
suspend fun verifyInviteCode(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
suspend fun verifyInviteCode(
suspend fun getVerifyInviteCode(

접두사로 HTTP 메소드 적어두면 한 눈에 알아볼 수 있을 것 같습니다~

@Path("code") code: String,
): VerifyCodeResponse

@POST("/trainees/connect-trainer")
suspend fun postConnectRequest(
@Body request: ConnectRequest,
): ConnectRequestResponse

@GET("/trainers/first-connected-trainee")
suspend fun getConnectedTraineeInfo(
@Query("trainerId") trainerId: String,
@Query("traineeId") traineeId: String,
): ConnectedTraineeResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package co.kr.data.network.source

import co.kr.data.network.model.ConnectRequest
import co.kr.data.network.model.ConnectRequestResponse
import co.kr.data.network.model.ConnectedTraineeResponse
import co.kr.data.network.model.InviteCodeResponse
import co.kr.data.network.model.VerifyCodeResponse
import co.kr.data.network.service.ApiService
import co.kr.data.network.util.networkHandler
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ConnectRemoteDataSource @Inject constructor(
private val apiService: ApiService,
) {
suspend fun getInviteCode(): InviteCodeResponse = networkHandler {
apiService.getInviteCode()
}

suspend fun regenerateInviteCode(): InviteCodeResponse = networkHandler {
apiService.regenerateInviteCode()
}

suspend fun verifyInviteCode(code: String): VerifyCodeResponse = networkHandler {
apiService.verifyInviteCode(code = code)
}

suspend fun connectRequest(connectRequest: ConnectRequest): ConnectRequestResponse =
networkHandler {
apiService.postConnectRequest(request = connectRequest)
}

suspend fun getConnectedTraineeInfo(
trainerId: String,
traineeId: String,
): ConnectedTraineeResponse = networkHandler {
apiService.getConnectedTraineeInfo(
trainerId = trainerId,
traineeId = traineeId,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package co.kr.data.repository

import co.kr.data.network.model.ConnectRequest
import co.kr.data.network.model.toDomain
import co.kr.data.network.source.ConnectRemoteDataSource
import co.kr.data.storage.source.UserLocalDataSource
import co.kr.tnt.domain.model.ConnectRequestResult
import co.kr.tnt.domain.model.ConnectedResult
import co.kr.tnt.domain.model.InviteCodeResult
import co.kr.tnt.domain.repository.ConnectRepository
import javax.inject.Inject

class ConnectRepositoryImpl @Inject constructor(
private val connectRemoteDataSource: ConnectRemoteDataSource,
private val userLocalDataSource: UserLocalDataSource,
) : ConnectRepository {
override suspend fun getInviteCode(): InviteCodeResult {
val response = connectRemoteDataSource.getInviteCode()

return response.toDomain()
}

override suspend fun regenerateInviteCode(): InviteCodeResult {
val response = connectRemoteDataSource.regenerateInviteCode()

return response.toDomain()
}

override suspend fun verifyInviteCode(code: String): Boolean {
val response = connectRemoteDataSource.verifyInviteCode(code = code)

return response.isVerified
}

override suspend fun connectRequest(
invitationCode: String,
startDate: String,
totalSession: Int,
completedSession: Int,
): ConnectRequestResult {
val response = connectRemoteDataSource.connectRequest(
connectRequest = ConnectRequest(
invitationCode = invitationCode,
startDate = startDate,
totalPtCount = totalSession,
finishedPtCount = completedSession,
),
)
return response.toDomain()
}

override suspend fun getConnectedTraineeInfo(): ConnectedResult {
// TODO trainerId, traineeId 저장하기 (FCM, 알림 화면)
val trainerId = userLocalDataSource.getTrainerId()
val traineeId = userLocalDataSource.getTraineeId()

val response = connectRemoteDataSource.getConnectedTraineeInfo(
trainerId = trainerId,
traineeId = traineeId,
)
return response.toDomain()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package co.kr.data.repository.di

import co.kr.data.repository.ConnectRepositoryImpl
import co.kr.data.repository.LoginRepositoryImpl
import co.kr.data.repository.SignUpRepositoryImpl
import co.kr.tnt.domain.repository.ConnectRepository
import co.kr.tnt.domain.repository.LoginRepository
import co.kr.tnt.domain.repository.SignUpRepository
import dagger.Binds
Expand All @@ -21,4 +23,9 @@ internal abstract class RepositoryModule {
abstract fun bindsSignUpRepository(
repository: SignUpRepositoryImpl,
): SignUpRepository

@Binds
abstract fun bindConnectRepository(
repository: ConnectRepositoryImpl,
): ConnectRepository
}
12 changes: 12 additions & 0 deletions data/storage/src/main/java/co/kr/data/storage/di/StorageModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Named
import javax.inject.Singleton

@Module
Expand All @@ -17,9 +18,20 @@ internal object StorageModule {
private const val SESSION_STORAGE_NAME = "SESSION_STORAGE"
private val Context.sessionDataStore by preferencesDataStore(name = SESSION_STORAGE_NAME)

private const val USER_STORAGE_NAME = "USER_STORAGE"
private val Context.userDataStore by preferencesDataStore(name = USER_STORAGE_NAME)

@Provides
@Singleton
@Named("Session")
fun provideSessionDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.sessionDataStore

@Provides
@Singleton
@Named("User")
fun provideUserLocalDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.userDataStore
}
Loading