diff --git a/app/core/navigation/src/commonMain/kotlin/io/github/taetae98coding/diary/core/navigation/tag/TagDestination.kt b/app/core/navigation/src/commonMain/kotlin/io/github/taetae98coding/diary/core/navigation/tag/TagDestination.kt
new file mode 100644
index 00000000..4f7be878
--- /dev/null
+++ b/app/core/navigation/src/commonMain/kotlin/io/github/taetae98coding/diary/core/navigation/tag/TagDestination.kt
@@ -0,0 +1,6 @@
+package io.github.taetae98coding.diary.core.navigation.tag
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+public data object TagDestination
diff --git a/app/core/resources/src/commonMain/composeResources/values-ko/strings.xml b/app/core/resources/src/commonMain/composeResources/values-ko/strings.xml
index 67d216a0..ed7c0ec6 100644
--- a/app/core/resources/src/commonMain/composeResources/values-ko/strings.xml
+++ b/app/core/resources/src/commonMain/composeResources/values-ko/strings.xml
@@ -14,6 +14,7 @@
제목
설명
날짜
+ 태그
%1$d년 %2$d월
%1$d월 %2$d일
diff --git a/app/core/resources/src/commonMain/composeResources/values/strings.xml b/app/core/resources/src/commonMain/composeResources/values/strings.xml
index 837fae89..dd88a2eb 100644
--- a/app/core/resources/src/commonMain/composeResources/values/strings.xml
+++ b/app/core/resources/src/commonMain/composeResources/values/strings.xml
@@ -14,6 +14,7 @@
Title
Description
Date
+ Tag
%1$d. %2$d.
%1$d. %2$d.
diff --git a/app/core/resources/src/commonMain/kotlin/io/github/taetae98coding/diary/core/resources/icon/TagIcon.kt b/app/core/resources/src/commonMain/kotlin/io/github/taetae98coding/diary/core/resources/icon/TagIcon.kt
new file mode 100644
index 00000000..c88c9a9c
--- /dev/null
+++ b/app/core/resources/src/commonMain/kotlin/io/github/taetae98coding/diary/core/resources/icon/TagIcon.kt
@@ -0,0 +1,18 @@
+package io.github.taetae98coding.diary.core.resources.icon
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Tag
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Composable
+public fun TagIcon(
+ modifier: Modifier = Modifier,
+) {
+ Icon(
+ imageVector = Icons.Rounded.Tag,
+ contentDescription = null,
+ modifier = modifier,
+ )
+}
diff --git a/app/feature/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/memo/detail/MemoDetailScreen.kt b/app/feature/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/memo/detail/MemoDetailScreen.kt
index 9ff3134a..7cc9bd54 100644
--- a/app/feature/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/memo/detail/MemoDetailScreen.kt
+++ b/app/feature/memo/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/memo/detail/MemoDetailScreen.kt
@@ -73,7 +73,7 @@ internal fun MemoDetailScreen(
}
}
- else -> Unit
+ is MemoDetailNavigationButton.None -> Unit
}
},
actions = {
@@ -111,7 +111,7 @@ internal fun MemoDetailScreen(
}
}
- else -> Unit
+ is MemoDetailFloatingButton.None -> Unit
}
},
) {
diff --git a/app/feature/tag/build.gradle.kts b/app/feature/tag/build.gradle.kts
new file mode 100644
index 00000000..322c149b
--- /dev/null
+++ b/app/feature/tag/build.gradle.kts
@@ -0,0 +1,16 @@
+plugins {
+ id("diary.app.feature")
+}
+
+kotlin {
+ sourceSets {
+ commonMain {
+ dependencies {
+ }
+ }
+ }
+}
+
+android {
+ namespace = "${Build.NAMESPACE}.feature.tag"
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/TagNavigation.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/TagNavigation.kt
new file mode 100644
index 00000000..fce4f59a
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/TagNavigation.kt
@@ -0,0 +1,14 @@
+package io.github.taetae98coding.diary.feature.tag
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import io.github.taetae98coding.diary.core.navigation.tag.TagDestination
+
+public fun NavGraphBuilder.tagNavigation(
+ navController: NavController,
+) {
+ composable {
+ TagRoute()
+ }
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/TagRoute.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/TagRoute.kt
new file mode 100644
index 00000000..2cbf32e6
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/TagRoute.kt
@@ -0,0 +1,93 @@
+package io.github.taetae98coding.diary.feature.tag
+
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.layout.AnimatedPane
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
+import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import io.github.taetae98coding.diary.feature.tag.detail.TagDetailFloatingButton
+import io.github.taetae98coding.diary.feature.tag.detail.TagDetailNavigationButton
+import io.github.taetae98coding.diary.feature.tag.detail.TagDetailScreen
+import io.github.taetae98coding.diary.feature.tag.list.TagListFloatingButton
+import io.github.taetae98coding.diary.feature.tag.list.TagListScreen
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+internal fun TagRoute(
+ modifier: Modifier = Modifier,
+) {
+ val navigator = rememberListDetailPaneScaffoldNavigator()
+
+ ListDetailPaneScaffold(
+ directive = navigator.scaffoldDirective,
+ value = navigator.scaffoldValue,
+ listPane = {
+ AnimatedPane {
+ val isFloatingVisible by remember {
+ derivedStateOf {
+ val isAdd = navigator.currentDestination?.content == null
+ val isListVisible = navigator.scaffoldValue.secondary == PaneAdaptedValue.Expanded
+ val isDetailVisible = navigator.scaffoldValue.primary == PaneAdaptedValue.Expanded
+
+ isAdd && isListVisible && !isDetailVisible
+ }
+ }
+
+ TagListScreen(
+ floatingButtonProvider = {
+ if (isFloatingVisible) {
+ TagListFloatingButton.Add(onAdd = { navigator.navigateTo(ThreePaneScaffoldRole.Primary) })
+ } else {
+ TagListFloatingButton.None
+ }
+ },
+ )
+ }
+ },
+ detailPane = {
+ val isNavigateUpVisible by remember {
+ derivedStateOf {
+ val isListVisible = navigator.scaffoldValue.secondary == PaneAdaptedValue.Expanded
+ val isDetailVisible = navigator.scaffoldValue.primary == PaneAdaptedValue.Expanded
+
+ !isListVisible && isDetailVisible
+ }
+ }
+
+ val isFloatingVisible by remember {
+ derivedStateOf {
+ val isAdd = navigator.currentDestination?.content == null
+ val isDetailVisible = navigator.scaffoldValue.primary == PaneAdaptedValue.Expanded
+
+ isAdd && isDetailVisible
+ }
+ }
+
+ AnimatedPane {
+ TagDetailScreen(
+ navigateButtonProvider = {
+ if (isNavigateUpVisible) {
+ TagDetailNavigationButton.NavigateUp(onNavigateUp = navigator::navigateBack)
+ } else {
+ TagDetailNavigationButton.None
+ }
+ },
+ floatingButtonProvider = {
+ if (isFloatingVisible) {
+ TagDetailFloatingButton.Add(onAdd = { })
+ } else {
+ TagDetailFloatingButton.None
+ }
+ },
+ )
+ }
+ },
+ modifier = modifier,
+ )
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailFloatingButton.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailFloatingButton.kt
new file mode 100644
index 00000000..2ecc5321
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailFloatingButton.kt
@@ -0,0 +1,6 @@
+package io.github.taetae98coding.diary.feature.tag.detail
+
+internal sealed class TagDetailFloatingButton {
+ data object None : TagDetailFloatingButton()
+ data class Add(val onAdd: () -> Unit) : TagDetailFloatingButton()
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailNavigationButton.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailNavigationButton.kt
new file mode 100644
index 00000000..9bf91a40
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailNavigationButton.kt
@@ -0,0 +1,6 @@
+package io.github.taetae98coding.diary.feature.tag.detail
+
+internal sealed class TagDetailNavigationButton {
+ data object None : TagDetailNavigationButton()
+ data class NavigateUp(val onNavigateUp: () -> Unit) : TagDetailNavigationButton()
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailScreen.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailScreen.kt
new file mode 100644
index 00000000..a35da054
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/detail/TagDetailScreen.kt
@@ -0,0 +1,51 @@
+package io.github.taetae98coding.diary.feature.tag.detail
+
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import io.github.taetae98coding.diary.core.resources.icon.AddIcon
+import io.github.taetae98coding.diary.core.resources.icon.NavigateUpIcon
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun TagDetailScreen(
+ navigateButtonProvider: () -> TagDetailNavigationButton,
+ floatingButtonProvider: () -> TagDetailFloatingButton,
+ modifier: Modifier = Modifier,
+) {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ TopAppBar(
+ title = {},
+ navigationIcon = {
+ when (val button = navigateButtonProvider()) {
+ is TagDetailNavigationButton.NavigateUp -> {
+ IconButton(onClick = button.onNavigateUp) {
+ NavigateUpIcon()
+ }
+ }
+
+ is TagDetailNavigationButton.None -> Unit
+ }
+ },
+ )
+ },
+ floatingActionButton = {
+ when(val button = floatingButtonProvider()) {
+ is TagDetailFloatingButton.Add -> {
+ FloatingActionButton(onClick = button.onAdd) {
+ AddIcon()
+ }
+ }
+ is TagDetailFloatingButton.None -> Unit
+ }
+ },
+ ) {
+
+ }
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/list/TagListFloatingButton.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/list/TagListFloatingButton.kt
new file mode 100644
index 00000000..413e5b5b
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/list/TagListFloatingButton.kt
@@ -0,0 +1,6 @@
+package io.github.taetae98coding.diary.feature.tag.list
+
+internal sealed class TagListFloatingButton {
+ data object None : TagListFloatingButton()
+ data class Add(val onAdd: () -> Unit) : TagListFloatingButton()
+}
diff --git a/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/list/TagListScreen.kt b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/list/TagListScreen.kt
new file mode 100644
index 00000000..a3c1271d
--- /dev/null
+++ b/app/feature/tag/src/commonMain/kotlin/io/github/taetae98coding/diary/feature/tag/list/TagListScreen.kt
@@ -0,0 +1,51 @@
+package io.github.taetae98coding.diary.feature.tag.list
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import io.github.taetae98coding.diary.core.resources.Res
+import io.github.taetae98coding.diary.core.resources.icon.AddIcon
+import io.github.taetae98coding.diary.core.resources.tag
+import org.jetbrains.compose.resources.stringResource
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun TagListScreen(
+ floatingButtonProvider: () -> TagListFloatingButton,
+ modifier: Modifier = Modifier,
+) {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ TopAppBar(
+ title = { Text(text = stringResource(Res.string.tag)) },
+ )
+ },
+ floatingActionButton = {
+ when(val button = floatingButtonProvider()) {
+ is TagListFloatingButton.Add -> {
+ FloatingActionButton(onClick = button.onAdd) {
+ AddIcon()
+ }
+ }
+ is TagListFloatingButton.None -> Unit
+ }
+ },
+ ) {
+ Box(
+ modifier = Modifier.fillMaxSize()
+ .padding(it),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(text = "태그 없어")
+ }
+ }
+}
diff --git a/app/platform/common/build.gradle.kts b/app/platform/common/build.gradle.kts
index 209b0741..8ca99abf 100644
--- a/app/platform/common/build.gradle.kts
+++ b/app/platform/common/build.gradle.kts
@@ -27,6 +27,7 @@ kotlin {
implementation(project(":app:core:holiday-service"))
implementation(project(":app:feature:memo"))
+ implementation(project(":app:feature:tag"))
implementation(project(":app:feature:calendar"))
implementation(project(":app:feature:more"))
implementation(project(":app:feature:account"))
diff --git a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/App.kt b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/App.kt
index efdf640b..e2cb32db 100644
--- a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/App.kt
+++ b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/App.kt
@@ -21,9 +21,11 @@ import io.github.taetae98coding.diary.core.design.system.theme.DiaryTheme
import io.github.taetae98coding.diary.core.navigation.calendar.CalendarDestination
import io.github.taetae98coding.diary.core.navigation.memo.MemoDestination
import io.github.taetae98coding.diary.core.navigation.more.MoreDestination
+import io.github.taetae98coding.diary.core.navigation.tag.TagDestination
import io.github.taetae98coding.diary.core.resources.icon.CalendarIcon
import io.github.taetae98coding.diary.core.resources.icon.MemoIcon
import io.github.taetae98coding.diary.core.resources.icon.MoreIcon
+import io.github.taetae98coding.diary.core.resources.icon.TagIcon
import org.jetbrains.compose.resources.stringResource
@Composable
@@ -45,6 +47,7 @@ private fun AppScaffold(
val isNavigationVisible by remember {
derivedStateOf {
val visibleDestination = listOf(
+ TagDestination::class,
MemoDestination::class,
CalendarDestination::class,
MoreDestination::class,
@@ -62,6 +65,7 @@ private fun AppScaffold(
navigationSuiteItems = {
listOf(
// AppNavigation.Memo,
+ AppNavigation.Tag,
AppNavigation.Calendar,
AppNavigation.More,
).forEach { navigation ->
@@ -99,5 +103,6 @@ private fun AppNavigationIcon(
AppNavigation.Memo -> MemoIcon(modifier = modifier)
AppNavigation.Calendar -> CalendarIcon(modifier = modifier)
AppNavigation.More -> MoreIcon(modifier = modifier)
+ AppNavigation.Tag -> TagIcon(modifier = modifier)
}
}
diff --git a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppNavHost.kt b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppNavHost.kt
index 5699e549..7e11c8e6 100644
--- a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppNavHost.kt
+++ b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/AppNavHost.kt
@@ -9,6 +9,7 @@ import io.github.taetae98coding.diary.feature.account.accountNavigation
import io.github.taetae98coding.diary.feature.calendar.calendarNavigation
import io.github.taetae98coding.diary.feature.memo.memoNavigation
import io.github.taetae98coding.diary.feature.more.moreNavigation
+import io.github.taetae98coding.diary.feature.tag.tagNavigation
@Composable
internal fun AppNavHost(
@@ -21,6 +22,7 @@ internal fun AppNavHost(
modifier = modifier,
) {
memoNavigation(navController = state.navController)
+ tagNavigation(navController = state.navController)
calendarNavigation(navController = state.navController)
moreNavigation(navController = state.navController)
accountNavigation(navController = state.navController)
diff --git a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/navigation/AppNavigation.kt b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/navigation/AppNavigation.kt
index a159440e..a41d5638 100644
--- a/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/navigation/AppNavigation.kt
+++ b/app/platform/common/src/commonMain/kotlin/io/github/taetae98coding/diary/app/navigation/AppNavigation.kt
@@ -3,10 +3,12 @@ package io.github.taetae98coding.diary.app.navigation
import io.github.taetae98coding.diary.core.navigation.calendar.CalendarDestination
import io.github.taetae98coding.diary.core.navigation.memo.MemoDestination
import io.github.taetae98coding.diary.core.navigation.more.MoreDestination
+import io.github.taetae98coding.diary.core.navigation.tag.TagDestination
import io.github.taetae98coding.diary.core.resources.Res
import io.github.taetae98coding.diary.core.resources.calendar
import io.github.taetae98coding.diary.core.resources.memo
import io.github.taetae98coding.diary.core.resources.more
+import io.github.taetae98coding.diary.core.resources.tag
import org.jetbrains.compose.resources.StringResource
internal enum class AppNavigation(
@@ -18,6 +20,11 @@ internal enum class AppNavigation(
route = MemoDestination,
),
+ Tag(
+ title = Res.string.tag,
+ route = TagDestination,
+ ),
+
Calendar(
title = Res.string.calendar,
route = CalendarDestination,
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0031627f..5e161526 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -77,6 +77,7 @@ include(":app:domain:fcm")
include(":app:domain:credential")
include(":app:feature:memo")
+include(":app:feature:tag")
include(":app:feature:calendar")
include(":app:feature:more")
include(":app:feature:account")