Skip to content

Commit

Permalink
Merge pull request #110 from YAPP-Github/feature/TNT-190
Browse files Browse the repository at this point in the history
[TNT-190] ํŠธ๋ ˆ์ด๋‹ˆ ์—ฐ๊ฒฐ ์š”์ฒญ ํŒ์—… ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • Loading branch information
SeonJeongk authored Feb 14, 2025
2 parents c5baa66 + 9b6a12e commit bd072b0
Show file tree
Hide file tree
Showing 31 changed files with 322 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.kr.tnt.ui.component

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -39,11 +40,16 @@ fun TnTCheckToggleDialog(
modifier: Modifier = Modifier,
onLeftButtonClick: () -> Unit,
onRightButtonClick: () -> Unit,
cancelable: Boolean = false,
onCheckClick: () -> Unit,
onDismiss: () -> Unit,
) {
Dialog(
onDismissRequest = { onDismiss() },
onDismissRequest = {
if (cancelable) {
onDismiss()
}
},
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
Card(
Expand Down Expand Up @@ -88,7 +94,9 @@ fun TnTCheckToggleDialog(
text = checkToggleText,
style = TnTTheme.typography.body2Medium,
color = TnTTheme.colors.neutralColors.Neutral500,
modifier = Modifier.padding(start = 4.dp),
modifier = Modifier
.padding(start = 4.dp)
.clickable(onClick = onCheckClick),
)
}
Row(
Expand Down
6 changes: 5 additions & 1 deletion core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<string name="ok">ํ™•์ธ</string>
<string name="move_to_setting">์„ค์ •์œผ๋กœ ์ด๋™</string>

<string name="error_server_request_failed">์„œ๋ฒ„ ์š”์ฒญ์— ์‹คํŒจํ–ˆ์–ด์š”</string>
<string name="entered_wrong_text">์ž˜๋ชป๋œ ์ˆ˜์น˜๋ฅผ ์ž…๋ ฅํ–ˆ์–ด์š”</string>
<string name="text_length_and_format_warning">%s์ž ๋ฏธ๋งŒ์˜ ํ•œ๊ธ€ ๋˜๋Š” ์˜๋ฌธ์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”</string>

<string name="trainee">ํŠธ๋ ˆ์ด๋‹ˆ</string>
<string name="trainer">ํŠธ๋ ˆ์ด๋„ˆ</string>
Expand All @@ -27,6 +27,7 @@
<string name="start">์‹œ์ž‘ํ•˜๊ธฐ</string>
<string name="skip">๊ฑด๋„ˆ๋›ฐ๊ธฐ</string>
<string name="cancel">์ทจ์†Œ</string>
<string name="next_time">๋‹ค์Œ์—</string>

<string name="name">์ด๋ฆ„</string>
<string name="age_label">๋‚˜์ด</string>
Expand All @@ -39,6 +40,9 @@
<string name="notification">์•Œ๋ฆผ</string>
<string name="no_recent_notifications">์ตœ๊ทผ ๋ฐ›์€ ์•Œ๋ฆผ์ด ์—†์–ด์š”</string>

<!-- Dialog -->
<string name="do_not_see_for_three_days">3์ผ ๋™์•ˆ ๋ณด์ง€ ์•Š๊ธฐ</string>

<!-- Meal -->
<string name="meal_breakfast">์•„์นจ</string>
<string name="meal_lunch">์ ์‹ฌ</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class CheckSessionResponse(
val memberType: MemberType,
val isConnected: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ 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.ConnectLocalDataSource
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 co.kr.tnt.domain.utils.DateFormatter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.time.LocalDateTime
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
internal class ConnectRepositoryImpl @Inject constructor(
private val connectRemoteDataSource: ConnectRemoteDataSource,
private val connectLocalDataSource: ConnectLocalDataSource,
private val dateFormatter: DateFormatter,
) : ConnectRepository {
override suspend fun getInviteCode(): InviteCodeResult {
val response = connectRemoteDataSource.getInviteCode()
Expand Down Expand Up @@ -59,4 +66,17 @@ internal class ConnectRepositoryImpl @Inject constructor(
)
return response.toDomain()
}

override suspend fun getHomeDialogHiddenDate(): Flow<LocalDateTime?> =
connectLocalDataSource.explicitDeniedConnectDate.map { dateString ->
dateString?.let {
runCatching { dateFormatter.parseDateTime(it) }
.getOrDefault(LocalDateTime.MIN)
}
}

override suspend fun updateHomeDialogHiddenDate(date: LocalDateTime) {
val formattedDate = dateFormatter.format(date)
connectLocalDataSource.updateExplicitDeniedConnectDate(formattedDate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import co.kr.data.network.model.LoginRequest
import co.kr.data.network.model.enum.toDomain
import co.kr.data.network.model.toDomain
import co.kr.data.network.source.LoginRemoteDataSource
import co.kr.data.storage.source.ConnectLocalDataSource
import co.kr.data.storage.source.SessionLocalDataSource
import co.kr.tnt.domain.model.AuthType
import co.kr.tnt.domain.model.LoginResult
Expand All @@ -16,9 +17,13 @@ import javax.inject.Singleton
internal class LoginRepositoryImpl @Inject constructor(
private val loginRemoteDataSource: LoginRemoteDataSource,
private val sessionLocalDataSource: SessionLocalDataSource,
private val connectLocalDataSource: ConnectLocalDataSource,
) : LoginRepository {
override suspend fun getUserType(): UserType =
loginRemoteDataSource.getCheckSession().memberType.toDomain()
override suspend fun getUserType(): UserType {
val response = loginRemoteDataSource.getCheckSession()

return response.memberType.toDomain()
}

override suspend fun login(
authType: AuthType,
Expand All @@ -41,10 +46,12 @@ internal class LoginRepositoryImpl @Inject constructor(
override suspend fun logout() {
loginRemoteDataSource.postLogout()
sessionLocalDataSource.removeSessionId()
connectLocalDataSource.clearExplicitDeniedConnectDate()
}

override suspend fun withdraw() {
loginRemoteDataSource.postWithdraw()
sessionLocalDataSource.removeSessionId()
connectLocalDataSource.clearExplicitDeniedConnectDate()
}
}
10 changes: 10 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 @@ -21,6 +21,9 @@ internal object StorageModule {
const val SETTING_STORAGE_NAME = "SETTING_STORAGE"
private val Context.settingDataStore by preferencesDataStore(name = SETTING_STORAGE_NAME)

const val CONNECT_STORAGE_NAME = "HOME_STORAGE"
private val Context.connectDataStore by preferencesDataStore(name = CONNECT_STORAGE_NAME)

@Provides
@Singleton
@Named(SESSION_STORAGE_NAME)
Expand All @@ -34,4 +37,11 @@ internal object StorageModule {
fun provideSettingDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.settingDataStore

@Provides
@Singleton
@Named(CONNECT_STORAGE_NAME)
fun provideConnectDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = context.connectDataStore
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package co.kr.data.storage.source

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import co.kr.data.storage.di.StorageModule.CONNECT_STORAGE_NAME
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton

@Singleton
class ConnectLocalDataSource @Inject constructor(
@Named(CONNECT_STORAGE_NAME) private val connectPreferences: DataStore<Preferences>,
) {
val explicitDeniedConnectDate: Flow<String?> = connectPreferences.data.map { preferences ->
preferences[EXPLICIT_DENIED_CONNECT_DATE] ?: ""
}

suspend fun updateExplicitDeniedConnectDate(startDate: String) {
connectPreferences.edit { preferences ->
preferences[EXPLICIT_DENIED_CONNECT_DATE] = startDate
}
}

suspend fun clearExplicitDeniedConnectDate() {
connectPreferences.edit { preferences ->
preferences.remove(EXPLICIT_DENIED_CONNECT_DATE)
}
}

companion object {
private val EXPLICIT_DENIED_CONNECT_DATE = stringPreferencesKey("EXPLICIT_DENIED_CONNECT_DATE")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package co.kr.tnt.domain.repository
import co.kr.tnt.domain.model.ConnectRequestResult
import co.kr.tnt.domain.model.ConnectedResult
import co.kr.tnt.domain.model.InviteCodeResult
import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime

interface ConnectRepository {
suspend fun getInviteCode(): InviteCodeResult
Expand All @@ -22,4 +24,8 @@ interface ConnectRepository {
trainerId: String,
traineeId: String,
): ConnectedResult

suspend fun getHomeDialogHiddenDate(): Flow<LocalDateTime?>

suspend fun updateHomeDialogHiddenDate(date: LocalDateTime)
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@ internal fun PTSessionFormPage(
* 1. ๋ชจ๋“  ํ•ญ๋ชฉ(์™„๋ฃŒ๋œ ํšŒ์ฐจ, ์ด ๋“ฑ๋ก ํšŒ์ฐจ, ์‹œ์ž‘์ผ)์ด ์ž…๋ ฅ๋˜์–ด์•ผ ํ•œ๋‹ค
* 2. ์™„๋ฃŒ๋œ ํšŒ์ฐจ๊ฐ€ ์ด ๋“ฑ๋ก ํšŒ์ฐจ๋ณด๋‹ค ํฌ์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค
*/
val isFormValid = completedSessionCount.isNotEmpty() &&
totalSessionCount.isNotEmpty() && sessionStartDate != null &&
(completedSessionCount.toIntOrNull() ?: 0) < (totalSessionCount.toIntOrNull() ?: 0)
val isFormValid = sessionStartDate != null &&
isInvalidInput(completedSessionCount, allowZero = true).not() &&
isInvalidInput(totalSessionCount, allowZero = false).not() &&
isCompletedSessionInvalid(completedSessionCount, totalSessionCount).not()

val showWarning = completedSessionCount.isNotEmpty() && totalSessionCount.isNotEmpty() &&
(completedSessionCount.toIntOrNull() ?: 0) >= (totalSessionCount.toIntOrNull() ?: 0)
val showTotalSessionWarning = isInvalidInput(totalSessionCount, allowZero = false)
val showCompletedSessionWarning = isInvalidInput(completedSessionCount, allowZero = true) ||
isCompletedSessionInvalid(completedSessionCount, totalSessionCount)

Scaffold(
topBar = {
Expand Down Expand Up @@ -137,19 +139,21 @@ internal fun PTSessionFormPage(
isSingleLine = true,
isRequired = true,
keyboardType = KeyboardType.Number,
showWarning = showWarning,
showWarning = showCompletedSessionWarning || showTotalSessionWarning,
trailingComponent = {
UnitLabel(R.string.count_unit)
},
onValueChange = { newValue ->
if (validateInput(newValue)) {
onChangeCompletedSessionCount(newValue)
}
onChangeCompletedSessionCount(newValue)
},
modifier = Modifier.fillMaxWidth(),
)
Text(
text = if (showWarning) stringResource(uiResource.string.entered_wrong_text) else "",
text = if (showCompletedSessionWarning || showTotalSessionWarning) {
stringResource(uiResource.string.entered_wrong_text)
} else {
""
},
style = TnTTheme.typography.body2Medium,
color = TnTTheme.colors.redColors.Red500,
modifier = Modifier.padding(top = 4.dp),
Expand All @@ -171,19 +175,21 @@ internal fun PTSessionFormPage(
isSingleLine = true,
isRequired = true,
keyboardType = KeyboardType.Number,
showWarning = showWarning,
showWarning = showTotalSessionWarning || showCompletedSessionWarning,
trailingComponent = {
UnitLabel(R.string.count_unit)
},
onValueChange = { newValue ->
if (validateInput(newValue)) {
onChangeTotalSessionCount(newValue)
}
onChangeTotalSessionCount(newValue)
},
modifier = Modifier.fillMaxWidth(),
)
Text(
text = if (showWarning) stringResource(uiResource.string.entered_wrong_text) else "",
text = if (showTotalSessionWarning || showCompletedSessionWarning) {
stringResource(uiResource.string.entered_wrong_text)
} else {
""
},
style = TnTTheme.typography.body2Medium,
color = TnTTheme.colors.redColors.Red500,
modifier = Modifier.padding(top = 4.dp),
Expand All @@ -205,6 +211,27 @@ internal fun PTSessionFormPage(
}
}

/**
* ์ž…๋ ฅ์ด ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ true ๋ฆฌํ„ด
* @param input ์ž…๋ ฅ ๊ฐ’
* @param allowZero `true`์ผ ๊ฒฝ์šฐ 0 ์ž…๋ ฅ ๊ฐ€๋Šฅ (ํ˜„์žฌ ์™„๋ฃŒ๋œ ํšŒ์ฐจ)
* @return `true`๋ฉด ๊ฒฝ๊ณ  ํ•„์š”, `false`๋ฉด ์ •์ƒ ์ž…๋ ฅ
*/
private fun isInvalidInput(input: String, allowZero: Boolean = false): Boolean {
val num = input.toIntOrNull() ?: return false

return if (allowZero) {
(num !in 0..MAX_COUNT) || (input.length > 1 && input.startsWith("0"))
} else {
(num !in 1..MAX_COUNT) || (input.length > 1 && input.startsWith("0"))
}
}

private fun isCompletedSessionInvalid(completedSession: String, totalSession: String): Boolean {
if (completedSession.isEmpty() || totalSession.isEmpty()) return false
return (completedSession.toIntOrNull() ?: 0) >= (totalSession.toIntOrNull() ?: 0)
}

@Composable
private fun DatePicker(
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -265,14 +292,6 @@ private fun UnitLabel(stringResId: Int) {
)
}

/**
* ์œ ํšจํ•œ ์ž…๋ ฅ ๊ฐ’์ธ์ง€ ๊ฒ€์‚ฌ
* ํ˜•์‹: 99 ์ดํ•˜์˜ ์ •์ˆ˜
*/
private fun validateInput(input: String): Boolean {
return input.isEmpty() || (input.toIntOrNull() != null && !input.startsWith("0") && input.toInt() <= MAX_COUNT)
}

@Preview
@Composable
private fun PTSessionFormPagePreview() {
Expand Down
Loading

0 comments on commit bd072b0

Please sign in to comment.