-
Notifications
You must be signed in to change notification settings - Fork 0
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-189] 트레이니 알림 화면 UI 구현 #60
Changes from 10 commits
17bb268
1716793
f0b90f4
92fd0e4
dbccdb7
f5697e7
b929fbb
70f7c2a
d5e4b38
dda4a3d
f759288
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package co.kr.tnt.designsystem.component.notification | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import co.kr.tnt.designsystem.component.notification.model.NotificationIcon | ||
import co.kr.tnt.designsystem.theme.TnTTheme | ||
|
||
@Composable | ||
fun TnTNotification( | ||
type: NotificationIcon, | ||
title: String, | ||
contents: String, | ||
time: String, | ||
isChecked: Boolean, | ||
) { | ||
val backgroundColor = if (isChecked) { | ||
TnTTheme.colors.commonColors.Common0 | ||
} else { | ||
TnTTheme.colors.neutralColors.Neutral100 | ||
} | ||
|
||
Row( | ||
horizontalArrangement = Arrangement.spacedBy(16.dp), | ||
verticalAlignment = Alignment.CenterVertically, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.background(backgroundColor) | ||
.padding(20.dp), | ||
) { | ||
Image( | ||
painter = painterResource(type.icon), | ||
contentDescription = null, | ||
) | ||
Column( | ||
modifier = Modifier.fillMaxWidth(), | ||
) { | ||
Row( | ||
horizontalArrangement = Arrangement.SpaceBetween, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(bottom = 6.dp), | ||
) { | ||
Text( | ||
text = title, | ||
color = TnTTheme.colors.neutralColors.Neutral400, | ||
style = TnTTheme.typography.label1Bold, | ||
) | ||
Text( | ||
text = time, | ||
color = TnTTheme.colors.neutralColors.Neutral400, | ||
style = TnTTheme.typography.label1Medium, | ||
) | ||
} | ||
Text( | ||
text = contents, | ||
color = TnTTheme.colors.neutralColors.Neutral900, | ||
style = TnTTheme.typography.body2Medium, | ||
modifier = Modifier.fillMaxWidth(), | ||
) | ||
} | ||
} | ||
} | ||
|
||
@Preview | ||
@Composable | ||
private fun TnTNotificationPreview() { | ||
TnTTheme { | ||
TnTNotification( | ||
type = NotificationIcon.LINK, | ||
title = "알림 문구", | ||
contents = "알림 상세 문구", | ||
time = "2분전", | ||
isChecked = false, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package co.kr.tnt.designsystem.component.notification.model | ||
|
||
import co.kr.tnt.core.designsystem.R | ||
|
||
enum class NotificationIcon(val icon: Int) { | ||
LINK(R.drawable.ic_link_notif), | ||
SCHEDULE(R.drawable.ic_schedule_notif), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="32dp" | ||
android:height="32dp" | ||
android:viewportWidth="32" | ||
android:viewportHeight="32"> | ||
<path | ||
android:pathData="M8,0L24,0A8,8 0,0 1,32 8L32,24A8,8 0,0 1,24 32L8,32A8,8 0,0 1,0 24L0,8A8,8 0,0 1,8 0z" | ||
android:fillColor="#D478FF"/> | ||
<path | ||
android:pathData="M15.396,17.187m-6.928,-4a8,8 48.563,1 1,13.856 8a8,8 48.563,1 1,-13.856 -8" | ||
android:fillColor="#FEFBFF"/> | ||
<path | ||
android:pathData="M18.597,8.643C18.873,8.165 19.484,8.001 19.963,8.277L21.262,9.027C21.74,9.303 21.904,9.915 21.628,10.393L20.128,12.991L17.097,11.241L18.597,8.643Z" | ||
android:fillColor="#FEFBFF"/> | ||
<path | ||
android:pathData="M19.664,10.19L19.914,9.757C20.466,8.8 21.689,8.473 22.646,9.025L23.079,9.275" | ||
android:strokeLineJoin="round" | ||
android:strokeWidth="0.5" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#FEFBFF" | ||
android:strokeLineCap="round"/> | ||
<path | ||
android:pathData="M26.553,7.866C26.698,7.714 26.949,7.859 26.89,8.061L26.513,9.347C26.475,9.477 26.574,9.606 26.709,9.603L28.049,9.571C28.259,9.567 28.334,9.846 28.15,9.947L26.974,10.59C26.855,10.655 26.833,10.816 26.931,10.91L27.902,11.835C28.053,11.98 27.908,12.231 27.707,12.172L26.421,11.794C26.291,11.756 26.161,11.856 26.164,11.991L26.196,13.331C26.201,13.541 25.921,13.616 25.821,13.432L25.178,12.255C25.113,12.136 24.951,12.115 24.858,12.213L23.933,13.183C23.788,13.335 23.537,13.19 23.596,12.989L23.973,11.703C24.011,11.573 23.912,11.443 23.777,11.446L22.437,11.478C22.227,11.483 22.152,11.203 22.336,11.102L23.512,10.46C23.631,10.395 23.653,10.233 23.555,10.139L22.584,9.214C22.433,9.069 22.578,8.819 22.779,8.878L24.065,9.255C24.195,9.293 24.325,9.194 24.321,9.058L24.29,7.718C24.285,7.509 24.565,7.434 24.665,7.618L25.308,8.794C25.373,8.913 25.535,8.934 25.628,8.836L26.553,7.866Z" | ||
android:fillColor="#F2D6FF"/> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="32dp" | ||
android:height="32dp" | ||
android:viewportWidth="32" | ||
android:viewportHeight="32"> | ||
<path | ||
android:pathData="M0,8C0,3.582 3.582,0 8,0H24C28.418,0 32,3.582 32,8V24C32,28.418 28.418,32 24,32H8C3.582,32 0,28.418 0,24V8Z" | ||
android:fillColor="#FFA938"/> | ||
<path | ||
android:pathData="M10.578,8.784L21.423,8.784A2.946,2.946 0,0 1,24.368 11.729L24.368,21.477A2.946,2.946 0,0 1,21.423 24.423L10.578,24.423A2.946,2.946 0,0 1,7.633 21.477L7.633,11.729A2.946,2.946 0,0 1,10.578 8.784z" | ||
android:fillColor="#FAFAFA"/> | ||
<path | ||
android:pathData="M7.633,11.729C7.633,10.102 8.952,8.784 10.578,8.784H21.423C23.049,8.784 24.368,10.102 24.368,11.729V14.315H7.633V11.729Z" | ||
android:fillColor="#FF9200"/> | ||
<path | ||
android:pathData="M18.802,16.574V21.216H17.839V17.492H17.812L16.747,18.16V17.304L17.896,16.574H18.802Z" | ||
android:fillColor="#FF9200"/> | ||
<path | ||
android:pathData="M14.425,21.316C13.262,21.313 12.557,20.425 12.56,18.899C12.562,17.378 13.264,16.51 14.425,16.51C15.581,16.51 16.291,17.383 16.288,18.899C16.286,20.43 15.586,21.316 14.425,21.316ZM13.543,18.899C13.541,19.974 13.886,20.513 14.425,20.513C14.962,20.513 15.307,19.974 15.305,18.899C15.305,17.839 14.96,17.304 14.425,17.304C13.888,17.304 13.546,17.839 13.543,18.899Z" | ||
android:fillColor="#FF9200"/> | ||
<path | ||
android:pathData="M11.241,7.578L11.241,7.578A0.359,0.359 0,0 1,11.6 7.936L11.6,9.54A0.359,0.359 0,0 1,11.241 9.899L11.241,9.899A0.359,0.359 0,0 1,10.883 9.54L10.883,7.936A0.359,0.359 0,0 1,11.241 7.578z" | ||
android:fillColor="#FAFAFA"/> | ||
<path | ||
android:pathData="M14.414,7.578L14.414,7.578A0.359,0.359 0,0 1,14.772 7.936L14.772,9.54A0.359,0.359 0,0 1,14.414 9.899L14.414,9.899A0.359,0.359 0,0 1,14.055 9.54L14.055,7.936A0.359,0.359 0,0 1,14.414 7.578z" | ||
android:fillColor="#FAFAFA"/> | ||
<path | ||
android:pathData="M17.586,7.578L17.586,7.578A0.359,0.359 0,0 1,17.944 7.936L17.944,9.54A0.359,0.359 0,0 1,17.586 9.899L17.586,9.899A0.359,0.359 0,0 1,17.227 9.54L17.227,7.936A0.359,0.359 0,0 1,17.586 7.578z" | ||
android:fillColor="#FAFAFA"/> | ||
<path | ||
android:pathData="M20.757,7.578L20.757,7.578A0.359,0.359 0,0 1,21.115 7.936L21.115,9.54A0.359,0.359 0,0 1,20.757 9.899L20.757,9.899A0.359,0.359 0,0 1,20.398 9.54L20.398,7.936A0.359,0.359 0,0 1,20.757 7.578z" | ||
android:fillColor="#FAFAFA"/> | ||
</vector> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package co.kr.tnt.ui.model | ||
|
||
import co.kr.tnt.designsystem.component.notification.model.NotificationIcon | ||
import co.kr.tnt.domain.model.NotificationInfo | ||
import co.kr.tnt.domain.model.NotificationType | ||
import co.kr.tnt.ui.util.NotificationTimeUtil | ||
|
||
data class NotificationState( | ||
val type: NotificationIcon, | ||
val title: String, | ||
val contents: String, | ||
val time: String, | ||
val isChecked: Boolean = true, | ||
) { | ||
companion object { | ||
fun fromDomain(domain: NotificationInfo): NotificationState { | ||
return NotificationState( | ||
type = when (domain.type) { | ||
NotificationType.LINK -> NotificationIcon.LINK | ||
NotificationType.SCHEDULE -> NotificationIcon.SCHEDULE | ||
}, | ||
title = domain.title, | ||
contents = domain.contents, | ||
time = NotificationTimeUtil.formatTime(domain.time), | ||
isChecked = domain.isChecked, | ||
) | ||
} | ||
} | ||
} | ||
|
||
fun List<NotificationInfo>.toUiStateList(): List<NotificationState> { | ||
return this.map { NotificationState.fromDomain(it) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package co.kr.tnt.ui.util | ||
|
||
import java.time.LocalDateTime | ||
import java.time.format.DateTimeFormatter | ||
import java.time.temporal.ChronoUnit | ||
import java.util.Locale | ||
|
||
object NotificationTimeUtil { | ||
fun formatTime(notificationTime: String): String { | ||
val now = LocalDateTime.now() | ||
val time = LocalDateTime.parse(notificationTime, DateTimeFormatter.ISO_DATE_TIME) | ||
|
||
val minutesDiff = ChronoUnit.MINUTES.between(time, now) | ||
val hoursDiff = ChronoUnit.HOURS.between(time, now) | ||
|
||
return when { | ||
minutesDiff < 1 -> "방금" | ||
minutesDiff < 60 -> "${minutesDiff}분 전" | ||
hoursDiff < 24 -> "${hoursDiff}시간 전" | ||
else -> { | ||
val dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd", Locale.getDefault()) | ||
time.format(dateFormatter) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package co.kr.tnt.domain.model | ||
|
||
data class NotificationInfo( | ||
val type: NotificationType, | ||
val title: String, | ||
val contents: String, | ||
val time: String, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요기서 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 엇 요걸 놓쳤네요 수정해두겠습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 시간까지 다뤄야 하기 때문에 |
||
val isChecked: Boolean, | ||
) | ||
|
||
// TODO API 나오면 수정 필요 | ||
enum class NotificationType { | ||
LINK, | ||
SCHEDULE, | ||
; | ||
|
||
companion object { | ||
fun from(type: String): NotificationType { | ||
return when (type) { | ||
"LINK" -> LINK | ||
"SCHEDULE" -> SCHEDULE | ||
else -> throw IllegalArgumentException("지원하지 않는 $type 입니다.") | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import co.kr.tnt.setNamespace | ||
|
||
plugins { | ||
id("tnt.android.feature") | ||
} | ||
|
||
android { | ||
setNamespace("feature.trainee.notification") | ||
} | ||
|
||
dependencies { | ||
implementation(libs.kotlinx.immutable) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
||
</manifest> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package co.kr.tnt.trainee.notification | ||
|
||
import co.kr.tnt.ui.base.UiEvent | ||
import co.kr.tnt.ui.base.UiSideEffect | ||
import co.kr.tnt.ui.base.UiState | ||
import co.kr.tnt.ui.model.NotificationState | ||
|
||
internal class TraineeNotificationContract { | ||
data class TraineeNotificationUiState( | ||
val notifications: List<NotificationState> = emptyList(), | ||
) : UiState | ||
|
||
sealed interface TraineeNotificationUiEvent : UiEvent { | ||
data object OnBackClick : TraineeNotificationUiEvent | ||
} | ||
|
||
sealed interface TraineeNotificationEffect : UiSideEffect { | ||
data class ShowToast(val message: String) : TraineeNotificationEffect | ||
data object NavigateToPrevious : TraineeNotificationEffect | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 요거 생각해보니 알림 화면에서만 쓸 친구같아서, 싱글톤으로 선언하면 안될 것 같아요!
일반 클래스로 선언하고,
NotificationTimeFormatter
정도로 네이밍 지으면 좋을 것 같습니돠!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵! 수정해두겠습니다