Skip to content

Commit fef80da

Browse files
authored
Merge pull request #145 from YAPP-Github/feature/TNT-261
[TnT-261] ํŠธ๋ ˆ์ด๋‹ˆ ๋‚ด ์ •๋ณด ์ˆ˜์ • ํ™”๋ฉด ๊ตฌํ˜„
2 parents 0f23ea2 + 4583001 commit fef80da

File tree

40 files changed

+1209
-118
lines changed

40 files changed

+1209
-118
lines changed

โ€Žcore/navigation/src/main/java/co/kr/tnt/navigation/RouteModel.ktโ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ sealed interface Route {
9696
@Serializable
9797
data object TraineeMyPage : Route
9898

99+
@Serializable
100+
data object TraineeModifyMyInfo : Route
101+
99102
@Serializable
100103
data object TraineeNotification : Route
101104

โ€Žcore/ui/src/main/res/values/strings.xmlโ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
<string name="core_entered_wrong_text">์ž˜๋ชป๋œ ์ˆ˜์น˜๋ฅผ ์ž…๋ ฅํ–ˆ์–ด์š”</string>
2727
<string name="core_text_length_and_format_warning">%s์ž ๋ฏธ๋งŒ์˜ ํ•œ๊ธ€ ๋˜๋Š” ์˜๋ฌธ์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”</string>
28+
<string name="core_text_length_warning">%s์ž ๋ฏธ๋งŒ์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”</string>
2829

2930
<string name="core_trainee">ํŠธ๋ ˆ์ด๋‹ˆ</string>
3031
<string name="core_trainer">ํŠธ๋ ˆ์ด๋„ˆ</string>
@@ -67,6 +68,14 @@
6768
<!-- PT Session -->
6869
<string name="core_pt_session">%dํšŒ์ฐจ ์ˆ˜์—…</string>
6970

71+
<!-- PT Purpose -->
72+
<string name="core_loss_weight">์ฒด์ค‘ ๊ฐ๋Ÿ‰</string>
73+
<string name="core_strength_improvement">๊ทผ๋ ฅ ํ–ฅ์ƒ</string>
74+
<string name="core_health_care">๊ฑด๊ฐ• ๊ด€๋ฆฌ</string>
75+
<string name="core_flexibility">์œ ์—ฐ์„ฑ ํ–ฅ์ƒ</string>
76+
<string name="core_body_profile">๋ฐ”๋””ํ”„๋กœํ•„</string>
77+
<string name="core_posture_correction">์ž์„ธ ๊ต์ •</string>
78+
7079
<!-- MyPage -->
7180
<string name="core_modifying_personal_info">๊ฐœ์ธ์ •๋ณด ์ˆ˜์ •</string>
7281
<string name="core_app_push_notification">์•ฑ ํ‘ธ์‹œ ์•Œ๋ฆผ</string>
@@ -81,6 +90,10 @@
8190
<string name="core_logout_content">์–ธ์ œ๋“ ์ง€ ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•  ์ˆ˜ ์žˆ์–ด์š”!</string>
8291
<string name="core_logout_complete_title">๋กœ๊ทธ์•„์›ƒ์ด ์™„๋ฃŒ๋˜์—ˆ์–ด์š”</string>
8392

93+
<string name="core_delete_account_title">๊ณ„์ •์„ ํƒˆํ‡ดํ• ๊นŒ์š”?</string>
94+
<string name="core_delete_account_complete_title">๊ณ„์ • ํƒˆํ‡ด๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์–ด์š”</string>
95+
<string name="core_delete_account_complete_content">๋‹ค์Œ์— ๋” ํญ๋ฐœ์ ์ธ ์ผ€๋ฏธ๋กœ ๋‹ค์‹œ ๋งŒ๋‚˜์š”! ๐Ÿ’ฃ</string>
96+
8497
<string name="core_length_warning">%d์ž ๋ฏธ๋งŒ์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.</string>
8598
<string name="core_no_records_yet">์•„์ง ๋“ฑ๋ก๋œ ๊ธฐ๋ก์ด ์—†์–ด์š”</string>
8699
<string name="core_confirm_modify_info_exit">"์ •๋ณด ์ˆ˜์ •์„ ์ข…๋ฃŒํ• ๊นŒ์š”?"</string>

โ€Ždata/network/src/main/java/co/kr/data/network/model/UpdateUserInfoRequest.ktโ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ data class UpdateUserInfoRequest(
88
val removeImage: Boolean,
99
val memberType: MemberType,
1010
val name: String,
11-
val birthDay: String? = null,
11+
val birthday: String? = null,
1212
val height: Double? = null,
1313
val weight: Double? = null,
1414
val cautionNote: String? = null,

โ€Ždata/network/src/main/java/co/kr/data/network/source/TraineeRemoteDataSource.ktโ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,11 @@ class TraineeRemoteDataSource @Inject constructor(
3434
suspend fun getMealRecord(dietId: Long) = networkHandler {
3535
apiService.getMealRecord(dietId)
3636
}
37+
38+
suspend fun putUserInfo(
39+
profileImage: MultipartBody.Part?,
40+
request: RequestBody,
41+
) = networkHandler {
42+
apiService.putMyInfo(profileImage, request)
43+
}
3744
}

โ€Ždata/repository/src/main/java/co/kr/data/repository/TraineeRepositoryImpl.ktโ€Ž

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
package co.kr.data.repository
22

3+
import co.kr.data.network.model.UpdateUserInfoRequest
4+
import co.kr.data.network.model.enum.MemberType
35
import co.kr.data.network.model.toDomain
46
import co.kr.data.network.model.trainee.MealRecordRequest
57
import co.kr.data.network.model.trainee.toDomain
68
import co.kr.data.network.source.TraineeRemoteDataSource
79
import co.kr.data.network.source.UserRemoteDataSource
10+
import co.kr.tnt.domain.model.ProfileImageUpdatePolicy
811
import co.kr.tnt.domain.model.User
912
import co.kr.tnt.domain.model.trainee.TraineeDailyRecord
1013
import co.kr.tnt.domain.model.trainee.TraineeDailyRecordStatus
1114
import co.kr.tnt.domain.model.trainee.TraineeMealRecordDetail
1215
import co.kr.tnt.domain.repository.TraineeRepository
1316
import co.kr.tnt.domain.utils.DateFormatter
17+
import kotlinx.coroutines.flow.Flow
18+
import kotlinx.coroutines.flow.MutableStateFlow
19+
import kotlinx.coroutines.flow.onStart
1420
import kotlinx.serialization.encodeToString
1521
import kotlinx.serialization.json.Json
1622
import okhttp3.MediaType.Companion.toMediaTypeOrNull
1723
import okhttp3.MultipartBody
18-
import okhttp3.RequestBody
1924
import okhttp3.RequestBody.Companion.asRequestBody
2025
import okhttp3.RequestBody.Companion.toRequestBody
2126
import java.io.File
@@ -30,10 +35,15 @@ internal class TraineeRepositoryImpl @Inject constructor(
3035
private val dateFormatter: DateFormatter,
3136
private val json: Json,
3237
) : TraineeRepository {
33-
override suspend fun getMyInfo(): User.Trainee {
34-
val user = userRemoteDataSource.getMyInfo().toDomain(dateFormatter)
35-
require(user is User.Trainee)
36-
return user
38+
private val cacheUserInfo = MutableStateFlow(User.Trainee.EMPTY)
39+
40+
override suspend fun getMyInfo(): Flow<User.Trainee> {
41+
return cacheUserInfo
42+
.onStart {
43+
if (cacheUserInfo.value == User.Trainee.EMPTY) {
44+
cacheUserInfo.value = fetchUserInfo()
45+
}
46+
}
3747
}
3848

3949
override suspend fun getWeeklyRecordedDate(
@@ -67,19 +77,71 @@ internal class TraineeRepositoryImpl @Inject constructor(
6777
dietType = mealType,
6878
memo = memo,
6979
)
70-
val requestBody = mealRecordRequest.toRequestBody()
80+
val requestBody = json
81+
.encodeToString(mealRecordRequest)
82+
.toRequestBody("application/json".toMediaTypeOrNull())
7183

7284
traineeRemoteDataSource.postMealRecord(
7385
dietImage = imagePart,
7486
request = requestBody,
7587
)
7688
}
7789

78-
private fun MealRecordRequest.toRequestBody(): RequestBody {
79-
val jsonString = json.encodeToString(this)
80-
return jsonString.toRequestBody("application/json".toMediaTypeOrNull())
81-
}
82-
8390
override suspend fun getMealRecord(dietId: Long): TraineeMealRecordDetail =
8491
traineeRemoteDataSource.getMealRecord(dietId).toDomain(dateFormatter)
92+
93+
override suspend fun updateUserInfo(
94+
profileImageUpdatePolicy: ProfileImageUpdatePolicy,
95+
userInfo: User.Trainee,
96+
) {
97+
val (profileImage, isRemoveProfileImage) = when (profileImageUpdatePolicy) {
98+
is ProfileImageUpdatePolicy.Change -> profileImageUpdatePolicy.newProfileImage to false
99+
ProfileImageUpdatePolicy.Keep -> null to false
100+
ProfileImageUpdatePolicy.Remove -> null to true
101+
}
102+
val imagePart = profileImage?.let {
103+
val requestFile = it.asRequestBody("image/*".toMediaTypeOrNull())
104+
MultipartBody.Part.createFormData("profileImage", it.name, requestFile)
105+
}
106+
val selectedDate = userInfo.birthday?.let { dateFormatter.format(it, "yyyy-MM-dd") }
107+
108+
val request = UpdateUserInfoRequest(
109+
removeImage = isRemoveProfileImage,
110+
memberType = MemberType.TRAINEE,
111+
name = userInfo.name,
112+
birthday = selectedDate,
113+
height = userInfo.height?.toDouble(),
114+
weight = userInfo.weight,
115+
cautionNote = userInfo.caution,
116+
ptGoals = userInfo.ptPurpose,
117+
)
118+
val requestBody = json
119+
.encodeToString(request)
120+
.toRequestBody("application/json".toMediaTypeOrNull())
121+
122+
runCatching {
123+
traineeRemoteDataSource.putUserInfo(
124+
profileImage = imagePart,
125+
request = requestBody,
126+
)
127+
}.onSuccess {
128+
refreshCachedUserInfo()
129+
}.onFailure { failure ->
130+
throw failure
131+
}
132+
}
133+
134+
private suspend fun fetchUserInfo(): User.Trainee {
135+
val user = userRemoteDataSource.getMyInfo().toDomain(dateFormatter)
136+
require(user is User.Trainee)
137+
return user
138+
}
139+
140+
override suspend fun refreshCachedUserInfo() {
141+
cacheUserInfo.value = fetchUserInfo()
142+
}
143+
144+
override suspend fun clearCachedUserInfo() {
145+
cacheUserInfo.value = User.Trainee.EMPTY
146+
}
85147
}

โ€Ždomain/src/main/java/co/kr/tnt/domain/Policy.ktโ€Ž

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ object UserProfilePolicy {
77
// TnT ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฆ„์˜ ์ตœ๋Œ€ ๊ธธ์ด๋Š” 15์ž์ด๋‹ค.
88
const val USER_NAME_MAX_LENGTH = 15
99

10+
// TnT ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค์˜ ์ตœ๋Œ€ ๊ธธ์ด๋Š” 3์ž์ด๋‹ค.
11+
const val USER_HEIGHT_MAX_LENGTH = 3
12+
13+
// TnT ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ชธ๋ฌด๊ฒŒ์˜ ์ตœ๋Œ€ ๊ธธ์ด๋Š” 5์ž์ด๋‹ค. (000.0)
14+
const val USER_WEIGHT_MAX_LENGTH = 5
15+
16+
// TnT ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์˜์‚ฌํ•ญ์˜ ์ตœ๋Œ€ ๊ธธ์ด๋Š” 100์ž์ด๋‹ค.
17+
const val USER_CAUTION_MAX_LENGTH = 100
18+
1019
// TnT ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฆ„์€ ํ•œ๊ธ€, ์˜์–ด, ๊ณต๋ฐฑ๋งŒ ํ—ˆ์šฉํ•œ๋‹ค.
1120
val USER_NAME_REGEX = Regex("^[a-zA-Zใ„ฑ-ใ…Žใ…-ใ…ฃ๊ฐ€-ํžฃ ]+\$")
21+
22+
// TnT ์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ชธ๋ฌด๊ฒŒ๋Š” ์†Œ์ˆ˜์  ์ดํ•˜ ํ•œ ์ž๋ฆฌ๊นŒ์ง€๋งŒ ํ—ˆ์šฉํ•œ๋‹ค. (000, 00, 00.0, 000.0)
23+
val USER_WEIGHT_REGEX = Regex("^(\\d{1,3}(\\.\\d)?)?\$")
1224
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package co.kr.tnt.domain.model
2+
3+
enum class PtPurpose {
4+
LOSS_WEIGHT,
5+
STRENGTH,
6+
HEALTH_CARE,
7+
FLEXIBILITY,
8+
BODY_PROFILE,
9+
POSTURE_CORRECTION,
10+
}

โ€Ždomain/src/main/java/co/kr/tnt/domain/repository/TraineeRepository.ktโ€Ž

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package co.kr.tnt.domain.repository
22

3+
import co.kr.tnt.domain.model.ProfileImageUpdatePolicy
34
import co.kr.tnt.domain.model.User
45
import co.kr.tnt.domain.model.trainee.TraineeDailyRecord
56
import co.kr.tnt.domain.model.trainee.TraineeDailyRecordStatus
67
import co.kr.tnt.domain.model.trainee.TraineeMealRecordDetail
8+
import kotlinx.coroutines.flow.Flow
79
import java.io.File
810
import java.time.LocalDate
911

1012
interface TraineeRepository {
11-
suspend fun getMyInfo(): User.Trainee
13+
suspend fun getMyInfo(): Flow<User.Trainee>
1214
suspend fun postMealRecord(
1315
mealImage: File?,
1416
date: String,
@@ -23,4 +25,10 @@ interface TraineeRepository {
2325
suspend fun getMealRecord(
2426
dietId: Long,
2527
): TraineeMealRecordDetail
28+
suspend fun updateUserInfo(
29+
profileImageUpdatePolicy: ProfileImageUpdatePolicy,
30+
userInfo: User.Trainee,
31+
)
32+
suspend fun refreshCachedUserInfo()
33+
suspend fun clearCachedUserInfo()
2634
}

โ€Žfeature/trainee/connect/src/main/java/co/kr/tnt/trainee/connect/TraineeConnectViewModel.ktโ€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package co.kr.tnt.trainee.connect
33
import androidx.lifecycle.viewModelScope
44
import co.kr.tnt.core.ui.R.string.core_failed_to_server_request
55
import co.kr.tnt.domain.repository.ConnectRepository
6+
import co.kr.tnt.domain.repository.TraineeRepository
67
import co.kr.tnt.trainee.connect.TraineeConnectContract.TraineeConnectPage
78
import co.kr.tnt.trainee.connect.TraineeConnectContract.TraineeConnectSideEffect
89
import co.kr.tnt.trainee.connect.TraineeConnectContract.TraineeConnectUiEvent
@@ -20,6 +21,7 @@ import javax.inject.Inject
2021
@HiltViewModel
2122
internal class TraineeConnectViewModel @Inject constructor(
2223
private val connectRepository: ConnectRepository,
24+
private val traineeRepository: TraineeRepository,
2325
) :
2426
BaseViewModel<TraineeConnectUiState, TraineeConnectUiEvent, TraineeConnectSideEffect>(
2527
TraineeConnectUiState(),
@@ -100,6 +102,7 @@ internal class TraineeConnectViewModel @Inject constructor(
100102
traineeImage = result.traineeImage,
101103
)
102104
}
105+
refreshCachedUserInfo()
103106
navigateToNext()
104107
}.onFailure {
105108
sendEffect(
@@ -113,6 +116,12 @@ internal class TraineeConnectViewModel @Inject constructor(
113116
}
114117
}
115118

119+
private fun refreshCachedUserInfo() {
120+
viewModelScope.launch {
121+
traineeRepository.refreshCachedUserInfo()
122+
}
123+
}
124+
116125
private fun navigateToBack() {
117126
if (currentState.page == TraineeConnectPage.firstPage) {
118127
handleDialogState()

โ€Žfeature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeScreen.ktโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import coil.compose.rememberAsyncImagePainter
7373
import coil.request.ImageRequest
7474
import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState
7575
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
76+
import kotlinx.coroutines.flow.collectLatest
7677
import kotlinx.coroutines.launch
7778
import java.time.DayOfWeek
7879
import java.time.LocalDate
@@ -145,7 +146,7 @@ internal fun TraineeHomeRoute(
145146
}
146147

147148
LaunchedEffect(viewModel.effect) {
148-
viewModel.effect.collect { effect ->
149+
viewModel.effect.collectLatest { effect ->
149150
when (effect) {
150151
TraineeHomeEffect.NavigateToExerciseRecord -> {
151152
showBottomSheet = false

0 commit comments

Comments
ย (0)