diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 801d9b2e7..adbb455aa 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -72,7 +72,7 @@
android:exported="false"
android:screenOrientation="portrait" />
(R.layout.activity_notice_detail) {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
-// val noticeItem = intent.getSerializableExtra(NOTICE_DETAIL_KEY) as? NoticeModel
-// binding.noticeItem = noticeItem
-
- onBackButtonClick()
- handleBackPressed()
-// setupHyperlink(noticeItem?.noticeContent)
- }
-
- private fun onBackButtonClick() {
- binding.ivNoticeDetailBack.setOnClickListener {
- finish()
- }
- }
-
- private fun handleBackPressed() {
- onBackPressedDispatcher.addCallback(this) {
- finish()
- }
- }
-
- private fun setupHyperlink(noticeContent: String?) {
- binding.tvNoticeDetailContent.apply {
- movementMethod = LinkMovementMethod.getInstance()
- text = noticeContent?.let { content ->
- Html.fromHtml(
- content,
- Html.FROM_HTML_MODE_LEGACY,
- )
- }
- }
- }
-
- companion object {
- const val NOTICE_DETAIL_KEY = "NOTICE_DETAIL_KEY"
-
- fun getIntent(
- context: Context,
- noticeId: Long,
- ): Intent =
- Intent(context, NoticeDetailActivity::class.java).apply {
- putExtra(NOTICE_DETAIL_KEY, noticeId)
- }
- }
-}
diff --git a/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailActivity.kt b/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailActivity.kt
new file mode 100644
index 000000000..bf5851a03
--- /dev/null
+++ b/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailActivity.kt
@@ -0,0 +1,48 @@
+package com.into.websoso.ui.notificationDetail
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.addCallback
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import com.into.websoso.core.designsystem.theme.WebsosoTheme
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class NotificationDetailActivity : ComponentActivity() {
+ private val notificationDetailViewModel: NotificationDetailViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ handleBackPressed()
+
+ setContent {
+ WebsosoTheme {
+ NotificationDetailScreen(
+ viewModel = notificationDetailViewModel,
+ onBackButtonClick = { finish() },
+ )
+ }
+ }
+ }
+
+ private fun handleBackPressed() {
+ onBackPressedDispatcher.addCallback(this) {
+ finish()
+ }
+ }
+
+ companion object {
+ const val NOTIFICATION_DETAIL_KEY = "NOTIFICATION_DETAIL_KEY"
+
+ fun getIntent(
+ context: Context,
+ notificationId: Long,
+ ): Intent =
+ Intent(context, NotificationDetailActivity::class.java).apply {
+ putExtra(NOTIFICATION_DETAIL_KEY, notificationId)
+ }
+ }
+}
diff --git a/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailScreen.kt b/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailScreen.kt
new file mode 100644
index 000000000..30b47ce2c
--- /dev/null
+++ b/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailScreen.kt
@@ -0,0 +1,47 @@
+package com.into.websoso.ui.notificationDetail
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.into.websoso.core.designsystem.theme.WebsosoTheme
+import com.into.websoso.data.model.NotificationDetailEntity
+import com.into.websoso.ui.notice.component.NoticeAppBar
+import com.into.websoso.ui.notificationDetail.component.NotificationDetailContent
+import com.into.websoso.ui.notificationDetail.model.NotificationDetailUiState
+
+@Composable
+fun NotificationDetailScreen(
+ viewModel: NotificationDetailViewModel,
+ onBackButtonClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val uiState by viewModel.notificationDetailUiState.collectAsStateWithLifecycle()
+
+ Column(modifier = modifier.fillMaxSize()) {
+ NoticeAppBar(onBackButtonClick)
+ NotificationDetailContent(
+ uiState = uiState,
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun NotificationDetailScreenPreview() {
+ val uiState = NotificationDetailUiState(
+ noticeDetail = NotificationDetailEntity.DEFAULT,
+ )
+
+ WebsosoTheme {
+ Column(modifier = Modifier.fillMaxSize()) {
+ NoticeAppBar(onBackButtonClick = {})
+ NotificationDetailContent(
+ uiState,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailViewModel.kt b/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailViewModel.kt
new file mode 100644
index 000000000..e5a756413
--- /dev/null
+++ b/app/src/main/java/com/into/websoso/ui/notificationDetail/NotificationDetailViewModel.kt
@@ -0,0 +1,68 @@
+package com.into.websoso.ui.notificationDetail
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.into.websoso.data.repository.NoticeRepository
+import com.into.websoso.ui.notificationDetail.model.NotificationDetailUiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class NotificationDetailViewModel
+ @Inject
+ constructor(
+ private val noticeRepository: NoticeRepository,
+ savedStateHandle: SavedStateHandle,
+ ) : ViewModel() {
+ private val _notificationDetailUiState: MutableStateFlow =
+ MutableStateFlow(NotificationDetailUiState())
+ val notificationDetailUiState: StateFlow = _notificationDetailUiState.asStateFlow()
+
+ // TODO: 에러처리 회의 종료 후 반영 필요
+ private val _errorFlow = MutableSharedFlow()
+ val errorFlow = _errorFlow.asSharedFlow()
+
+ init {
+ val notificationId =
+ savedStateHandle.get(NotificationDetailActivity.NOTIFICATION_DETAIL_KEY) ?: DEFAULT_NOTIFICATION_ID
+ if (notificationId != DEFAULT_NOTIFICATION_ID) {
+ getNotificationDetail(notificationId)
+ } else {
+ handleInvalidNotificationId()
+ }
+ }
+
+ private fun getNotificationDetail(notificationId: Long) {
+ viewModelScope.launch {
+ runCatching {
+ noticeRepository.fetchNotificationDetail(notificationId)
+ }.onSuccess { noticeDetail ->
+ _notificationDetailUiState.update { uiState ->
+ uiState.copy(
+ noticeDetail = noticeDetail,
+ )
+ }
+ }.onFailure { throwable ->
+ _errorFlow.emit(throwable)
+ }
+ }
+ }
+
+ private fun handleInvalidNotificationId() {
+ viewModelScope.launch {
+ _errorFlow.emit(IllegalArgumentException("Invalid notification ID"))
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_NOTIFICATION_ID = -1L
+ }
+ }
diff --git a/app/src/main/java/com/into/websoso/ui/notificationDetail/component/HyperlinkText.kt b/app/src/main/java/com/into/websoso/ui/notificationDetail/component/HyperlinkText.kt
new file mode 100644
index 000000000..fd245d626
--- /dev/null
+++ b/app/src/main/java/com/into/websoso/ui/notificationDetail/component/HyperlinkText.kt
@@ -0,0 +1,88 @@
+package com.into.websoso.ui.notificationDetail.component
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.withLink
+import androidx.compose.ui.tooling.preview.Preview
+import com.into.websoso.core.designsystem.theme.Black
+import com.into.websoso.core.designsystem.theme.HyperlinkBlue
+import com.into.websoso.core.designsystem.theme.WebsosoTheme
+
+private const val URL_REGEX = "(https?://[\\w./?=&%#-]+|www\\.[\\w./?=&%#-]+)"
+
+@Composable
+fun HyperlinkText(
+ text: String,
+ style: TextStyle = WebsosoTheme.typography.body2,
+ textColor: Color = Black,
+ hyperlinkColor: Color = HyperlinkBlue,
+ hyperlinkDecoration: TextDecoration = TextDecoration.Underline,
+ modifier: Modifier = Modifier,
+) {
+ val uriHandler = LocalUriHandler.current
+ val urlRegex = Regex(URL_REGEX)
+
+ val annotatedString = buildAnnotatedString {
+ var lastIndex = 0
+ urlRegex.findAll(text).forEach { matchResult ->
+ var url = matchResult.value
+ val startIndex = matchResult.range.first
+ val endIndex = matchResult.range.last + 1
+
+ // `www.`로 시작하면 `https://` 추가
+ if (url.startsWith("www.")) {
+ url = "https://$url"
+ }
+
+ if (startIndex > lastIndex) {
+ append(text.substring(lastIndex, startIndex))
+ }
+
+ val linkUrl = LinkAnnotation.Url(
+ url,
+ TextLinkStyles(
+ SpanStyle(
+ color = hyperlinkColor,
+ textDecoration = hyperlinkDecoration,
+ ),
+ ),
+ ) {
+ uriHandler.openUri(url)
+ }
+
+ withLink(linkUrl) { append(matchResult.value) }
+ lastIndex = endIndex
+ }
+
+ if (lastIndex < text.length) {
+ append(text.substring(lastIndex))
+ }
+
+ addStyle(
+ style = SpanStyle(color = textColor),
+ start = 0,
+ end = text.length,
+ )
+ }
+
+ Text(text = annotatedString, modifier = modifier, style = style)
+}
+
+@Preview(showBackground = true)
+@Composable
+fun HyperlinkTextPreview() {
+ WebsosoTheme {
+ HyperlinkText(
+ text = "구글은 www.google.com \n네이버는 https://www.naver.com 입니다.\n웹소소는 websoso.kr 입니다.",
+ )
+ }
+}
diff --git a/app/src/main/java/com/into/websoso/ui/notificationDetail/component/NotificationDetailContent.kt b/app/src/main/java/com/into/websoso/ui/notificationDetail/component/NotificationDetailContent.kt
new file mode 100644
index 000000000..a7a0ed52b
--- /dev/null
+++ b/app/src/main/java/com/into/websoso/ui/notificationDetail/component/NotificationDetailContent.kt
@@ -0,0 +1,74 @@
+package com.into.websoso.ui.notificationDetail.component
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.into.websoso.core.designsystem.theme.Black
+import com.into.websoso.core.designsystem.theme.Gray200
+import com.into.websoso.core.designsystem.theme.Gray50
+import com.into.websoso.core.designsystem.theme.WebsosoTheme
+import com.into.websoso.data.model.NotificationDetailEntity
+import com.into.websoso.ui.notificationDetail.model.NotificationDetailUiState
+
+@Composable
+fun NotificationDetailContent(
+ uiState: NotificationDetailUiState,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxSize(),
+ ) {
+ Column(
+ modifier = modifier
+ .padding(20.dp),
+ ) {
+ Text(
+ text = uiState.noticeDetail.notificationTitle,
+ style = WebsosoTheme.typography.headline1,
+ color = Black,
+ textAlign = TextAlign.Start,
+ )
+ Spacer(modifier = Modifier.height(10.dp))
+ Text(
+ text = uiState.noticeDetail.notificationCreatedDate,
+ style = WebsosoTheme.typography.body5,
+ color = Gray200,
+ textAlign = TextAlign.Start,
+ )
+ }
+ HorizontalDivider(thickness = 1.dp, color = Gray50)
+ HyperlinkText(
+ uiState.noticeDetail.notificationDetail,
+ style =
+ WebsosoTheme.typography.body2,
+ textColor = Black,
+ modifier = Modifier
+ .padding(top = 24.dp, start = 20.dp, end = 20.dp)
+ .fillMaxSize(),
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun NotificationDetailBodyPreview() {
+ val uiState = NotificationDetailUiState(
+ noticeDetail = NotificationDetailEntity.DEFAULT,
+ )
+
+ WebsosoTheme {
+ NotificationDetailContent(
+ uiState,
+ )
+ }
+}
diff --git a/app/src/main/java/com/into/websoso/ui/notificationDetail/model/NotificationDetailUiState.kt b/app/src/main/java/com/into/websoso/ui/notificationDetail/model/NotificationDetailUiState.kt
new file mode 100644
index 000000000..9a158d52b
--- /dev/null
+++ b/app/src/main/java/com/into/websoso/ui/notificationDetail/model/NotificationDetailUiState.kt
@@ -0,0 +1,7 @@
+package com.into.websoso.ui.notificationDetail.model
+
+import com.into.websoso.data.model.NotificationDetailEntity
+
+data class NotificationDetailUiState(
+ val noticeDetail: NotificationDetailEntity = NotificationDetailEntity.DEFAULT,
+)
diff --git a/app/src/main/res/layout/activity_notice_detail.xml b/app/src/main/res/layout/activity_notice_detail.xml
deleted file mode 100644
index 0e8659e72..000000000
--- a/app/src/main/res/layout/activity_notice_detail.xml
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-