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-189] 트레이니 알림 화면 UI 구현 #60

Merged
merged 11 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 10 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
Expand Up @@ -54,7 +54,7 @@ fun TnTCheckToggle(
@Composable
fun TnTSwitch(
checked: Boolean,
onCheckedChange: () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val trackColor = if (checked) {
Expand All @@ -75,7 +75,7 @@ fun TnTSwitch(
.height(24.dp)
.clip(RoundedCornerShape(12.dp))
.background(trackColor)
.clickable(onClick = onCheckedChange)
.clickable(onClick = onClick)
.padding(horizontal = 2.dp),
) {
Box(
Expand Down Expand Up @@ -109,7 +109,7 @@ private fun TnTSwitchPreview() {

TnTSwitch(
checked = checked,
onCheckedChange = { checked = !checked },
onClick = { checked = !checked },
)
}
}
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),
}
25 changes: 25 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_link_notif.xml
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>
33 changes: 33 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_schedule_notif.xml
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
Expand Up @@ -42,6 +42,9 @@ sealed interface Route {
@Serializable
data object TraineeMyPage : Route

@Serializable
data object TraineeNotification : Route

@Serializable
data class WebView(val url: String) : Route
}
33 changes: 33 additions & 0 deletions core/ui/src/main/java/co/kr/tnt/ui/model/NotificationState.kt
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) }
}
26 changes: 26 additions & 0 deletions core/ui/src/main/java/co/kr/tnt/ui/util/NotificationTimeUtil.kt
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 {
Copy link
Member

Choose a reason for hiding this comment

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

음 요거 생각해보니 알림 화면에서만 쓸 친구같아서, 싱글톤으로 선언하면 안될 것 같아요!

일반 클래스로 선언하고, NotificationTimeFormatter 정도로 네이밍 지으면 좋을 것 같습니돠!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵! 수정해두겠습니다

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)
}
}
}
}
3 changes: 3 additions & 0 deletions core/ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<string name="height_unit">cm</string>
<string name="weight_unit">kg</string>

<string name="notification">알림</string>
<string name="no_recent_notifications">최근 받은 알림이 없어요</string>

<!-- Meal -->
<string name="meal_breakfast">아침</string>
<string name="meal_lunch">점심</string>
Expand Down
26 changes: 26 additions & 0 deletions domain/src/main/java/co/kr/tnt/domain/model/NotificationInfo.kt
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,
Copy link
Member

Choose a reason for hiding this comment

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

요기서 timeLocalDate 형태일 것 같습니당!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

엇 요걸 놓쳤네요 수정해두겠습니다!

Copy link
Contributor Author

@SeonJeongk SeonJeongk Feb 3, 2025

Choose a reason for hiding this comment

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

시간까지 다뤄야 하기 때문에 LocalDateTime으로 수정해뒀습니다!

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 입니다.")
}
}
}
}
1 change: 1 addition & 0 deletions feature/main/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
implementation(projects.feature.trainer.connect)
implementation(projects.feature.trainee.connect)
implementation(projects.feature.trainee.mypage)
implementation(projects.feature.trainee.notification)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
Expand Down
4 changes: 4 additions & 0 deletions feature/main/src/main/java/co/kr/tnt/main/ui/TnTNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import co.kr.tnt.roleselect.roleSelectionScreen
import co.kr.tnt.trainee.connect.navigation.navigateToTraineeConnect
import co.kr.tnt.trainee.connect.navigation.traineeConnectScreen
import co.kr.tnt.trainee.mypage.navigation.traineeMyPageScreen
import co.kr.tnt.trainee.notification.navigation.traineeNotification
import co.kr.tnt.trainee.signup.navigation.navigateToTraineeSignUp
import co.kr.tnt.trainee.signup.navigation.traineeSignUpScreen
import co.kr.tnt.trainer.connect.navigation.navigateToTrainerConnect
Expand Down Expand Up @@ -100,6 +101,9 @@ fun TnTNavHost(
navController.navigateToWebView(url = url)
},
)
traineeNotification(
navigateToPrevious = { navController.popBackStack() },
)
webViewScreen(
navigateToPrevious = { navController.popBackStack() },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private fun TraineeMyPageScreen(
)
TnTSwitch(
checked = state.isPushEnabled,
onCheckedChange = onPushNotificationToggle,
onClick = onPushNotificationToggle,
)
}
Column(
Expand Down
1 change: 1 addition & 0 deletions feature/trainee/notification/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
13 changes: 13 additions & 0 deletions feature/trainee/notification/build.gradle.kts
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)
}
4 changes: 4 additions & 0 deletions feature/trainee/notification/src/main/AndroidManifest.xml
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
}
}
Loading