diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..b65b2d92
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+max_line_length = 160
+
+[*.{kt,kts}]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+trim_trailing_whitespace = true
+insert_final_newline = true
+tab_width = 4
+ij_kotlin_allow_trailing_comma = true
\ No newline at end of file
diff --git "a/.github/ISSUE_TEMPLATE/issue-\354\203\235\354\204\261.md" "b/.github/ISSUE_TEMPLATE/issue-\354\203\235\354\204\261.md"
new file mode 100644
index 00000000..2f2be163
--- /dev/null
+++ "b/.github/ISSUE_TEMPLATE/issue-\354\203\235\354\204\261.md"
@@ -0,0 +1,14 @@
+---
+name: Issue 생성
+about: 여러 타입의 이슈를 생성합니다.
+title: "[FEATURE CHORE BUG HOTFIX BASE UI] 이슈 제목"
+labels: ''
+assignees: ''
+
+---
+
+## 내용
+작업 내용을 요약해주세요.
+## Todo
+- [ ] todo1
+- [ ] todo2
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..0301fbb6
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,29 @@
+## Key Changes
+
+Resolves: #(Isuue Number)
+
+## PR 유형
+어떤 변경 사항이 있나요?
+- [ ] 새로운 기능 추가
+- [ ] 버그 수정
+- [ ] CSS 등 사용자 UI 디자인 변경
+- [ ] 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
+- [ ] 코드 리팩토링
+- [ ] 주석 추가 및 수정
+- [ ] 문서 수정
+- [ ] 테스트 추가, 테스트 리팩토링
+- [ ] 빌드 부분 혹은 패키지 매니저 수정
+- [ ] 파일 혹은 폴더명 수정
+- [ ] 파일 혹은 폴더 삭제
+
+## To Reviewers
+- 리뷰어에게 중점적으로 확인받고 싶은 내용을 알려주세요.
+
+## PR Checklist
+PR이 다음 요구 사항을 충족하는지 확인하세요.
+- [ ] 커밋 메시지 컨벤션에 맞게 작성했습니다.
+- [ ] 정해진 코딩 컨벤션에 맞게 작성했습니다.
+- [ ] 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트)
+
+## Etc.
+
diff --git a/.github/workflows/ktlint.yml b/.github/workflows/ktlint.yml
new file mode 100644
index 00000000..51079c46
--- /dev/null
+++ b/.github/workflows/ktlint.yml
@@ -0,0 +1,37 @@
+name: Android CI
+
+on:
+ push:
+ branches: [ "develop" ]
+ pull_request:
+ branches: [ "develop" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: check out repository
+ uses: actions/checkout@v3
+
+ - name: set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
+ - name: check google client id
+ env:
+ GOOGLE_CLIENT_ID: ${{secrets.GOOGLE_CLIENT_ID}}
+ run: |
+ echo web_client_id=\""$GOOGLE_CLIENT_ID"\" >> local.properties
+
+ - name: Run ktlint
+ run: ./gradlew ktlintCheck
+
+ - name: Run unit tests
+ run: ./gradlew testDebugUnitTest
+
+ - name: Build with Gradle
+ run: ./gradlew assembleDebug
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index aa724b77..ae4db0fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,4 @@
/captures
.externalNativeBuild
.cxx
-local.properties
+app/google-services.json
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 0c0c3383..b0ecad44 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -5,6 +5,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 32522c1e..891e626f 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,5 +1,6 @@
+
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fdf8d994..0fc31131 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
index 42afabfd..2cb404ba 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1 +1,3 @@
-/build
\ No newline at end of file
+/build
+/local.properties
+google-services.json
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6147e31d..affa6e7f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,10 @@
plugins {
- alias(libs.plugins.androidApplication)
- alias(libs.plugins.jetbrainsKotlinAndroid)
+ alias(libs.plugins.com.android.application)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+ alias(libs.plugins.hilt)
+ alias(libs.plugins.googleServices)
+ alias(libs.plugins.kotlin.parcelize)
+ id("kotlin-kapt")
}
android {
@@ -11,8 +15,8 @@ android {
applicationId = "pokitmons.pokit"
minSdk = 24
targetSdk = 34
- versionCode = 1
- versionName = "1.0"
+ versionCode = 2
+ versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -50,7 +54,6 @@ android {
}
dependencies {
-
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
@@ -66,4 +69,34 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
-}
\ No newline at end of file
+
+ implementation(project(":core:ui"))
+ implementation(project(":core:feature"))
+ implementation(project(":data"))
+ implementation(project(":domain"))
+ implementation(project(":feature:addlink"))
+ implementation(project(":feature:addpokit"))
+ implementation(project(":feature:alarm"))
+ implementation(project(":feature:login"))
+ implementation(project(":feature:pokitdetail"))
+ implementation(project(":feature:search"))
+ implementation(project(":feature:settings"))
+ implementation(project(":feature:home"))
+
+ // hilt
+ implementation(libs.hilt)
+ kapt(libs.hilt.compiler)
+
+ // firebase
+ implementation(platform(libs.firebase.bom))
+ implementation(libs.firebase.auth.ktx)
+
+ // navigation
+ implementation(libs.androidx.navigation.compose)
+ implementation(libs.hilt.navigation.compose)
+
+ // orbit
+ implementation(libs.orbit.compose)
+ implementation(libs.orbit.core)
+ implementation(libs.orbit.viewmodel)
+}
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 00000000..3566f694
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,62 @@
+{
+ "project_info": {
+ "project_number": "217769178527",
+ "project_id": "pokit-f5c83",
+ "storage_bucket": "pokit-f5c83.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:217769178527:android:5d7efa4ceafe61c37af9aa",
+ "android_client_info": {
+ "package_name": "pokitmons.pokit"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "217769178527-jaa8p8nfmic1j1065qs5a7vfqt18qec0.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "pokitmons.pokit",
+ "certificate_hash": "9ded6dcd446add68f506001f3c1b457cc1c3be9e"
+ }
+ },
+ {
+ "client_id": "217769178527-mmbheg9v5npdhdrbfq78slpsk8lt2nga.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "pokitmons.pokit",
+ "certificate_hash": "b2f3e34f8e02d15beb0d10d3d48a05148e943642"
+ }
+ },
+ {
+ "client_id": "217769178527-tslgsrrr1o8bli4hr4qnas2u9kg80a9h.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDMh93QvJGUUX8-E-wyJoSS3cFrwfw8Q3w"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "217769178527-l4prj2q9qsuvkodc2cpi84psvul5rth2.apps.googleusercontent.com",
+ "client_type": 3
+ },
+ {
+ "client_id": "217769178527-021j3dpbues9rhbkp6cffnn19mdajorq.apps.googleusercontent.com",
+ "client_type": 2,
+ "ios_info": {
+ "bundle_id": "com.pokitmons.pokit.App"
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/pokitmons/pokit/ExampleInstrumentedTest.kt b/app/src/androidTest/java/pokitmons/pokit/ExampleInstrumentedTest.kt
index 0ac7c25a..58c48f57 100644
--- a/app/src/androidTest/java/pokitmons/pokit/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/pokitmons/pokit/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package pokitmons.pokit
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("pokitmons.pokit", appContext.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 844b4b78..fcab7b1f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,20 +2,24 @@
+
+
+ tools:targetApi="34">
diff --git a/app/src/main/ic_launcher_pooki-playstore.png b/app/src/main/ic_launcher_pooki-playstore.png
new file mode 100644
index 00000000..e9921519
Binary files /dev/null and b/app/src/main/ic_launcher_pooki-playstore.png differ
diff --git a/app/src/main/java/pokitmons/pokit/MainActivity.kt b/app/src/main/java/pokitmons/pokit/MainActivity.kt
index 7450e6d5..8bf0ce26 100644
--- a/app/src/main/java/pokitmons/pokit/MainActivity.kt
+++ b/app/src/main/java/pokitmons/pokit/MainActivity.kt
@@ -3,26 +3,54 @@ package pokitmons.pokit
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import pokitmons.pokit.ui.theme.PokitTheme
+import androidx.compose.ui.res.painterResource
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.delay
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.navigation.RootNavHost
+@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
+ var showSplash by remember { mutableStateOf(true) }
+
+ LaunchedEffect(Unit) {
+ delay(1500)
+ showSplash = false
+ }
+
+ val navHostController = rememberNavController()
+ val navBackStackEntry by navHostController.currentBackStackEntryAsState()
+ val currentDestination by remember(navBackStackEntry) { derivedStateOf { navBackStackEntry?.destination } }
+
PokitTheme {
- // A surface container using the 'background' color from the theme
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Greeting("Android")
+ if (showSplash) {
+ SplashScreen()
+ } else {
+ RootNavHost(navHostController = navHostController)
+ }
+
+ LaunchedEffect(currentDestination) {
+ currentDestination?.route?.let { route ->
+ // 믹스패널/파베 애널리틱스 화면 이동 로깅용
+ }
}
}
}
@@ -30,17 +58,16 @@ class MainActivity : ComponentActivity() {
}
@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- PokitTheme {
- Greeting("Android")
+fun SplashScreen() {
+ Box(
+ modifier = Modifier
+ .background(color = PokitTheme.colors.brandBold)
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ painter = painterResource(id = pokitmons.pokit.core.ui.R.drawable.logo_white),
+ contentDescription = null
+ )
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/pokitmons/pokit/PokitApplication.kt b/app/src/main/java/pokitmons/pokit/PokitApplication.kt
new file mode 100644
index 00000000..77af5190
--- /dev/null
+++ b/app/src/main/java/pokitmons/pokit/PokitApplication.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit
+
+import android.app.Application
+import com.google.firebase.FirebaseApp
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class PokitApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ FirebaseApp.initializeApp(this)
+ }
+}
diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt b/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt
new file mode 100644
index 00000000..aa7f1bb6
--- /dev/null
+++ b/app/src/main/java/pokitmons/pokit/navigation/RootDestination.kt
@@ -0,0 +1,82 @@
+package pokitmons.pokit.navigation
+
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+
+object Login {
+ val route: String = "login"
+}
+
+object TermOfService {
+ val route: String = "termOfService"
+}
+
+object InputNickname {
+ val route: String = "inputNickname"
+}
+
+object SelectKeyword {
+ val route: String = "selectKeyword"
+}
+
+object SignUpSuccess {
+ val route: String = "signupSuccess"
+}
+
+object Home {
+ val route: String = "home"
+}
+
+object AddLink {
+ val route: String = "addLink"
+ val linkIdArg = "link_id"
+ val routeWithArgs = "$route?$linkIdArg={$linkIdArg}"
+ var arguments = listOf(
+ navArgument(linkIdArg) {
+ nullable = true
+ type = NavType.StringType
+ }
+ )
+}
+
+object AddPokit {
+ val route: String = "addPokit"
+ val pokitIdArg = "pokit_id"
+ val routeWithArgs = "$route?$pokitIdArg={$pokitIdArg}"
+ var arguments = listOf(
+ navArgument(pokitIdArg) {
+ nullable = true
+ type = NavType.StringType
+ }
+ )
+}
+
+object PokitDetail {
+ val route: String = "pokitDetail"
+ val pokitIdArg = "pokit_id"
+ val pokitCountQuery = "pokit_count"
+ val routeWithArgs = "$route/{$pokitIdArg}?$pokitCountQuery={$pokitCountQuery}"
+ var arguments = listOf(
+ navArgument(pokitIdArg) { defaultValue = "-" },
+ navArgument(pokitCountQuery) {
+ nullable = true
+ type = NavType.StringType
+ }
+ )
+}
+
+object Search {
+ val route: String = "search"
+}
+
+object Setting {
+ val route: String = "setting"
+}
+
+object EditNickname {
+ val route: String = "editNickname"
+}
+
+object Alarm {
+ val route: String = "alarm"
+}
diff --git a/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt
new file mode 100644
index 00000000..238f3b8a
--- /dev/null
+++ b/app/src/main/java/pokitmons/pokit/navigation/RootNavHost.kt
@@ -0,0 +1,209 @@
+package pokitmons.pokit.navigation
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import com.strayalpaca.addlink.AddLinkScreenContainer
+import com.strayalpaca.addlink.AddLinkViewModel
+import com.strayalpaca.addpokit.AddPokitScreenContainer
+import com.strayalpaca.addpokit.AddPokitViewModel
+import com.strayalpaca.pokitdetail.PokitDetailScreenContainer
+import com.strayalpaca.pokitdetail.PokitDetailViewModel
+import pokitmons.pokit.LoginViewModel
+import pokitmons.pokit.alarm.AlarmScreenContainer
+import pokitmons.pokit.alarm.AlarmViewModel
+import pokitmons.pokit.home.HomeScreen
+import pokitmons.pokit.home.pokit.PokitViewModel
+import pokitmons.pokit.keyword.KeywordScreen
+import pokitmons.pokit.login.LoginScreen
+import pokitmons.pokit.nickname.InputNicknameScreen
+import pokitmons.pokit.search.SearchScreenContainer
+import pokitmons.pokit.search.SearchViewModel
+import pokitmons.pokit.settings.SettingViewModel
+import pokitmons.pokit.settings.nickname.EditNicknameScreen
+import pokitmons.pokit.settings.setting.SettingsScreen
+import pokitmons.pokit.success.SignUpSuccessScreen
+import pokitmons.pokit.terms.TermsOfServiceScreen
+
+@Composable
+fun RootNavHost(
+ navHostController: NavHostController,
+) {
+ NavHost(
+ modifier = Modifier.background(color = Color.White),
+ navController = navHostController,
+ startDestination = Login.route
+ ) {
+ composable(Login.route) {
+ val viewModel: LoginViewModel = hiltViewModel()
+ LoginScreen(
+ loginViewModel = viewModel,
+ onNavigateToTermsOfServiceScreen = { navHostController.navigate(TermOfService.route) },
+ onNavigateToHomeScreen = {
+ navHostController.navigate(Home.route) {
+ popUpTo(navHostController.graph.startDestinationId) {
+ inclusive = true
+ }
+ }
+ }
+ )
+ }
+
+ composable(TermOfService.route) {
+ TermsOfServiceScreen(
+ onNavigateToInputNicknameScreen = { navHostController.navigate(InputNickname.route) },
+ onBackPressed = navHostController::popBackStack
+ )
+ }
+
+ composable(InputNickname.route) {
+ val viewModel: LoginViewModel = hiltViewModel()
+ InputNicknameScreen(
+ viewModel = viewModel,
+ onNavigateToKeywordScreen = { navHostController.navigate(SelectKeyword.route) },
+ onBackPressed = navHostController::popBackStack
+ )
+ }
+
+ composable(SelectKeyword.route) {
+ val viewModel: LoginViewModel = hiltViewModel()
+ KeywordScreen(
+ viewModel = viewModel,
+ onNavigateToSignUpScreen = { navHostController.navigate(SignUpSuccess.route) },
+ onBackPressed = navHostController::popBackStack
+ )
+ }
+
+ composable(SignUpSuccess.route) {
+ SignUpSuccessScreen(
+ onNavigateToMainScreen = {
+ navHostController.navigate(Home.route) {
+ popUpTo(navHostController.graph.startDestinationId) {
+ inclusive = true
+ }
+ }
+ }
+ )
+ }
+
+ composable(Home.route) {
+ Box(modifier = Modifier.fillMaxSize())
+ }
+
+ composable(
+ route = AddLink.routeWithArgs,
+ arguments = AddLink.arguments
+ ) {
+ val viewModel: AddLinkViewModel = hiltViewModel()
+ AddLinkScreenContainer(
+ viewModel = viewModel,
+ onBackPressed = navHostController::popBackStack,
+ onNavigateToAddPokit = {
+ navHostController.navigate(AddPokit.route)
+ }
+ )
+ }
+
+ composable(
+ route = AddPokit.routeWithArgs,
+ arguments = AddPokit.arguments
+ ) {
+ val viewModel: AddPokitViewModel = hiltViewModel()
+ AddPokitScreenContainer(
+ viewModel = viewModel,
+ onBackPressed = navHostController::popBackStack
+ )
+ }
+
+ composable(
+ route = PokitDetail.routeWithArgs,
+ arguments = PokitDetail.arguments
+ ) {
+ val viewModel: PokitDetailViewModel = hiltViewModel()
+ PokitDetailScreenContainer(
+ viewModel = viewModel,
+ onBackPressed = navHostController::popBackStack,
+ onNavigateToLinkModify = { linkId ->
+ navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$linkId")
+ },
+ onNavigateToPokitModify = { pokitId ->
+ navHostController.navigate("${AddPokit.route}?${AddPokit.pokitIdArg}=$pokitId")
+ }
+ )
+ }
+
+ composable(
+ route = Search.route
+ ) {
+ val viewModel: SearchViewModel = hiltViewModel()
+ SearchScreenContainer(
+ viewModel = viewModel,
+ onBackPressed = navHostController::popBackStack,
+ onNavigateToLinkModify = { linkId ->
+ navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$linkId")
+ }
+ )
+ }
+
+ composable(route = Setting.route) {
+ val viewModel: SettingViewModel = hiltViewModel()
+ SettingsScreen(
+ settingViewModel = viewModel,
+ onBackPressed = navHostController::popBackStack,
+ onNavigateToEditNickname = { navHostController.navigate(EditNickname.route) },
+ onNavigateToLogin = {
+ navHostController.navigate(Login.route) {
+ popUpTo(navHostController.graph.startDestinationId) {
+ inclusive = true
+ }
+ }
+ }
+ )
+ }
+
+ composable(route = EditNickname.route) {
+ val viewModel: SettingViewModel = hiltViewModel()
+ EditNicknameScreen(
+ settingViewModel = viewModel,
+ onBackPressed = navHostController::popBackStack
+ )
+ }
+
+ composable(route = Home.route) {
+ val viewModel: PokitViewModel = hiltViewModel()
+ HomeScreen(
+ viewModel = viewModel,
+ onNavigateToSearch = { navHostController.navigate(Search.route) },
+ onNavigateToSetting = { navHostController.navigate(Setting.route) },
+ onNavigateToPokitDetail = { pokitId, linkCount ->
+ navHostController.navigate(
+ "${PokitDetail.route}/$pokitId?${PokitDetail.pokitCountQuery}=$linkCount"
+ )
+ },
+ onNavigateAddLink = { navHostController.navigate(AddLink.route) },
+ onNavigateAddPokit = { navHostController.navigate(AddPokit.route) },
+ onNavigateToLinkModify = { navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$it") },
+ onNavigateToPokitModify = { navHostController.navigate("${AddPokit.route}?${AddPokit.pokitIdArg}=$it") },
+ onNavigateToAlarm = { navHostController.navigate(Alarm.route) }
+ )
+ }
+
+ composable(route = Alarm.route) {
+ val viewModel: AlarmViewModel = hiltViewModel()
+ AlarmScreenContainer(
+ viewModel = viewModel,
+ onBackPressed = navHostController::popBackStack,
+ onNavigateToLinkModify = { linkId ->
+ navHostController.navigate("${AddLink.route}?${AddLink.linkIdArg}=$linkId")
+ }
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/pokitmons/pokit/ui/theme/Color.kt b/app/src/main/java/pokitmons/pokit/ui/theme/Color.kt
index 730c0544..a9154712 100644
--- a/app/src/main/java/pokitmons/pokit/ui/theme/Color.kt
+++ b/app/src/main/java/pokitmons/pokit/ui/theme/Color.kt
@@ -8,4 +8,4 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
+val Pink40 = Color(0xFF7D5260)
diff --git a/app/src/main/java/pokitmons/pokit/ui/theme/Theme.kt b/app/src/main/java/pokitmons/pokit/ui/theme/Theme.kt
index fbb08f18..63ce68b7 100644
--- a/app/src/main/java/pokitmons/pokit/ui/theme/Theme.kt
+++ b/app/src/main/java/pokitmons/pokit/ui/theme/Theme.kt
@@ -42,7 +42,7 @@ fun PokitTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
- content: @Composable () -> Unit
+ content: @Composable () -> Unit,
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
@@ -67,4 +67,4 @@ fun PokitTheme(
typography = Typography,
content = content
)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/pokitmons/pokit/ui/theme/Type.kt b/app/src/main/java/pokitmons/pokit/ui/theme/Type.kt
index d1e3a967..180ce696 100644
--- a/app/src/main/java/pokitmons/pokit/ui/theme/Type.kt
+++ b/app/src/main/java/pokitmons/pokit/ui/theme/Type.kt
@@ -31,4 +31,4 @@ val Typography = Typography(
letterSpacing = 0.5.sp
)
*/
-)
\ No newline at end of file
+)
diff --git a/app/src/main/res/drawable/ic_launcher_pooki_background.xml b/app/src/main/res/drawable/ic_launcher_pooki_background.xml
new file mode 100644
index 00000000..ca3826a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_pooki_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pooki.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pooki.xml
new file mode 100644
index 00000000..16d6c6ad
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pooki.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pooki_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pooki_round.xml
new file mode 100644
index 00000000..16d6c6ad
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pooki_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_pooki.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_pooki.webp
new file mode 100644
index 00000000..32bf6809
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_pooki.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_pooki_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_pooki_foreground.webp
new file mode 100644
index 00000000..f6c54924
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_pooki_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_pooki_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_pooki_round.webp
new file mode 100644
index 00000000..f0ac05d8
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_pooki_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_pooki.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_pooki.webp
new file mode 100644
index 00000000..56045021
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_pooki.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_pooki_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_pooki_foreground.webp
new file mode 100644
index 00000000..4ec1077f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_pooki_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_pooki_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_pooki_round.webp
new file mode 100644
index 00000000..d193bdd5
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_pooki_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki.webp
new file mode 100644
index 00000000..d133a7de
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki_foreground.webp
new file mode 100644
index 00000000..01ba3853
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki_round.webp
new file mode 100644
index 00000000..ca2308aa
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_pooki_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki.webp
new file mode 100644
index 00000000..9eb92b03
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki_foreground.webp
new file mode 100644
index 00000000..f5461244
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki_round.webp
new file mode 100644
index 00000000..22eb982c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_pooki_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki.webp
new file mode 100644
index 00000000..9547d5a4
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki_foreground.webp
new file mode 100644
index 00000000..37cb0d80
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki_round.webp
new file mode 100644
index 00000000..09acb390
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_pooki_round.webp differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127d..b2eab495 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,4 +7,5 @@
#FF018786
#FF000000
#FFFFFFFF
+ #FE8422
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b4f8758e..90024d92 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
- pokit
+ Pokit
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index dc078bc4..95492f4f 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,7 @@
-
-
+
-
+
+
\ No newline at end of file
diff --git a/app/src/test/java/pokitmons/pokit/ExampleUnitTest.kt b/app/src/test/java/pokitmons/pokit/ExampleUnitTest.kt
index d28f0268..a885e7ed 100644
--- a/app/src/test/java/pokitmons/pokit/ExampleUnitTest.kt
+++ b/app/src/test/java/pokitmons/pokit/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
package pokitmons.pokit
+import junit.framework.TestCase.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
@@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
-}
\ No newline at end of file
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index a0985efc..c6357ec3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- alias(libs.plugins.androidApplication) apply false
- alias(libs.plugins.jetbrainsKotlinAndroid) apply false
-}
\ No newline at end of file
+ alias(libs.plugins.com.android.application) apply false
+ alias(libs.plugins.org.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.ktlint.gradle) apply false
+ alias(libs.plugins.org.jetbrains.kotlin.jvm) apply false
+ alias(libs.plugins.hilt) apply false
+ alias(libs.plugins.com.android.library) apply false
+ alias(libs.plugins.googleServices) apply false
+ alias(libs.plugins.kotlin.parcelize) apply false
+}
+
+subprojects {
+ apply(plugin = "org.jlleitschuh.gradle.ktlint")
+}
diff --git a/core/feature/.gitignore b/core/feature/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core/feature/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/feature/build.gradle.kts b/core/feature/build.gradle.kts
new file mode 100644
index 00000000..26159256
--- /dev/null
+++ b/core/feature/build.gradle.kts
@@ -0,0 +1,59 @@
+plugins {
+ alias(libs.plugins.com.android.library)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+ alias(libs.plugins.kotlin.parcelize)
+}
+
+android {
+ namespace = "pokitmons.pokit.core.feature"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.1"
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+}
diff --git a/core/feature/consumer-rules.pro b/core/feature/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/core/feature/proguard-rules.pro b/core/feature/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/core/feature/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt b/core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..b32e09bf
--- /dev/null
+++ b/core/feature/src/androidTest/java/pokitmons/pokit/core/feature/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package pokitmons.pokit.core.feature
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("pokitmons.pokit.core.feature.test", appContext.packageName)
+ }
+}
diff --git a/core/feature/src/main/AndroidManifest.xml b/core/feature/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/core/feature/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/flow/CollectAsEffect.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/flow/CollectAsEffect.kt
new file mode 100644
index 00000000..bb004398
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/flow/CollectAsEffect.kt
@@ -0,0 +1,22 @@
+package pokitmons.pokit.core.feature.flow
+
+import android.annotation.SuppressLint
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+@SuppressLint("ComposableNaming")
+@Composable
+fun Flow.collectAsEffect(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: (T) -> Unit,
+) {
+ LaunchedEffect(Unit) {
+ onEach(block).flowOn(context).launchIn(this)
+ }
+}
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/flow/EventFlow.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/flow/EventFlow.kt
new file mode 100644
index 00000000..9c938339
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/flow/EventFlow.kt
@@ -0,0 +1,47 @@
+package pokitmons.pokit.core.feature.flow
+
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.MutableSharedFlow
+import java.util.concurrent.atomic.AtomicBoolean
+
+interface EventFlow : Flow {
+ companion object {
+ const val DEFAULT_REPLAY: Int = 1
+ }
+}
+
+interface MutableEventFlow : EventFlow, FlowCollector
+
+fun MutableEventFlow(
+ replay: Int = EventFlow.DEFAULT_REPLAY,
+): MutableEventFlow = EventFlowImpl(replay)
+
+fun MutableEventFlow.asEventFlow(): EventFlow = ReadOnlyEventFlow(this)
+
+private class ReadOnlyEventFlow(flow: EventFlow) : EventFlow by flow
+
+private class EventFlowImpl(
+ replay: Int,
+) : MutableEventFlow {
+
+ private val flow: MutableSharedFlow> = MutableSharedFlow(replay = replay)
+
+ @InternalCoroutinesApi
+ override suspend fun collect(collector: FlowCollector) = flow
+ .collect { slot ->
+ if (!slot.markConsumed()) {
+ collector.emit(slot.value)
+ }
+ }
+
+ override suspend fun emit(value: T) {
+ flow.emit(EventFlowSlot(value))
+ }
+}
+
+private class EventFlowSlot(val value: T) {
+ private val consumed: AtomicBoolean = AtomicBoolean(false)
+ fun markConsumed(): Boolean = consumed.getAndSet(true)
+}
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/model/NetworkState.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/NetworkState.kt
new file mode 100644
index 00000000..4732be10
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/model/NetworkState.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.core.feature.model
+
+enum class NetworkState {
+ IDLE, LOADING, ERROR
+}
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt
new file mode 100644
index 00000000..eff7a88d
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkArg.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.core.feature.navigation.args
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class LinkArg(
+ val id: Int,
+ val title: String,
+ val thumbnail: String,
+ val createdAt: String,
+ val domain: String,
+ val pokitId: Int,
+) : Parcelable
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt
new file mode 100644
index 00000000..2877d356
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/LinkUpdateEvent.kt
@@ -0,0 +1,36 @@
+package pokitmons.pokit.core.feature.navigation.args
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+
+object LinkUpdateEvent {
+ private val _updatedLink = MutableSharedFlow()
+ val updatedLink = _updatedLink.asSharedFlow()
+
+ private val _removedLink = MutableSharedFlow()
+ val removedLink = _removedLink.asSharedFlow()
+
+ private val _addedLink = MutableSharedFlow()
+ val addedLink = _addedLink.asSharedFlow()
+
+ fun modifySuccess(link: LinkArg) {
+ CoroutineScope(Dispatchers.Default).launch {
+ _updatedLink.emit(link)
+ }
+ }
+
+ fun removeSuccess(linkId: Int) {
+ CoroutineScope(Dispatchers.Default).launch {
+ _removedLink.emit(linkId)
+ }
+ }
+
+ fun createSuccess(link: LinkArg) {
+ CoroutineScope(Dispatchers.Default).launch {
+ _addedLink.emit(link)
+ }
+ }
+}
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt
new file mode 100644
index 00000000..6a67a471
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitArg.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.core.feature.navigation.args
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class PokitArg(
+ val id: Int,
+ val imageId: Int,
+ val imageUrl: String,
+ val title: String,
+) : Parcelable
diff --git a/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt
new file mode 100644
index 00000000..a506e3ec
--- /dev/null
+++ b/core/feature/src/main/java/pokitmons/pokit/core/feature/navigation/args/PokitUpdateEvent.kt
@@ -0,0 +1,36 @@
+package pokitmons.pokit.core.feature.navigation.args
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+
+object PokitUpdateEvent {
+ private val _updatedPokit = MutableSharedFlow()
+ val updatedPokit = _updatedPokit.asSharedFlow()
+
+ private val _removedPokitId = MutableSharedFlow()
+ val removedPokitId = _removedPokitId.asSharedFlow()
+
+ private val _addedPokit = MutableSharedFlow()
+ val addedPokit = _addedPokit.asSharedFlow()
+
+ fun updatePokit(pokitArg: PokitArg) {
+ CoroutineScope(Dispatchers.Default).launch {
+ _updatedPokit.emit(pokitArg)
+ }
+ }
+
+ fun removePokit(pokitId: Int) {
+ CoroutineScope(Dispatchers.Default).launch {
+ _removedPokitId.emit(pokitId)
+ }
+ }
+
+ fun createPokit(pokitArg: PokitArg) {
+ CoroutineScope(Dispatchers.Default).launch {
+ _addedPokit.emit(pokitArg)
+ }
+ }
+}
diff --git a/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt b/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt
new file mode 100644
index 00000000..00cadeb1
--- /dev/null
+++ b/core/feature/src/test/java/pokitmons/pokit/core/feature/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.core.feature
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/core/ui/.gitignore b/core/ui/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/core/ui/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
new file mode 100644
index 00000000..ebf7dcf7
--- /dev/null
+++ b/core/ui/build.gradle.kts
@@ -0,0 +1,55 @@
+plugins {
+ alias(libs.plugins.com.android.library)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+}
+
+android {
+ namespace = "pokitmons.pokit.core.ui"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.1"
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+}
diff --git a/core/ui/consumer-rules.pro b/core/ui/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/core/ui/proguard-rules.pro b/core/ui/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/core/ui/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/ui/src/androidTest/java/pokitmons/pokit/core/ui/ExampleInstrumentedTest.kt b/core/ui/src/androidTest/java/pokitmons/pokit/core/ui/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..ba72dbef
--- /dev/null
+++ b/core/ui/src/androidTest/java/pokitmons/pokit/core/ui/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package pokitmons.pokit.core.ui
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("pokitmons.pokit.core.ui.test", appContext.packageName)
+ }
+}
diff --git a/core/ui/src/main/AndroidManifest.xml b/core/ui/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/core/ui/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/PokitButton.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/PokitButton.kt
new file mode 100644
index 00000000..a2827902
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/PokitButton.kt
@@ -0,0 +1,68 @@
+package pokitmons.pokit.core.ui.components.atom.button
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIcon
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIconPosition
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonShape
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonState
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.components.atom.button.subcomponents.container.PokitButtonContainer
+import pokitmons.pokit.core.ui.components.atom.button.subcomponents.icon.PokitButtonIcon
+import pokitmons.pokit.core.ui.components.atom.button.subcomponents.text.PokitButtonText
+
+@Composable
+fun PokitButton(
+ text: String?,
+ icon: PokitButtonIcon?,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enable: Boolean = true,
+ shape: PokitButtonShape = PokitButtonShape.RECTANGLE,
+ style: PokitButtonStyle = PokitButtonStyle.FILLED,
+ size: PokitButtonSize = PokitButtonSize.MIDDLE,
+ type: PokitButtonType = PokitButtonType.PRIMARY,
+) {
+ val state = remember(enable) { getState(enable) }
+
+ PokitButtonContainer(
+ hasText = text != null,
+ iconPosition = icon?.position,
+ modifier = modifier,
+ shape = shape,
+ state = state,
+ style = style,
+ size = size,
+ type = type,
+ onClick = onClick
+ ) {
+ if (icon?.position == PokitButtonIconPosition.LEFT) {
+ PokitButtonIcon(size = size, state = state, type = type, style = style, resourceId = icon.resourceId)
+ }
+
+ text?.let {
+ PokitButtonText(
+ text = text,
+ size = size,
+ state = state,
+ type = type,
+ style = style
+ )
+ }
+
+ if (icon?.position == PokitButtonIconPosition.RIGHT) {
+ PokitButtonIcon(size = size, state = state, type = type, style = style, resourceId = icon.resourceId)
+ }
+ }
+}
+
+private fun getState(enable: Boolean): PokitButtonState {
+ return if (enable) {
+ PokitButtonState.DEFAULT
+ } else {
+ PokitButtonState.DISABLE
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/Preview.kt
new file mode 100644
index 00000000..78a814b3
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/Preview.kt
@@ -0,0 +1,85 @@
+package pokitmons.pokit.core.ui.components.atom.button
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIcon
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIconPosition
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonShape
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitButtonPreview() {
+ val scrollState = rememberScrollState()
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ enumValues().forEach { size ->
+ enumValues().forEach { shape ->
+ enumValues().forEach { type ->
+ enumValues().forEach { style ->
+ for (enable in arrayOf(false, true)) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ enumValues().forEach { iconPosition ->
+ PokitButton(
+ text = "버튼",
+ icon = PokitButtonIcon(resourceId = R.drawable.icon_24_search, position = iconPosition),
+ onClick = {},
+ enable = enable,
+ size = size,
+ shape = shape,
+ style = style,
+ type = type
+ )
+ }
+
+ PokitButton(
+ text = "버튼",
+ icon = null,
+ onClick = {},
+ enable = enable,
+ size = size,
+ shape = shape,
+ style = style,
+ type = type
+ )
+
+ PokitButton(
+ text = null,
+ icon = PokitButtonIcon(resourceId = R.drawable.icon_24_search, position = PokitButtonIconPosition.LEFT),
+ onClick = {},
+ enable = enable,
+ size = size,
+ shape = shape,
+ style = style,
+ type = type
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/attributes/ButtonAttributes.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/attributes/ButtonAttributes.kt
new file mode 100644
index 00000000..f48ad11b
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/attributes/ButtonAttributes.kt
@@ -0,0 +1,44 @@
+package pokitmons.pokit.core.ui.components.atom.button.attributes
+
+import androidx.compose.ui.graphics.Color
+
+enum class PokitButtonShape {
+ ROUND, RECTANGLE,
+}
+
+internal enum class PokitButtonState {
+ DEFAULT, DISABLE,
+}
+
+enum class PokitButtonStyle {
+ FILLED, STROKE, DEFAULT
+}
+
+enum class PokitButtonSize {
+ SMALL, MIDDLE, LARGE,
+}
+
+enum class PokitButtonType {
+ PRIMARY, SECONDARY,
+}
+
+data class PokitButtonIcon(
+ val resourceId: Int,
+ val position: PokitButtonIconPosition,
+)
+
+enum class PokitButtonIconPosition {
+ RIGHT, LEFT,
+}
+
+enum class PokitLoginButtonType {
+ GOOGLE, APPLE
+}
+
+data class PokitLoginResource(
+ val iconResourceId: Int,
+ val iconTintColor: Color = Color.Unspecified,
+ val textColor: Color,
+ val backgroundColor: Color,
+ val borderColor: Color,
+)
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/container/PokitButtonContainer.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/container/PokitButtonContainer.kt
new file mode 100644
index 00000000..f64f435d
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/container/PokitButtonContainer.kt
@@ -0,0 +1,63 @@
+package pokitmons.pokit.core.ui.components.atom.button.subcomponents.container
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIconPosition
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonShape
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonState
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+
+@Composable
+internal fun PokitButtonContainer(
+ hasText: Boolean,
+ iconPosition: PokitButtonIconPosition?,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+ shape: PokitButtonShape = PokitButtonShape.RECTANGLE,
+ state: PokitButtonState = PokitButtonState.DEFAULT,
+ style: PokitButtonStyle = PokitButtonStyle.FILLED,
+ size: PokitButtonSize = PokitButtonSize.MIDDLE,
+ type: PokitButtonType = PokitButtonType.PRIMARY,
+ content: @Composable RowScope.() -> Unit,
+) {
+ val space = getItemSpace(size)
+
+ Row(
+ modifier = Modifier
+ .pokitButtonContainerModifier(
+ hasText = hasText,
+ iconPosition = iconPosition,
+ shape = shape,
+ state = state,
+ style = style,
+ size = size,
+ type = type,
+ onClick = onClick
+ )
+ .then(modifier),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement
+ .spacedBy(
+ space = space,
+ alignment = Alignment.CenterHorizontally
+ )
+ ) {
+ content()
+ }
+}
+
+private fun getItemSpace(size: PokitButtonSize): Dp {
+ return when (size) {
+ PokitButtonSize.SMALL -> 4.dp
+ PokitButtonSize.MIDDLE -> 8.dp
+ PokitButtonSize.LARGE -> 12.dp
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/container/PokitButtonContainerModifier.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/container/PokitButtonContainerModifier.kt
new file mode 100644
index 00000000..c90ac92d
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/container/PokitButtonContainerModifier.kt
@@ -0,0 +1,211 @@
+package pokitmons.pokit.core.ui.components.atom.button.subcomponents.container
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonIconPosition
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonShape
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonState
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun Modifier.pokitButtonContainerModifier(
+ hasText: Boolean,
+ iconPosition: PokitButtonIconPosition?,
+ shape: PokitButtonShape,
+ state: PokitButtonState,
+ style: PokitButtonStyle,
+ size: PokitButtonSize,
+ type: PokitButtonType,
+ onClick: () -> Unit = {},
+): Modifier {
+ val buttonShape = getShape(shape, size)
+ val strokeWidth = getBorderWidth(style)
+ val padding = getPadding(size, hasText, iconPosition)
+ val backgroundColor = getBackgroundColor(type, style, state)
+ val strokeColor = getStrokeColor(type, state)
+
+ return this then Modifier
+ .clip(
+ shape = buttonShape
+ )
+ .clickable(
+ enabled = (state != PokitButtonState.DISABLE),
+ onClick = onClick,
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ )
+ .background(
+ shape = buttonShape,
+ color = backgroundColor
+ )
+ .border(
+ shape = buttonShape,
+ width = strokeWidth,
+ color = strokeColor
+ )
+ .padding(
+ paddingValues = padding
+ )
+}
+
+private fun getShape(
+ shape: PokitButtonShape,
+ size: PokitButtonSize,
+): Shape {
+ return when {
+ shape == PokitButtonShape.RECTANGLE && size == PokitButtonSize.SMALL -> {
+ RoundedCornerShape(4.dp)
+ }
+
+ shape == PokitButtonShape.RECTANGLE && size == PokitButtonSize.MIDDLE -> {
+ RoundedCornerShape(8.dp)
+ }
+
+ shape == PokitButtonShape.RECTANGLE && size == PokitButtonSize.LARGE -> {
+ RoundedCornerShape(8.dp)
+ }
+
+ else -> { // rounded button
+ RoundedCornerShape(9999.dp)
+ }
+ }
+}
+
+private fun getBorderWidth(
+ style: PokitButtonStyle,
+): Dp {
+ return when (style) {
+ PokitButtonStyle.FILLED -> 0.dp
+ PokitButtonStyle.STROKE -> 1.dp
+ PokitButtonStyle.DEFAULT -> 1.dp
+ }
+}
+
+private fun getPadding(
+ size: PokitButtonSize,
+ hasText: Boolean,
+ iconPosition: PokitButtonIconPosition?,
+): PaddingValues {
+ return when (size) {
+ PokitButtonSize.SMALL -> {
+ getPaddingBySize(
+ hasText = hasText,
+ iconPosition = iconPosition,
+ verticalPaddingSize = 8.dp,
+ textSidePaddingSize = 12.dp,
+ iconSidePaddingSize = 8.dp,
+ textOnlyPadding = 12.dp
+ )
+ }
+
+ PokitButtonSize.MIDDLE -> {
+ getPaddingBySize(
+ hasText = hasText,
+ iconPosition = iconPosition,
+ verticalPaddingSize = 10.dp,
+ textSidePaddingSize = 20.dp,
+ iconSidePaddingSize = 16.dp,
+ textOnlyPadding = 16.dp
+ )
+ }
+
+ PokitButtonSize.LARGE -> {
+ getPaddingBySize(
+ hasText = hasText,
+ iconPosition = iconPosition,
+ verticalPaddingSize = 13.dp,
+ textSidePaddingSize = 24.dp,
+ iconSidePaddingSize = 20.dp,
+ textOnlyPadding = 20.dp
+ )
+ }
+ }
+}
+
+private fun getPaddingBySize(
+ hasText: Boolean,
+ iconPosition: PokitButtonIconPosition?,
+ verticalPaddingSize: Dp,
+ textSidePaddingSize: Dp,
+ iconSidePaddingSize: Dp,
+ textOnlyPadding: Dp,
+): PaddingValues {
+ return if (!hasText) { // icon only
+ PaddingValues(all = verticalPaddingSize)
+ } else if (iconPosition == PokitButtonIconPosition.LEFT) {
+ PaddingValues(start = iconSidePaddingSize, top = verticalPaddingSize, bottom = verticalPaddingSize, end = textSidePaddingSize)
+ } else if (iconPosition == PokitButtonIconPosition.RIGHT) {
+ PaddingValues(start = textSidePaddingSize, top = verticalPaddingSize, bottom = verticalPaddingSize, end = iconSidePaddingSize)
+ } else { // text only
+ PaddingValues(horizontal = textOnlyPadding, vertical = verticalPaddingSize)
+ }
+}
+
+@Composable
+private fun getBackgroundColor(
+ type: PokitButtonType,
+ style: PokitButtonStyle,
+ state: PokitButtonState,
+): Color {
+ return when {
+ state == PokitButtonState.DISABLE -> {
+ PokitTheme.colors.backgroundDisable
+ }
+
+ style == PokitButtonStyle.STROKE -> {
+ PokitTheme.colors.backgroundBase
+ }
+
+ type == PokitButtonType.PRIMARY && style == PokitButtonStyle.FILLED -> {
+ PokitTheme.colors.brand
+ }
+
+ type == PokitButtonType.SECONDARY && style == PokitButtonStyle.FILLED -> {
+ PokitTheme.colors.backgroundTertiary
+ }
+
+ else -> {
+ PokitTheme.colors.backgroundBase
+ }
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ type: PokitButtonType,
+ state: PokitButtonState,
+): Color {
+ return when {
+ state == PokitButtonState.DISABLE -> {
+ Color.Unspecified
+ }
+
+ type == PokitButtonType.PRIMARY -> {
+ PokitTheme.colors.brand
+ }
+
+ type == PokitButtonType.SECONDARY -> {
+ PokitTheme.colors.borderSecondary
+ }
+
+ else -> {
+ Color.Unspecified
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/icon/PokitButtonIcon.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/icon/PokitButtonIcon.kt
new file mode 100644
index 00000000..5a0dd969
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/icon/PokitButtonIcon.kt
@@ -0,0 +1,82 @@
+package pokitmons.pokit.core.ui.components.atom.button.subcomponents.icon
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonState
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun PokitButtonIcon(
+ size: PokitButtonSize,
+ state: PokitButtonState,
+ type: PokitButtonType,
+ style: PokitButtonStyle,
+ resourceId: Int,
+) {
+ val iconColor = getColor(state = state, type = type, style = style)
+ val iconSize = getSize(size = size)
+
+ Icon(
+ painter = painterResource(id = resourceId),
+ contentDescription = null,
+ tint = iconColor,
+ modifier = Modifier.size(iconSize)
+ )
+}
+
+@Composable
+private fun getColor(
+ state: PokitButtonState,
+ type: PokitButtonType,
+ style: PokitButtonStyle,
+): Color {
+ return when {
+ style == PokitButtonStyle.DEFAULT -> {
+ PokitTheme.colors.borderSecondary
+ }
+
+ state == PokitButtonState.DISABLE -> {
+ PokitTheme.colors.iconDisable
+ }
+
+ type == PokitButtonType.PRIMARY && style == PokitButtonStyle.FILLED -> {
+ PokitTheme.colors.inverseWh
+ }
+
+ type == PokitButtonType.PRIMARY && style == PokitButtonStyle.STROKE -> {
+ PokitTheme.colors.iconPrimary
+ }
+
+ type == PokitButtonType.SECONDARY && style == PokitButtonStyle.FILLED -> {
+ PokitTheme.colors.inverseWh
+ }
+
+ type == PokitButtonType.SECONDARY && style == PokitButtonStyle.STROKE -> {
+ PokitTheme.colors.iconPrimary
+ }
+
+ else -> {
+ PokitTheme.colors.iconPrimary
+ }
+ }
+}
+
+@Composable
+private fun getSize(
+ size: PokitButtonSize,
+): Dp {
+ return when (size) {
+ PokitButtonSize.SMALL -> 16.dp
+ PokitButtonSize.MIDDLE -> 20.dp
+ PokitButtonSize.LARGE -> 24.dp
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/text/PokitButtonText.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/text/PokitButtonText.kt
new file mode 100644
index 00000000..8efd82fd
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/button/subcomponents/text/PokitButtonText.kt
@@ -0,0 +1,92 @@
+package pokitmons.pokit.core.ui.components.atom.button.subcomponents.text
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonState
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun PokitButtonText(
+ text: String,
+ size: PokitButtonSize,
+ state: PokitButtonState,
+ type: PokitButtonType,
+ style: PokitButtonStyle,
+) {
+ val textStyle = getStyle(size = size, state = state, type = type, style = style)
+ val textColor = getColor(state = state, type = type, style = style)
+ Text(text = text, style = textStyle, color = textColor)
+}
+
+@Composable
+private fun getStyle(
+ size: PokitButtonSize,
+ state: PokitButtonState,
+ type: PokitButtonType,
+ style: PokitButtonStyle,
+): TextStyle {
+ return when {
+ size == PokitButtonSize.SMALL -> {
+ PokitTheme.typography.label3Regular
+ }
+
+ size == PokitButtonSize.MIDDLE -> {
+ PokitTheme.typography.label2Regular
+ }
+
+ (
+ size == PokitButtonSize.LARGE &&
+ state == PokitButtonState.DEFAULT &&
+ type == PokitButtonType.PRIMARY &&
+ style == PokitButtonStyle.FILLED
+ ) -> {
+ PokitTheme.typography.label1SemiBold
+ }
+
+ else -> {
+ PokitTheme.typography.label1Regular
+ }
+ }
+}
+
+@Composable
+private fun getColor(
+ state: PokitButtonState,
+ type: PokitButtonType,
+ style: PokitButtonStyle,
+): Color {
+ return when {
+ style == PokitButtonStyle.DEFAULT -> {
+ PokitTheme.colors.textTertiary
+ }
+
+ state == PokitButtonState.DISABLE -> {
+ PokitTheme.colors.textDisable
+ }
+
+ type == PokitButtonType.PRIMARY && style == PokitButtonStyle.FILLED -> {
+ PokitTheme.colors.inverseWh
+ }
+
+ type == PokitButtonType.PRIMARY && style == PokitButtonStyle.STROKE -> {
+ PokitTheme.colors.textSecondary
+ }
+
+ type == PokitButtonType.SECONDARY && style == PokitButtonStyle.FILLED -> {
+ PokitTheme.colors.inverseWh
+ }
+
+ type == PokitButtonType.SECONDARY && style == PokitButtonStyle.STROKE -> {
+ PokitTheme.colors.textPrimary
+ }
+
+ else -> {
+ PokitTheme.colors.textPrimary
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/PokitCheckbox.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/PokitCheckbox.kt
new file mode 100644
index 00000000..c366b9c8
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/PokitCheckbox.kt
@@ -0,0 +1,153 @@
+package pokitmons.pokit.core.ui.components.atom.checkbox
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.checkbox.attributes.PokitCheckboxShape
+import pokitmons.pokit.core.ui.components.atom.checkbox.attributes.PokitCheckboxStyle
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitCheckbox(
+ checked: Boolean,
+ onClick: (Boolean) -> Unit,
+ style: PokitCheckboxStyle = PokitCheckboxStyle.STROKE,
+ shape: PokitCheckboxShape = PokitCheckboxShape.RECTANGLE,
+ enabled: Boolean = true,
+) {
+ val checkboxShape = getShape(shape = shape)
+ val backgroundColor = getBackgroundColor(style = style, checked = checked, enabled = enabled)
+ val iconTintColor = getIconTintColor(style = style, checked = checked, enabled = enabled)
+ val strokeColor = getStrokeColor(style = style, checked = checked, enabled = enabled)
+
+ Image(
+ painter = painterResource(id = R.drawable.icon_24_check),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(iconTintColor),
+ modifier = Modifier
+ .size(24.dp)
+ .clip(
+ shape = checkboxShape
+ )
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ enabled = enabled,
+ onClick = {
+ onClick(!checked)
+ }
+ )
+ .background(
+ color = backgroundColor
+ )
+ .border(
+ width = 1.dp,
+ color = strokeColor,
+ shape = checkboxShape
+ )
+ )
+}
+
+@Composable
+private fun getShape(
+ shape: PokitCheckboxShape,
+): Shape {
+ return when (shape) {
+ PokitCheckboxShape.RECTANGLE -> RoundedCornerShape(4.dp)
+ PokitCheckboxShape.CIRCLE -> CircleShape
+ }
+}
+
+@Composable
+private fun getIconTintColor(
+ style: PokitCheckboxStyle,
+ checked: Boolean,
+ enabled: Boolean,
+): Color {
+ return when {
+ !enabled -> {
+ PokitTheme.colors.iconDisable
+ }
+
+ !checked -> {
+ PokitTheme.colors.iconTertiary
+ }
+
+ style == PokitCheckboxStyle.FILLED -> {
+ PokitTheme.colors.inverseWh
+ }
+
+ else -> {
+ PokitTheme.colors.brand
+ }
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ style: PokitCheckboxStyle,
+ checked: Boolean,
+ enabled: Boolean,
+): Color {
+ return when {
+ !enabled -> {
+ Color.Unspecified
+ }
+
+ !checked && (style != PokitCheckboxStyle.ICON_ONLY) -> {
+ PokitTheme.colors.borderSecondary
+ }
+
+ style == PokitCheckboxStyle.STROKE -> {
+ PokitTheme.colors.brand
+ }
+
+ else -> {
+ Color.Unspecified
+ }
+ }
+}
+
+@Composable
+private fun getBackgroundColor(
+ style: PokitCheckboxStyle,
+ checked: Boolean,
+ enabled: Boolean,
+): Color {
+ return when {
+ !enabled -> {
+ PokitTheme.colors.backgroundDisable
+ }
+
+ !checked && (style != PokitCheckboxStyle.ICON_ONLY) -> {
+ PokitTheme.colors.backgroundBase
+ }
+
+ style == PokitCheckboxStyle.FILLED -> {
+ PokitTheme.colors.brand
+ }
+
+ style == PokitCheckboxStyle.STROKE -> {
+ PokitTheme.colors.backgroundBase
+ }
+
+ else -> {
+ Color.Unspecified
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/Preview.kt
new file mode 100644
index 00000000..7442c1e8
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/Preview.kt
@@ -0,0 +1,73 @@
+package pokitmons.pokit.core.ui.components.atom.checkbox
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.checkbox.attributes.PokitCheckboxShape
+import pokitmons.pokit.core.ui.components.atom.checkbox.attributes.PokitCheckboxStyle
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitButtonPreview() {
+ val scrollState = rememberScrollState()
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ enumValues().forEach { shape ->
+ enumValues().forEach { style ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PokitCheckbox(
+ checked = true,
+ onClick = {},
+ shape = shape,
+ style = style,
+ enabled = true
+ )
+
+ PokitCheckbox(
+ checked = false,
+ onClick = {},
+ shape = shape,
+ style = style,
+ enabled = true
+ )
+
+ PokitCheckbox(
+ checked = true,
+ onClick = {},
+ shape = shape,
+ style = style,
+ enabled = false
+ )
+
+ PokitCheckbox(
+ checked = false,
+ onClick = {},
+ shape = shape,
+ style = style,
+ enabled = false
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/attributes/CheckboxAttributes.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/attributes/CheckboxAttributes.kt
new file mode 100644
index 00000000..3aefeee8
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/checkbox/attributes/CheckboxAttributes.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.core.ui.components.atom.checkbox.attributes
+
+enum class PokitCheckboxStyle {
+ FILLED, STROKE, ICON_ONLY
+}
+
+enum class PokitCheckboxShape {
+ RECTANGLE, CIRCLE
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/PokitChip.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/PokitChip.kt
new file mode 100644
index 00000000..6b10cfd1
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/PokitChip.kt
@@ -0,0 +1,40 @@
+package pokitmons.pokit.core.ui.components.atom.chip
+
+import androidx.compose.runtime.Composable
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipIconPosiion
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipSize
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipState
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipType
+import pokitmons.pokit.core.ui.components.atom.chip.subcomponents.container.PokitChipContainer
+import pokitmons.pokit.core.ui.components.atom.chip.subcomponents.icon.PokitChipIcon
+import pokitmons.pokit.core.ui.components.atom.chip.subcomponents.text.PokitChipText
+
+@Composable
+fun PokitChip(
+ data: T,
+ text: String,
+ removeIconPosition: PokitChipIconPosiion?,
+ onClickRemove: ((T) -> Unit)?,
+ onClickItem: ((T) -> Unit)?,
+ state: PokitChipState = PokitChipState.DEFAULT,
+ size: PokitChipSize = PokitChipSize.SMALL,
+ type: PokitChipType = PokitChipType.PRIMARY,
+) {
+ PokitChipContainer(
+ iconPosiion = removeIconPosition,
+ state = state,
+ size = size,
+ type = type,
+ onClick = { onClickItem?.invoke(data) }
+ ) {
+ if (removeIconPosition == PokitChipIconPosiion.LEFT) {
+ PokitChipIcon(state = state, size = size, onClick = { onClickRemove?.invoke(data) })
+ }
+
+ PokitChipText(text = text, state = state, size = size)
+
+ if (removeIconPosition == PokitChipIconPosiion.RIGHT) {
+ PokitChipIcon(state = state, size = size, onClick = { onClickRemove?.invoke(data) })
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/Preview.kt
new file mode 100644
index 00000000..4f994733
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/Preview.kt
@@ -0,0 +1,78 @@
+package pokitmons.pokit.core.ui.components.atom.chip
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipIconPosiion
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipSize
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipState
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitButtonPreview() {
+ val scrollState = rememberScrollState()
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ enumValues().forEach { size ->
+ enumValues().forEach { state ->
+ enumValues().forEach { type ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PokitChip(
+ data = "String",
+ text = "텍스트",
+ state = state,
+ type = type,
+ size = size,
+ removeIconPosition = PokitChipIconPosiion.LEFT,
+ onClickRemove = {},
+ onClickItem = {}
+ )
+
+ PokitChip(
+ data = "String",
+ text = "텍스트",
+ state = state,
+ type = type,
+ size = size,
+ removeIconPosition = PokitChipIconPosiion.RIGHT,
+ onClickRemove = {},
+ onClickItem = {}
+ )
+
+ PokitChip(
+ data = "String",
+ text = "텍스트",
+ state = state,
+ type = type,
+ size = size,
+ removeIconPosition = null,
+ onClickRemove = {},
+ onClickItem = {}
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/attributes/ChipAttributes.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/attributes/ChipAttributes.kt
new file mode 100644
index 00000000..254ddb76
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/attributes/ChipAttributes.kt
@@ -0,0 +1,17 @@
+package pokitmons.pokit.core.ui.components.atom.chip.attributes
+
+enum class PokitChipState {
+ DEFAULT, FILLED, STROKE, DISABLED
+}
+
+enum class PokitChipType {
+ PRIMARY, SECONDARY
+}
+
+enum class PokitChipSize {
+ SMALL, MEDIUM
+}
+
+enum class PokitChipIconPosiion {
+ RIGHT, LEFT,
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/container/PokitChipContainer.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/container/PokitChipContainer.kt
new file mode 100644
index 00000000..ce1e3545
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/container/PokitChipContainer.kt
@@ -0,0 +1,54 @@
+package pokitmons.pokit.core.ui.components.atom.chip.subcomponents.container
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipIconPosiion
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipSize
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipState
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipType
+
+@Composable
+fun PokitChipContainer(
+ iconPosiion: PokitChipIconPosiion?,
+ state: PokitChipState,
+ size: PokitChipSize,
+ type: PokitChipType,
+ modifier: Modifier = Modifier,
+ onClick: (() -> Unit)? = null,
+ content: @Composable RowScope.() -> Unit,
+) {
+ val space = getItemSpace(size)
+
+ Row(
+ modifier = Modifier
+ .pokitChipContainerModifier(
+ iconPosition = iconPosiion,
+ state = state,
+ size = size,
+ type = type,
+ onClick = onClick
+ )
+ .then(modifier),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement
+ .spacedBy(
+ space = space,
+ alignment = Alignment.CenterHorizontally
+ )
+ ) {
+ content()
+ }
+}
+
+private fun getItemSpace(size: PokitChipSize): Dp {
+ return when (size) {
+ PokitChipSize.SMALL -> 4.dp
+ PokitChipSize.MEDIUM -> 0.dp
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/container/PokitChipContainerModifier.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/container/PokitChipContainerModifier.kt
new file mode 100644
index 00000000..50788d35
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/container/PokitChipContainerModifier.kt
@@ -0,0 +1,147 @@
+package pokitmons.pokit.core.ui.components.atom.chip.subcomponents.container
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipIconPosiion
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipSize
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipState
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun Modifier.pokitChipContainerModifier(
+ iconPosition: PokitChipIconPosiion?,
+ state: PokitChipState,
+ size: PokitChipSize,
+ type: PokitChipType,
+ onClick: (() -> Unit)? = null,
+): Modifier {
+ val backgroundColor = getBackgroundColor(state = state, type = type)
+ val strokeColor = getStrokeColor(state = state, type = type)
+ val padding = getPadding(iconPosition = iconPosition, size = size)
+
+ return this then Modifier
+ .clip(
+ shape = RoundedCornerShape(9999.dp)
+ )
+ .clickable(
+ enabled = (onClick != null && state != PokitChipState.DISABLED),
+ onClick = onClick ?: {},
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null
+ )
+ .background(
+ shape = RoundedCornerShape(9999.dp),
+ color = backgroundColor
+ )
+ .border(
+ shape = RoundedCornerShape(9999.dp),
+ width = 1.dp,
+ color = strokeColor
+ )
+ .padding(
+ paddingValues = padding
+ )
+}
+
+@Composable
+private fun getBackgroundColor(
+ state: PokitChipState,
+ type: PokitChipType,
+): Color {
+ return when {
+ state == PokitChipState.DEFAULT -> {
+ PokitTheme.colors.backgroundBase
+ }
+
+ state == PokitChipState.DISABLED -> {
+ PokitTheme.colors.backgroundDisable
+ }
+
+ state == PokitChipState.STROKE -> {
+ PokitTheme.colors.backgroundBase
+ }
+
+ state == PokitChipState.FILLED && type == PokitChipType.PRIMARY -> {
+ PokitTheme.colors.brand
+ }
+
+ state == PokitChipState.FILLED && type == PokitChipType.SECONDARY -> {
+ PokitTheme.colors.backgroundTertiary
+ }
+
+ else -> {
+ PokitTheme.colors.backgroundBase
+ }
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ state: PokitChipState,
+ type: PokitChipType,
+): Color {
+ return when {
+ state == PokitChipState.DEFAULT -> {
+ PokitTheme.colors.borderSecondary
+ }
+
+ state == PokitChipState.DISABLED -> {
+ Color.Unspecified
+ }
+
+ state == PokitChipState.FILLED -> {
+ Color.Unspecified
+ }
+
+ state == PokitChipState.STROKE && type == PokitChipType.PRIMARY -> {
+ PokitTheme.colors.brand
+ }
+
+ state == PokitChipState.STROKE && type == PokitChipType.SECONDARY -> {
+ PokitTheme.colors.borderPrimary
+ }
+
+ else -> {
+ PokitTheme.colors.borderSecondary
+ }
+ }
+}
+
+private fun getPadding(
+ iconPosition: PokitChipIconPosiion?,
+ size: PokitChipSize,
+): PaddingValues {
+ return when (size) {
+ PokitChipSize.SMALL -> {
+ if (iconPosition == PokitChipIconPosiion.LEFT) {
+ PaddingValues(start = 8.dp, end = 12.dp, top = 8.dp, bottom = 8.dp)
+ } else if (iconPosition == PokitChipIconPosiion.RIGHT) {
+ PaddingValues(start = 12.dp, end = 8.dp, top = 8.dp, bottom = 8.dp)
+ } else {
+ PaddingValues(all = 8.dp)
+ }
+ }
+
+ PokitChipSize.MEDIUM -> {
+ if (iconPosition == PokitChipIconPosiion.LEFT) {
+ PaddingValues(start = 12.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
+ } else if (iconPosition == PokitChipIconPosiion.RIGHT) {
+ PaddingValues(start = 16.dp, end = 12.dp, top = 8.dp, bottom = 8.dp)
+ } else {
+ PaddingValues(horizontal = 16.dp, vertical = 8.dp)
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/icon/PokitChipIcon.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/icon/PokitChipIcon.kt
new file mode 100644
index 00000000..b54da0af
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/icon/PokitChipIcon.kt
@@ -0,0 +1,63 @@
+package pokitmons.pokit.core.ui.components.atom.chip.subcomponents.icon
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipSize
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitChipIcon(
+ state: PokitChipState,
+ size: PokitChipSize,
+ onClick: () -> Unit,
+) {
+ val iconTintColor = getIconTintColor(state = state)
+ val iconSize = getIconSize(size = size)
+
+ Icon(
+ painter = painterResource(id = R.drawable.icon_24_x),
+ contentDescription = null,
+ tint = iconTintColor,
+ modifier = Modifier
+ .size(iconSize)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClick,
+ enabled = (state != PokitChipState.DISABLED)
+ )
+ )
+}
+
+@Composable
+private fun getIconTintColor(
+ state: PokitChipState,
+): Color {
+ return when (state) {
+ PokitChipState.DEFAULT -> PokitTheme.colors.iconSecondary
+ PokitChipState.FILLED -> PokitTheme.colors.inverseWh
+ PokitChipState.STROKE -> PokitTheme.colors.iconPrimary
+ PokitChipState.DISABLED -> PokitTheme.colors.iconDisable
+ }
+}
+
+@Composable
+private fun getIconSize(
+ size: PokitChipSize,
+): Dp {
+ return when (size) {
+ PokitChipSize.SMALL -> 16.dp
+ PokitChipSize.MEDIUM -> 22.dp
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/text/PokitChipText.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/text/PokitChipText.kt
new file mode 100644
index 00000000..3a4c65a6
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/chip/subcomponents/text/PokitChipText.kt
@@ -0,0 +1,43 @@
+package pokitmons.pokit.core.ui.components.atom.chip.subcomponents.text
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipSize
+import pokitmons.pokit.core.ui.components.atom.chip.attributes.PokitChipState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitChipText(
+ text: String,
+ state: PokitChipState,
+ size: PokitChipSize,
+) {
+ val textStyle = getTextStyle(size = size)
+ val textColor = getTextColor(state = state)
+
+ Text(text = text, style = textStyle.copy(color = textColor))
+}
+
+@Composable
+private fun getTextColor(
+ state: PokitChipState,
+): Color {
+ return when (state) {
+ PokitChipState.DEFAULT -> PokitTheme.colors.textTertiary
+ PokitChipState.FILLED -> PokitTheme.colors.inverseWh
+ PokitChipState.STROKE -> PokitTheme.colors.textPrimary
+ PokitChipState.DISABLED -> PokitTheme.colors.textDisable
+ }
+}
+
+@Composable
+private fun getTextStyle(
+ size: PokitChipSize,
+): TextStyle {
+ return when (size) {
+ PokitChipSize.SMALL -> PokitTheme.typography.label3Regular
+ PokitChipSize.MEDIUM -> PokitTheme.typography.label2Regular
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/PokitInput.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/PokitInput.kt
new file mode 100644
index 00000000..79e24e41
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/PokitInput.kt
@@ -0,0 +1,154 @@
+package pokitmons.pokit.core.ui.components.atom.input
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputIcon
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputIconPosition
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputShape
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
+import pokitmons.pokit.core.ui.components.atom.input.subcomponents.container.PokitInputContainer
+import pokitmons.pokit.core.ui.components.atom.input.subcomponents.icon.PokitInputIcon
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.conditional
+
+@Composable
+fun PokitInput(
+ text: String,
+ hintText: String,
+ onChangeText: (String) -> Unit,
+ icon: PokitInputIcon?,
+ modifier: Modifier = Modifier,
+ onClickIcon: (() -> Unit)? = null,
+ shape: PokitInputShape = PokitInputShape.RECTANGLE,
+ readOnly: Boolean = false,
+ enable: Boolean = true,
+ isError: Boolean = false,
+ keyboardActions: KeyboardActions = KeyboardActions.Default,
+ focusRequester: FocusRequester? = null,
+) {
+ var focused by remember { mutableStateOf(false) }
+ val state = remember(focused, isError, readOnly, enable) {
+ getState(
+ enabled = enable,
+ readOnly = readOnly,
+ focused = focused,
+ error = isError,
+ text = text
+ )
+ }
+ val textColor = getTextColor(state = state)
+ val textStyle = PokitTheme.typography.body3Medium.copy(color = textColor)
+
+ BasicTextField(
+ value = text,
+ onValueChange = onChangeText,
+ textStyle = textStyle,
+ enabled = (enable && !readOnly),
+ singleLine = true,
+ modifier = Modifier
+ .onFocusChanged { focusState ->
+ focused = focusState.isFocused
+ }
+ .conditional(focusRequester != null) {
+ focusRequester(focusRequester!!)
+ },
+ keyboardActions = keyboardActions,
+ decorationBox = { innerTextField ->
+ PokitInputContainer(
+ iconPosition = icon?.position,
+ modifier = modifier,
+ shape = shape,
+ state = state
+ ) {
+ if (icon == null) {
+ Box(modifier = Modifier.height(24.dp)) // 아이콘 없을 떄 공간 맞추기용
+ }
+
+ if (icon?.position == PokitInputIconPosition.LEFT) {
+ PokitInputIcon(state = state, resourceId = icon.resourceId, onClick = onClickIcon)
+ Box(modifier = Modifier.width(8.dp))
+ }
+
+ if (text.isEmpty() && !focused) {
+ Text(text = hintText, style = textStyle)
+ }
+
+ Box(modifier = Modifier.weight(1f)) {
+ innerTextField()
+ }
+
+ if (icon?.position == PokitInputIconPosition.RIGHT) {
+ PokitInputIcon(state = state, resourceId = icon.resourceId, onClick = onClickIcon)
+ }
+ }
+ }
+ )
+}
+
+@Composable
+private fun getTextColor(
+ state: PokitInputState,
+): Color {
+ return when (state) {
+ PokitInputState.DEFAULT -> PokitTheme.colors.textTertiary
+
+ PokitInputState.INPUT -> PokitTheme.colors.textSecondary
+
+ PokitInputState.ACTIVE -> PokitTheme.colors.textSecondary
+
+ PokitInputState.DISABLE -> PokitTheme.colors.textDisable
+
+ PokitInputState.READ_ONLY -> PokitTheme.colors.textTertiary
+
+ PokitInputState.ERROR -> PokitTheme.colors.textSecondary
+ }
+}
+
+private fun getState(
+ enabled: Boolean,
+ readOnly: Boolean,
+ focused: Boolean,
+ error: Boolean,
+ text: String,
+): PokitInputState {
+ return when {
+ !enabled -> {
+ PokitInputState.DISABLE
+ }
+
+ readOnly -> {
+ PokitInputState.READ_ONLY
+ }
+
+ focused -> {
+ PokitInputState.ACTIVE
+ }
+
+ error -> {
+ PokitInputState.ERROR
+ }
+
+ text.isEmpty() -> {
+ PokitInputState.DEFAULT
+ }
+
+ else -> {
+ PokitInputState.INPUT
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/Preview.kt
new file mode 100644
index 00000000..aa576dba
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/Preview.kt
@@ -0,0 +1,63 @@
+package pokitmons.pokit.core.ui.components.atom.input
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputIcon
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputIconPosition
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputShape
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitInputPreview() {
+ val scrollState = rememberScrollState()
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ enumValues().forEach { shape ->
+ for (isError in arrayOf(false, true)) {
+ for (enabled in arrayOf(false, true)) {
+ for (readOnly in arrayOf(false, true)) {
+ enumValues().forEach { iconPosition ->
+ PokitInput(
+ text = "",
+ hintText = "error:$isError, enabled:$enabled, readOnly:$readOnly",
+ onChangeText = {},
+ shape = shape,
+ enable = enabled,
+ readOnly = readOnly,
+ isError = isError,
+ icon = PokitInputIcon(iconPosition, R.drawable.icon_24_search)
+ )
+ }
+ PokitInput(
+ text = "val",
+ hintText = "error:$isError, enabled:$enabled, readOnly:$readOnly",
+ onChangeText = {},
+ shape = shape,
+ enable = enabled,
+ readOnly = readOnly,
+ isError = isError,
+ icon = null
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/attributes/InputAttributes.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/attributes/InputAttributes.kt
new file mode 100644
index 00000000..ed160427
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/attributes/InputAttributes.kt
@@ -0,0 +1,20 @@
+package pokitmons.pokit.core.ui.components.atom.input.attributes
+
+import pokitmons.pokit.core.ui.R
+
+enum class PokitInputShape {
+ ROUND, RECTANGLE,
+}
+
+internal enum class PokitInputState {
+ DEFAULT, INPUT, ACTIVE, DISABLE, READ_ONLY, ERROR,
+}
+
+data class PokitInputIcon(
+ val position: PokitInputIconPosition,
+ val resourceId: Int = R.drawable.icon_24_search,
+)
+
+enum class PokitInputIconPosition {
+ RIGHT, LEFT,
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/container/PokitInputContainer.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/container/PokitInputContainer.kt
new file mode 100644
index 00000000..ea8cff2b
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/container/PokitInputContainer.kt
@@ -0,0 +1,32 @@
+package pokitmons.pokit.core.ui.components.atom.input.subcomponents.container
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputIconPosition
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputShape
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
+
+@Composable
+internal fun PokitInputContainer(
+ iconPosition: PokitInputIconPosition?,
+ modifier: Modifier = Modifier,
+ shape: PokitInputShape = PokitInputShape.RECTANGLE,
+ state: PokitInputState = PokitInputState.DEFAULT,
+ content: @Composable RowScope.() -> Unit,
+) {
+ Row(
+ modifier = Modifier
+ .pokitInputContainerModifier(
+ iconPosition = iconPosition,
+ shape = shape,
+ state = state
+ )
+ .then(modifier),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ content()
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/container/PokitInputContainerModifier.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/container/PokitInputContainerModifier.kt
new file mode 100644
index 00000000..45987e4b
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/container/PokitInputContainerModifier.kt
@@ -0,0 +1,132 @@
+package pokitmons.pokit.core.ui.components.atom.input.subcomponents.container
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputIconPosition
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputShape
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun Modifier.pokitInputContainerModifier(
+ iconPosition: PokitInputIconPosition?,
+ shape: PokitInputShape,
+ state: PokitInputState,
+): Modifier {
+ val inputContainerShape = getShape(shape = shape)
+ val padding = getPadding(shape = shape, iconPosition = iconPosition)
+ val backgroundColor = getBackgroundColor(state = state)
+ val strokeColor = getStrokeColor(state = state)
+
+ return this then Modifier
+ .clip(
+ shape = inputContainerShape
+ )
+ .background(
+ shape = inputContainerShape,
+ color = backgroundColor
+ )
+ .border(
+ shape = inputContainerShape,
+ width = 1.dp,
+ color = strokeColor
+ )
+ .padding(
+ paddingValues = padding
+ )
+}
+
+private fun getShape(
+ shape: PokitInputShape,
+): Shape {
+ return when (shape) {
+ PokitInputShape.RECTANGLE -> RoundedCornerShape(8.dp)
+ PokitInputShape.ROUND -> RoundedCornerShape(9999.dp)
+ }
+}
+
+private fun getPadding(
+ shape: PokitInputShape,
+ iconPosition: PokitInputIconPosition?,
+): PaddingValues {
+ val verticalPadding = getVerticalPadding(shape = shape)
+
+ return when {
+ shape == PokitInputShape.RECTANGLE -> {
+ PaddingValues(horizontal = 12.dp, vertical = verticalPadding)
+ }
+
+ shape == PokitInputShape.ROUND && iconPosition == null -> {
+ PaddingValues(horizontal = 20.dp, vertical = verticalPadding)
+ }
+
+ shape == PokitInputShape.ROUND && iconPosition == PokitInputIconPosition.LEFT -> {
+ PaddingValues(horizontal = 16.dp, vertical = verticalPadding)
+ }
+
+ else -> {
+ PaddingValues(start = 20.dp, end = 16.dp, top = verticalPadding, bottom = verticalPadding)
+ }
+ }
+}
+
+private fun getVerticalPadding(
+ shape: PokitInputShape,
+): Dp {
+ return when (shape) {
+ PokitInputShape.ROUND -> {
+ 8.dp
+ }
+ PokitInputShape.RECTANGLE -> {
+ 13.dp
+ }
+ }
+}
+
+@Composable
+private fun getBackgroundColor(
+ state: PokitInputState,
+): Color {
+ return when (state) {
+ PokitInputState.DEFAULT -> PokitTheme.colors.backgroundPrimary
+
+ PokitInputState.INPUT -> PokitTheme.colors.backgroundPrimary
+
+ PokitInputState.ACTIVE -> PokitTheme.colors.backgroundBase
+
+ PokitInputState.DISABLE -> PokitTheme.colors.backgroundDisable
+
+ PokitInputState.READ_ONLY -> PokitTheme.colors.backgroundSecondary
+
+ PokitInputState.ERROR -> PokitTheme.colors.backgroundBase
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ state: PokitInputState,
+): Color {
+ return when (state) {
+ PokitInputState.DEFAULT -> Color.Unspecified
+
+ PokitInputState.INPUT -> Color.Unspecified
+
+ PokitInputState.ACTIVE -> PokitTheme.colors.brand
+
+ PokitInputState.DISABLE -> PokitTheme.colors.borderDisable
+
+ PokitInputState.READ_ONLY -> PokitTheme.colors.borderSecondary
+
+ PokitInputState.ERROR -> PokitTheme.colors.error
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/icon/PokitInputIcon.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/icon/PokitInputIcon.kt
new file mode 100644
index 00000000..0dec8891
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/input/subcomponents/icon/PokitInputIcon.kt
@@ -0,0 +1,57 @@
+package pokitmons.pokit.core.ui.components.atom.input.subcomponents.icon
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun PokitInputIcon(
+ state: PokitInputState,
+ resourceId: Int,
+ onClick: (() -> Unit)? = null,
+) {
+ val iconColor = getColor(state = state)
+
+ Icon(
+ painter = painterResource(id = resourceId),
+ contentDescription = null,
+ tint = iconColor,
+ modifier = Modifier.size(24.dp).then(
+ other = onClick?.let { method ->
+ Modifier.clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = method
+ )
+ } ?: Modifier
+ )
+ )
+}
+
+@Composable
+private fun getColor(
+ state: PokitInputState,
+): Color {
+ return when (state) {
+ PokitInputState.DEFAULT -> PokitTheme.colors.iconSecondary
+
+ PokitInputState.INPUT -> PokitTheme.colors.iconPrimary
+
+ PokitInputState.ACTIVE -> PokitTheme.colors.iconPrimary
+
+ PokitInputState.DISABLE -> PokitTheme.colors.iconDisable
+
+ PokitInputState.READ_ONLY -> PokitTheme.colors.iconSecondary
+
+ PokitInputState.ERROR -> PokitTheme.colors.error
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/PokitInputArea.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/PokitInputArea.kt
new file mode 100644
index 00000000..717374f8
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/PokitInputArea.kt
@@ -0,0 +1,118 @@
+package pokitmons.pokit.core.ui.components.atom.inputarea
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import pokitmons.pokit.core.ui.components.atom.inputarea.attributes.PokitInputAreaState
+import pokitmons.pokit.core.ui.components.atom.inputarea.subcomponents.PokitInputAreaContainer
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitInputArea(
+ text: String,
+ hintText: String,
+ onChangeText: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ readOnly: Boolean = false,
+ enable: Boolean = true,
+ isError: Boolean = false,
+) {
+ var focused by remember { mutableStateOf(false) }
+ val state = remember(focused, isError, readOnly, enable) {
+ getState(
+ enabled = enable,
+ readOnly = readOnly,
+ focused = focused,
+ error = isError,
+ text = text
+ )
+ }
+ val textColor = getTextColor(state = state)
+ val textStyle = PokitTheme.typography.body3Regular.copy(color = textColor)
+
+ BasicTextField(
+ value = text,
+ onValueChange = onChangeText,
+ textStyle = textStyle,
+ enabled = (enable && !readOnly),
+ minLines = 5,
+ modifier = Modifier.onFocusChanged { focusState ->
+ focused = focusState.isFocused
+ },
+ decorationBox = { innerTextField ->
+ PokitInputAreaContainer(
+ state = state,
+ modifier = modifier
+ ) {
+ if (text.isEmpty() && !focused) {
+ Text(text = hintText, style = textStyle)
+ }
+
+ Box(modifier = Modifier.weight(1f)) {
+ innerTextField()
+ }
+ }
+ }
+ )
+}
+
+@Composable
+private fun getTextColor(
+ state: PokitInputAreaState,
+): Color {
+ return when (state) {
+ PokitInputAreaState.DEFAULT -> PokitTheme.colors.textTertiary
+
+ PokitInputAreaState.INPUT -> PokitTheme.colors.textSecondary
+
+ PokitInputAreaState.ACTIVE -> PokitTheme.colors.textSecondary
+
+ PokitInputAreaState.DISABLE -> PokitTheme.colors.textDisable
+
+ PokitInputAreaState.READ_ONLY -> PokitTheme.colors.textTertiary
+
+ PokitInputAreaState.ERROR -> PokitTheme.colors.textSecondary
+ }
+}
+
+private fun getState(
+ enabled: Boolean,
+ readOnly: Boolean,
+ focused: Boolean,
+ error: Boolean,
+ text: String,
+): PokitInputAreaState {
+ return when {
+ !enabled -> {
+ PokitInputAreaState.DISABLE
+ }
+
+ readOnly -> {
+ PokitInputAreaState.READ_ONLY
+ }
+
+ focused -> {
+ PokitInputAreaState.ACTIVE
+ }
+
+ error -> {
+ PokitInputAreaState.ERROR
+ }
+
+ text.isEmpty() -> {
+ PokitInputAreaState.DEFAULT
+ }
+
+ else -> {
+ PokitInputAreaState.INPUT
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/Preview.kt
new file mode 100644
index 00000000..856a0467
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/Preview.kt
@@ -0,0 +1,45 @@
+package pokitmons.pokit.core.ui.components.atom.inputarea
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitInputAreaPreview() {
+ val scrollState = rememberScrollState()
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ for (isError in arrayOf(false, true)) {
+ for (enabled in arrayOf(false, true)) {
+ for (readOnly in arrayOf(false, true)) {
+ PokitInputArea(
+ text = "text",
+ hintText = "내용을 입력해주세요",
+ onChangeText = {},
+ enable = enabled,
+ readOnly = readOnly,
+ isError = isError,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/attributes/PokitInputAreaState.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/attributes/PokitInputAreaState.kt
new file mode 100644
index 00000000..e1b9a57a
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/attributes/PokitInputAreaState.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.core.ui.components.atom.inputarea.attributes
+
+enum class PokitInputAreaState {
+ DEFAULT, INPUT, ACTIVE, DISABLE, READ_ONLY, ERROR,
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/subcomponents/PokitInputContainer.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/subcomponents/PokitInputContainer.kt
new file mode 100644
index 00000000..8f67aeb4
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/subcomponents/PokitInputContainer.kt
@@ -0,0 +1,24 @@
+package pokitmons.pokit.core.ui.components.atom.inputarea.subcomponents
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import pokitmons.pokit.core.ui.components.atom.inputarea.attributes.PokitInputAreaState
+
+@Composable
+internal fun PokitInputAreaContainer(
+ modifier: Modifier = Modifier,
+ state: PokitInputAreaState = PokitInputAreaState.DEFAULT,
+ content: @Composable RowScope.() -> Unit,
+) {
+ Row(
+ modifier = Modifier
+ .pokitInputAreaContainerModifier(
+ state = state
+ )
+ .then(modifier)
+ ) {
+ content()
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/subcomponents/PokitInputContainerModifier.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/subcomponents/PokitInputContainerModifier.kt
new file mode 100644
index 00000000..21eba74e
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/inputarea/subcomponents/PokitInputContainerModifier.kt
@@ -0,0 +1,78 @@
+package pokitmons.pokit.core.ui.components.atom.inputarea.subcomponents
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.inputarea.attributes.PokitInputAreaState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun Modifier.pokitInputAreaContainerModifier(
+ state: PokitInputAreaState,
+): Modifier {
+ val inputContainerShape = RoundedCornerShape(8.dp)
+ val backgroundColor = getBackgroundColor(state = state)
+ val strokeColor = getStrokeColor(state = state)
+
+ return this then Modifier
+ .clip(
+ shape = inputContainerShape
+ )
+ .background(
+ shape = inputContainerShape,
+ color = backgroundColor
+ )
+ .border(
+ shape = inputContainerShape,
+ width = 1.dp,
+ color = strokeColor
+ )
+ .padding(
+ paddingValues = PaddingValues(16.dp)
+ )
+}
+
+@Composable
+private fun getBackgroundColor(
+ state: PokitInputAreaState,
+): Color {
+ return when (state) {
+ PokitInputAreaState.DEFAULT -> PokitTheme.colors.backgroundBase
+
+ PokitInputAreaState.INPUT -> PokitTheme.colors.backgroundBase
+
+ PokitInputAreaState.ACTIVE -> PokitTheme.colors.backgroundBase
+
+ PokitInputAreaState.DISABLE -> PokitTheme.colors.backgroundDisable
+
+ PokitInputAreaState.READ_ONLY -> PokitTheme.colors.backgroundSecondary
+
+ PokitInputAreaState.ERROR -> PokitTheme.colors.backgroundBase
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ state: PokitInputAreaState,
+): Color {
+ return when (state) {
+ PokitInputAreaState.DEFAULT -> PokitTheme.colors.borderSecondary
+
+ PokitInputAreaState.INPUT -> PokitTheme.colors.borderSecondary
+
+ PokitInputAreaState.ACTIVE -> PokitTheme.colors.brand
+
+ PokitInputAreaState.DISABLE -> PokitTheme.colors.borderDisable
+
+ PokitInputAreaState.READ_ONLY -> PokitTheme.colors.borderSecondary
+
+ PokitInputAreaState.ERROR -> PokitTheme.colors.error
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/loading/LoadingProgress.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/loading/LoadingProgress.kt
new file mode 100644
index 00000000..1e832cb3
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/loading/LoadingProgress.kt
@@ -0,0 +1,24 @@
+package pokitmons.pokit.core.ui.components.atom.loading
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun LoadingProgress(modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier.width(64.dp),
+ color = PokitTheme.colors.brand,
+ trackColor = PokitTheme.colors.borderTertiary
+ )
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/loginbutton/PokitLoginButton.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/loginbutton/PokitLoginButton.kt
new file mode 100644
index 00000000..a42618f9
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/atom/loginbutton/PokitLoginButton.kt
@@ -0,0 +1,86 @@
+package pokitmons.pokit.core.ui.components.atom.loginbutton
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Surface
+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.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitLoginButtonType
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitLoginResource
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitLoginButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+ loginType: PokitLoginButtonType,
+ text: String,
+) {
+ val loginResource: PokitLoginResource = getLoginResource(loginType)
+
+ Surface(
+ modifier = Modifier
+ .height(50.dp)
+ .border(
+ shape = RoundedCornerShape(8.dp),
+ width = 1.dp,
+ color = loginResource.borderColor
+ ),
+ shape = RoundedCornerShape(8.dp),
+ color = loginResource.backgroundColor,
+ onClick = onClick
+ ) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Icon(
+ modifier = Modifier
+ .size(24.dp),
+ painter = painterResource(id = loginResource.iconResourceId),
+ tint = loginResource.iconTintColor,
+ contentDescription = null
+ )
+ Text(
+ modifier = Modifier
+ .padding(start = 12.dp),
+ text = text,
+ color = loginResource.textColor,
+ style = PokitTheme.typography.label1Regular
+ )
+ }
+ }
+}
+
+@Composable
+private fun getLoginResource(loginType: PokitLoginButtonType): PokitLoginResource {
+ return when (loginType) {
+ PokitLoginButtonType.APPLE -> PokitLoginResource(
+ iconResourceId = R.drawable.icon_24_apple,
+ iconTintColor = PokitTheme.colors.inverseWh,
+ textColor = PokitTheme.colors.inverseWh,
+ backgroundColor = PokitTheme.colors.backgroundTertiary,
+ borderColor = PokitTheme.colors.backgroundTertiary
+ )
+
+ PokitLoginButtonType.GOOGLE -> PokitLoginResource(
+ iconResourceId = R.drawable.icon_24_google,
+ textColor = PokitTheme.colors.textPrimary,
+ backgroundColor = PokitTheme.colors.backgroundBase,
+ borderColor = PokitTheme.colors.borderSecondary
+ )
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/labeledinput/LabeledInput.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/labeledinput/LabeledInput.kt
new file mode 100644
index 00000000..76934443
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/labeledinput/LabeledInput.kt
@@ -0,0 +1,151 @@
+package pokitmons.pokit.core.ui.components.block.labeledinput
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.input.PokitInput
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun LabeledInput(
+ label: String,
+ inputText: String,
+ hintText: String,
+ onChangeText: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ sub: String = "",
+ maxLength: Int? = null,
+ readOnly: Boolean = false,
+ enable: Boolean = true,
+ isError: Boolean = false,
+) {
+ var focused by remember { mutableStateOf(false) }
+ val state = remember(focused, isError, readOnly, enable) {
+ getState(
+ enabled = enable,
+ readOnly = readOnly,
+ focused = focused,
+ error = isError,
+ text = inputText
+ )
+ }
+ val subTextColor = getSubTextColor(state)
+
+ Column(
+ modifier = modifier
+ .onFocusChanged { focusState ->
+ focused = focusState.hasFocus
+ }
+ ) {
+ Text(
+ text = label,
+ style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ PokitInput(
+ text = inputText,
+ hintText = hintText,
+ onChangeText = onChangeText,
+ icon = null,
+ isError = isError,
+ enable = enable,
+ readOnly = readOnly
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (state == PokitInputState.ERROR) {
+ Icon(
+ painter = painterResource(id = R.drawable.icon_24_info),
+ contentDescription = null,
+ modifier = Modifier.size(20.dp),
+ tint = PokitTheme.colors.error
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+ }
+
+ Text(
+ text = sub,
+ style = PokitTheme.typography.detail1.copy(color = subTextColor)
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ if (maxLength != null) {
+ Text(
+ text = "${inputText.length}/$maxLength",
+ style = PokitTheme.typography.detail1.copy(color = subTextColor)
+ )
+ }
+ }
+ }
+}
+
+private fun getState(
+ enabled: Boolean,
+ readOnly: Boolean,
+ focused: Boolean,
+ error: Boolean,
+ text: String,
+): PokitInputState {
+ return when {
+ !enabled -> {
+ PokitInputState.DISABLE
+ }
+
+ readOnly -> {
+ PokitInputState.READ_ONLY
+ }
+
+ focused -> {
+ PokitInputState.ACTIVE
+ }
+
+ error -> {
+ PokitInputState.ERROR
+ }
+
+ text.isEmpty() -> {
+ PokitInputState.DEFAULT
+ }
+
+ else -> {
+ PokitInputState.INPUT
+ }
+ }
+}
+
+@Composable
+private fun getSubTextColor(state: PokitInputState): Color {
+ return when (state) {
+ PokitInputState.ERROR -> PokitTheme.colors.error
+ PokitInputState.DISABLE -> PokitTheme.colors.textDisable
+ else -> PokitTheme.colors.textTertiary
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/labeledinput/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/labeledinput/Preview.kt
new file mode 100644
index 00000000..ee2620dc
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/labeledinput/Preview.kt
@@ -0,0 +1,34 @@
+package pokitmons.pokit.core.ui.components.block.labeledinput
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitInputPreview() {
+ val scrollState = rememberScrollState()
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ enumValues().forEach { state ->
+ LabeledInput(label = "Label", sub = "내용을 입력해주세요", maxLength = 10, inputText = "으앙", hintText = "내용을 입력해주세요", onChangeText = {})
+ LabeledInput(label = "Label", sub = "", maxLength = null, inputText = "으앙", hintText = "내용을 입력해주세요", onChangeText = {})
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkcard/LinkCard.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkcard/LinkCard.kt
new file mode 100644
index 00000000..99d7c89f
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkcard/LinkCard.kt
@@ -0,0 +1,156 @@
+package pokitmons.pokit.core.ui.components.block.linkcard
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+fun LinkCard(
+ item: T,
+ title: String,
+ sub: String,
+ painter: Painter,
+ notRead: Boolean,
+ badgeText: String?,
+ onClickKebab: (T) -> Unit,
+ onClickItem: (T) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier = modifier) {
+ Row(
+ modifier = Modifier
+ .clip(RoundedCornerShape(8.dp))
+ .noRippleClickable { onClickItem(item) }
+ ) {
+ Box(
+ modifier = Modifier
+ .height(94.dp)
+ .width(124.dp)
+ ) {
+ Image(
+ painter = painter,
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(RoundedCornerShape(8.dp))
+ .background(Color.Gray),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .heightIn(min = 94.dp)
+ .padding(vertical = 3.dp),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ modifier = Modifier.weight(1f),
+ text = title,
+ style = PokitTheme.typography.body3Medium.copy(color = PokitTheme.colors.textPrimary),
+ maxLines = 2,
+ minLines = 2,
+ overflow = TextOverflow.Ellipsis
+ )
+
+ IconButton(
+ onClick = { onClickKebab(item) },
+ modifier = Modifier.size(24.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.icon_24_kebab),
+ contentDescription = null
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = sub,
+ style = PokitTheme.typography.detail2.copy(color = PokitTheme.colors.textTertiary),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(6.dp)
+ ) {
+ badgeText?.let { badge ->
+ Text(
+ text = badge,
+ modifier = Modifier
+ .background(
+ color = PokitTheme.colors.backgroundPrimary,
+ shape = RoundedCornerShape(4.dp)
+ )
+ .padding(horizontal = 8.dp, vertical = 4.dp),
+ style = PokitTheme.typography.label4.copy(color = PokitTheme.colors.textTertiary)
+ )
+ }
+
+ if (notRead) {
+ Text(
+ text = stringResource(id = R.string.not_read),
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = PokitTheme.colors.borderTertiary,
+ shape = RoundedCornerShape(4.dp)
+ )
+ .background(
+ color = PokitTheme.colors.backgroundBase,
+ shape = RoundedCornerShape(4.dp)
+ )
+ .padding(horizontal = 8.dp, vertical = 4.dp),
+ style = PokitTheme.typography.label4.copy(color = PokitTheme.colors.textTertiary)
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkcard/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkcard/Preview.kt
new file mode 100644
index 00000000..68b8616b
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkcard/Preview.kt
@@ -0,0 +1,51 @@
+package pokitmons.pokit.core.ui.components.block.linkcard
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun LinkCardPreview() {
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.LightGray)
+ .padding(12.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ LinkCard(
+ title = "타이틀\n컴포스는 왜 이런가",
+ sub = "2024.06.25. youtube.comyoutube.comyoutube.comyoutube",
+ badgeText = "텍스트",
+ painter = painterResource(id = R.drawable.icon_24_link),
+ notRead = true,
+ item = 3,
+ onClickKebab = { value: Int -> },
+ onClickItem = { value: Int -> }
+ )
+
+ LinkCard(
+ title = "동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세",
+ sub = "2024.06.25. youtube.com",
+ badgeText = "텍스트",
+ painter = painterResource(id = R.drawable.icon_24_link),
+ notRead = true,
+ item = 3,
+ onClickKebab = { value: Int -> },
+ onClickItem = { value: Int -> }
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkurlcard/LinkUrlCard.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkurlcard/LinkUrlCard.kt
new file mode 100644
index 00000000..2e610797
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkurlcard/LinkUrlCard.kt
@@ -0,0 +1,81 @@
+package pokitmons.pokit.core.ui.components.block.linkurlcard
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+fun LinkUrlCard(
+ modifier: Modifier = Modifier,
+ thumbnailPainter: Painter,
+ url: String,
+ title: String,
+ openWebBrowserByClick: Boolean,
+) {
+ val uriHandler = LocalUriHandler.current
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(12.dp))
+ .height(IntrinsicSize.Min)
+ .noRippleClickable {
+ if (openWebBrowserByClick) {
+ uriHandler.openUri(url)
+ }
+ }
+ .border(
+ width = 1.dp,
+ color = PokitTheme.colors.borderTertiary,
+ shape = RoundedCornerShape(12.dp)
+ )
+ ) {
+ Image(
+ painter = thumbnailPainter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.width(124.dp).fillMaxHeight()
+ )
+
+ Column(
+ modifier = Modifier
+ .padding(start = 16.dp, end = 20.dp, top = 16.dp, bottom = 16.dp)
+ .weight(1f)
+ ) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = title,
+ maxLines = 2,
+ style = PokitTheme.typography.body3Medium.copy(color = PokitTheme.colors.textSecondary)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = url,
+ maxLines = 2,
+ style = PokitTheme.typography.detail2.copy(color = PokitTheme.colors.textTertiary)
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkurlcard/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkurlcard/Preview.kt
new file mode 100644
index 00000000..ebb1a9a6
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/linkurlcard/Preview.kt
@@ -0,0 +1,42 @@
+package pokitmons.pokit.core.ui.components.block.linkurlcard
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+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 pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun LinkUrlCardPreview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ LinkUrlCard(
+ thumbnailPainter = painterResource(id = R.drawable.icon_24_google),
+ url = "https://naver.com",
+ title = "네이버",
+ openWebBrowserByClick = false
+ )
+
+ LinkUrlCard(
+ modifier = Modifier.padding(20.dp),
+ thumbnailPainter = painterResource(id = R.drawable.icon_24_google),
+ url = "https://naver.com",
+ title = "네이버",
+ openWebBrowserByClick = false
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitcard/PokitCard.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitcard/PokitCard.kt
new file mode 100644
index 00000000..1d3a6038
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitcard/PokitCard.kt
@@ -0,0 +1,102 @@
+package pokitmons.pokit.core.ui.components.block.pokitcard
+
+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.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+fun PokitCard(
+ text: String,
+ linkCount: Int,
+ painter: Painter?,
+ onClick: () -> Unit,
+ onClickKebab: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(
+ modifier = modifier
+ .clip(
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(
+ color = PokitTheme.colors.backgroundPrimary,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .noRippleClickable { onClick() }
+ .padding(top = 12.dp, start = 12.dp, bottom = 8.dp, end = 8.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ modifier = Modifier.weight(1f),
+ text = text,
+ style = PokitTheme.typography.body1SemiBold.copy(color = PokitTheme.colors.textPrimary),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+
+ IconButton(
+ onClick = { onClickKebab() },
+ modifier = Modifier
+ .size(24.dp)
+ .align(Alignment.Top)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.icon_24_kebab),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Text(
+ text = stringResource(id = R.string.pokit_count_format, linkCount),
+ style = PokitTheme.typography.detail2.copy(color = PokitTheme.colors.textTertiary)
+ )
+
+ Spacer(modifier = Modifier.height(18.dp))
+
+ Row(
+ modifier = Modifier
+ .height(84.dp)
+ .fillMaxWidth(1f),
+ horizontalArrangement = Arrangement.End
+ ) {
+ if (painter != null) {
+ Image(
+ modifier = Modifier.size(84.dp),
+ contentScale = ContentScale.Crop,
+ painter = painter,
+ contentDescription = null
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitcard/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitcard/Preview.kt
new file mode 100644
index 00000000..0720267d
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitcard/Preview.kt
@@ -0,0 +1,48 @@
+package pokitmons.pokit.core.ui.components.block.pokitcard
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+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 pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitardPreview() {
+ PokitTheme {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PokitCard(
+ modifier = Modifier.weight(1f),
+ text = "요리/레시피",
+ linkCount = 10,
+ painter = null,
+ onClick = { },
+ onClickKebab = { }
+ )
+
+ PokitCard(
+ modifier = Modifier.weight(1f),
+ text = "요리/레시피",
+ linkCount = 5,
+ painter = painterResource(id = R.drawable.icon_24_google),
+ onClick = { },
+ onClickKebab = { }
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/PokitList.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/PokitList.kt
new file mode 100644
index 00000000..3830ee42
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/PokitList.kt
@@ -0,0 +1,86 @@
+package pokitmons.pokit.core.ui.components.block.pokitlist
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.block.pokitlist.attributes.PokitListState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitList(
+ item: T,
+ title: String,
+ sub: String,
+ onClickItem: (T) -> Unit,
+ modifier: Modifier = Modifier,
+ state: PokitListState = PokitListState.DEFAULT,
+) {
+ val titleTextColor = getTitleTextColor(state = state)
+ val subTextColor = getSubTextColor(state = state)
+
+ Row(
+ modifier = modifier
+ .clickable(
+ enabled = state != PokitListState.DISABLE,
+ onClick = { onClickItem(item) },
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ )
+ .padding(horizontal = 20.dp, vertical = 12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ Text(
+ text = title,
+ style = PokitTheme.typography.body1Bold.copy(color = titleTextColor),
+ overflow = TextOverflow.Ellipsis
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Text(
+ text = sub,
+ style = PokitTheme.typography.detail1.copy(color = subTextColor)
+ )
+ }
+ }
+}
+
+@Composable
+private fun getTitleTextColor(
+ state: PokitListState,
+): Color {
+ return when (state) {
+ PokitListState.DEFAULT -> PokitTheme.colors.textPrimary
+ PokitListState.ACTIVE -> PokitTheme.colors.textPrimary
+ PokitListState.DISABLE -> PokitTheme.colors.textDisable
+ }
+}
+
+@Composable
+private fun getSubTextColor(
+ state: PokitListState,
+): Color {
+ return when (state) {
+ PokitListState.DEFAULT -> PokitTheme.colors.textTertiary
+ PokitListState.ACTIVE -> PokitTheme.colors.textTertiary
+ PokitListState.DISABLE -> PokitTheme.colors.textDisable
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/Preview.kt
new file mode 100644
index 00000000..b245fcba
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/Preview.kt
@@ -0,0 +1,46 @@
+package pokitmons.pokit.core.ui.components.block.pokitlist
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.block.pokitlist.attributes.PokitListState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PokitListPreview() {
+ PokitTheme {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PokitList(
+ state = PokitListState.DISABLE,
+ item = "STRING",
+ title = "카테고리입니당",
+ sub = "15개 항목",
+ onClickItem = {}
+ )
+
+ PokitList(
+ state = PokitListState.DEFAULT,
+ item = "STRING",
+ title = "카테고리입니당",
+ sub = "15개 항목",
+ onClickItem = {}
+ )
+
+ PokitList(
+ state = PokitListState.ACTIVE,
+ item = "STRING",
+ title = "카테고리입니당",
+ sub = "15개 항목",
+ onClickItem = {}
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/attributes/PokitListState.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/attributes/PokitListState.kt
new file mode 100644
index 00000000..aab7815d
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokitlist/attributes/PokitListState.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.core.ui.components.block.pokitlist.attributes
+
+enum class PokitListState {
+ DEFAULT, ACTIVE, DISABLE
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt
new file mode 100644
index 00000000..494115cf
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/PokitToast.kt
@@ -0,0 +1,60 @@
+package pokitmons.pokit.core.ui.components.block.pokittoast
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitToast(
+ modifier: Modifier = Modifier,
+ text: String,
+ onClick: (() -> Unit)? = null,
+ onClickClose: () -> Unit = {},
+) {
+ Row(
+ modifier = modifier
+ .clip(RoundedCornerShape(9999.dp))
+ .background(PokitTheme.colors.backgroundTertiary)
+ .clickable(
+ enabled = onClick != null,
+ onClick = onClick ?: {}
+ )
+ .padding(start = 20.dp, end = 14.dp, top = 12.dp, bottom = 12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = text,
+ style = PokitTheme.typography.body3Medium.copy(color = PokitTheme.colors.inverseWh),
+ modifier = Modifier.weight(1f)
+ )
+
+ Spacer(modifier = Modifier.width(12.dp))
+
+ IconButton(
+ onClick = onClickClose,
+ modifier = Modifier.size(36.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.icon_24_x),
+ contentDescription = null,
+ tint = PokitTheme.colors.inverseWh
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/Preview.kt
new file mode 100644
index 00000000..663d3435
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pokittoast/Preview.kt
@@ -0,0 +1,28 @@
+package pokitmons.pokit.core.ui.components.block.pokittoast
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview() {
+ PokitTheme {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PokitToast(
+ modifier = Modifier.padding(20.dp),
+ text = "최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요.",
+ onClick = {}
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pushcard/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pushcard/Preview.kt
new file mode 100644
index 00000000..77effd63
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pushcard/Preview.kt
@@ -0,0 +1,58 @@
+package pokitmons.pokit.core.ui.components.block.pushcard
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+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 pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+fun PushCardPreview() {
+ PokitTheme {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PushCard(
+ title = "일단 글 링크의 타이틀은 조금 길수도 있으니까 아무렇게나 길게 적어봅니다",
+ sub = "내일 알림이 예정되어 있어요!\n잊지 말고 링크를 확인하세요!",
+ timeString = "1시간 전",
+ painter = null,
+ onClick = {},
+ read = false
+ )
+
+ PushCard(
+ title = "일단 글 링크의 타이틀은 조금 길수도 있으니까 아무렇게나 길게 적어봅니다",
+ sub = "내일 알림이 예정되어 있어요!\n잊지 말고 링크를 확인하세요!",
+ timeString = "1시간 전",
+ painter = null,
+ onClick = {},
+ read = true
+ )
+
+ PushCard(
+ title = "일단 글 링크의 타이틀은 조금 길수도 있으니까 아무렇게나 길게 적어봅니다",
+ sub = "내일 알림이 예정되어 있어요!\n잊지 말고 링크를 확인하세요!",
+ timeString = "1시간 전",
+ painter = painterResource(id = R.drawable.icon_24_google),
+ onClick = {},
+ read = false
+ )
+ PushCard(
+ title = "일단 글 링크의 타이틀은 조금 길수도 있으니까 아무렇게나 길게 적어봅니다",
+ sub = "내일 알림이 예정되어 있어요!\n잊지 말고 링크를 확인하세요!",
+ timeString = "1시간 전",
+ painter = painterResource(id = R.drawable.icon_24_google),
+ onClick = {},
+ read = true
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pushcard/PushCard.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pushcard/PushCard.kt
new file mode 100644
index 00000000..0621b518
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/pushcard/PushCard.kt
@@ -0,0 +1,94 @@
+package pokitmons.pokit.core.ui.components.block.pushcard
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+fun PushCard(
+ title: String,
+ sub: String,
+ timeString: String,
+ painter: Painter?,
+ read: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val titleTextColor = getTitleTextColor(read = read)
+
+ Row(
+ modifier = modifier
+ .noRippleClickable { onClick() }
+ .padding(all = 20.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (painter != null) {
+ Image(
+ modifier = Modifier
+ .width(94.dp)
+ .height(70.dp)
+ .clip(shape = RoundedCornerShape(2.dp)),
+ painter = painter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop
+ )
+ }
+
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ Text(
+ text = title,
+ style = PokitTheme.typography.body2Bold.copy(color = titleTextColor),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Text(
+ text = sub,
+ style = PokitTheme.typography.detail2.copy(color = PokitTheme.colors.textSecondary),
+ minLines = 2,
+ maxLines = 2
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = timeString,
+ style = PokitTheme.typography.detail2.copy(color = PokitTheme.colors.textTertiary)
+ )
+ }
+ }
+}
+
+@Composable
+private fun getTitleTextColor(
+ read: Boolean,
+): Color {
+ return if (read) {
+ PokitTheme.colors.textPrimary
+ } else {
+ PokitTheme.colors.textDisable
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/PokitSelect.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/PokitSelect.kt
new file mode 100644
index 00000000..557718b8
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/PokitSelect.kt
@@ -0,0 +1,136 @@
+package pokitmons.pokit.core.ui.components.block.select
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.block.select.attributes.PokitSelectState
+import pokitmons.pokit.core.ui.components.block.select.modifier.pokitSelectContentContainerModifier
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun PokitSelect(
+ text: String,
+ hintText: String,
+ label: String,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit = {},
+ readOnly: Boolean = false,
+ enable: Boolean = true,
+) {
+ val state = remember(readOnly, enable, text) {
+ getState(text = text, readOnly = readOnly, enable = enable)
+ }
+ val labelTextColor = getLabelTextColor(state = state)
+ val contentTextColor = getContentTextColor(state = state)
+ val iconTintColor = getIconTintColor(state = state)
+
+ Column(
+ modifier = modifier
+ ) {
+ Text(
+ text = label,
+ style = PokitTheme.typography.body2Medium.copy(color = labelTextColor)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Row(
+ modifier = Modifier
+ .pokitSelectContentContainerModifier(state)
+ .clip(RoundedCornerShape(8.dp))
+ .clickable(
+ onClick = onClick,
+ enabled = (state != PokitSelectState.DISABLE) && (state != PokitSelectState.READ_ONLY),
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ )
+ .padding(all = 12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = text.ifEmpty { hintText },
+ style = PokitTheme.typography.body3Medium.copy(contentTextColor),
+ modifier = Modifier.weight(1f)
+ )
+
+ Icon(
+ painter = painterResource(id = R.drawable.icon_24_arrow_down),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp),
+ tint = iconTintColor
+ )
+ }
+ }
+}
+
+private fun getState(
+ text: String,
+ readOnly: Boolean,
+ enable: Boolean,
+): PokitSelectState {
+ return when {
+ !enable -> {
+ PokitSelectState.DISABLE
+ }
+
+ readOnly -> {
+ PokitSelectState.READ_ONLY
+ }
+
+ text.isEmpty() -> {
+ PokitSelectState.DEFAULT
+ }
+
+ else -> {
+ PokitSelectState.INPUT
+ }
+ }
+}
+
+@Composable
+private fun getLabelTextColor(
+ state: PokitSelectState,
+): Color {
+ return when (state) {
+ PokitSelectState.DISABLE -> PokitTheme.colors.textDisable
+ else -> PokitTheme.colors.textSecondary
+ }
+}
+
+@Composable
+private fun getContentTextColor(
+ state: PokitSelectState,
+): Color {
+ return when (state) {
+ PokitSelectState.DISABLE -> PokitTheme.colors.textDisable
+ PokitSelectState.INPUT -> PokitTheme.colors.textSecondary
+ else -> PokitTheme.colors.textTertiary
+ }
+}
+
+@Composable
+private fun getIconTintColor(
+ state: PokitSelectState,
+): Color {
+ return when (state) {
+ PokitSelectState.DISABLE -> PokitTheme.colors.iconDisable
+ else -> PokitTheme.colors.iconSecondary
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/Preview.kt
new file mode 100644
index 00000000..e5793fad
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/Preview.kt
@@ -0,0 +1,43 @@
+package pokitmons.pokit.core.ui.components.block.select
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun PokitSelectPreview() {
+ PokitTheme {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ for (enabled in arrayOf(false, true)) {
+ for (readOnly in arrayOf(false, true)) {
+ PokitSelect(
+ text = "입력",
+ hintText = "선택해주세요",
+ label = "Label",
+ onClick = {},
+ enable = enabled,
+ readOnly = readOnly
+ )
+
+ PokitSelect(
+ text = "",
+ hintText = "선택해주세요",
+ label = "Label",
+ onClick = {},
+ enable = enabled,
+ readOnly = readOnly
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/attributes/PokitSelectState.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/attributes/PokitSelectState.kt
new file mode 100644
index 00000000..666af8a9
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/attributes/PokitSelectState.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.core.ui.components.block.select.attributes
+
+internal enum class PokitSelectState {
+ DEFAULT, INPUT, DISABLE, READ_ONLY
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/modifier/PokitSelectContainerModifier.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/modifier/PokitSelectContainerModifier.kt
new file mode 100644
index 00000000..d1584e0b
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/select/modifier/PokitSelectContainerModifier.kt
@@ -0,0 +1,58 @@
+package pokitmons.pokit.core.ui.components.block.select.modifier
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.block.select.attributes.PokitSelectState
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun Modifier.pokitSelectContentContainerModifier(
+ state: PokitSelectState,
+): Modifier {
+ val backgroundColor = getBackgroundColor(state = state)
+ val strokeColor = getStrokeColor(state = state)
+
+ return this then Modifier
+ .clip(
+ shape = RoundedCornerShape(8.dp)
+ )
+ .background(
+ color = backgroundColor,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .border(
+ shape = RoundedCornerShape(8.dp),
+ width = 1.dp,
+ color = strokeColor
+ )
+}
+
+@Composable
+private fun getBackgroundColor(
+ state: PokitSelectState,
+): Color {
+ return when (state) {
+ PokitSelectState.DEFAULT -> PokitTheme.colors.backgroundBase
+ PokitSelectState.INPUT -> PokitTheme.colors.backgroundBase
+ PokitSelectState.DISABLE -> PokitTheme.colors.backgroundDisable
+ PokitSelectState.READ_ONLY -> PokitTheme.colors.backgroundSecondary
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ state: PokitSelectState,
+): Color {
+ return when (state) {
+ PokitSelectState.DEFAULT -> PokitTheme.colors.borderSecondary
+ PokitSelectState.INPUT -> PokitTheme.colors.borderSecondary
+ PokitSelectState.DISABLE -> PokitTheme.colors.borderDisable
+ PokitSelectState.READ_ONLY -> PokitTheme.colors.borderSecondary
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/PokitRadio.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/PokitRadio.kt
new file mode 100644
index 00000000..583b4a93
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/PokitRadio.kt
@@ -0,0 +1,39 @@
+package pokitmons.pokit.core.ui.components.block.switchradio
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.block.switchradio.attributes.PokitSwitchRadioStyle
+import pokitmons.pokit.core.ui.components.block.switchradio.subcomponents.SwitchRadioItem
+
+@Composable
+fun PokitSwitchRadio(
+ itemList: List,
+ selectedItem: T,
+ onClickItem: (T) -> Unit,
+ getTitleFromItem: (T) -> String,
+ modifier: Modifier = Modifier,
+ style: PokitSwitchRadioStyle = PokitSwitchRadioStyle.FILLED,
+ itemSpace: Dp = 8.dp,
+ enabled: Boolean = true,
+) {
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(itemSpace)
+ ) {
+ for (item in itemList) {
+ SwitchRadioItem(
+ text = getTitleFromItem(item),
+ data = item,
+ onClickItem = { onClickItem(item) },
+ style = style,
+ selected = selectedItem == item,
+ enabled = enabled,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/Preview.kt
new file mode 100644
index 00000000..f64b411a
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/Preview.kt
@@ -0,0 +1,52 @@
+package pokitmons.pokit.core.ui.components.block.switchradio
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.block.switchradio.attributes.PokitSwitchRadioStyle
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun PokitSwitchRadioPreview() {
+ val scrollState = rememberScrollState()
+ val sampleItemList = listOf("텍스트1", "텍스트2")
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ enumValues().forEach { style ->
+ PokitSwitchRadio(
+ itemList = sampleItemList,
+ selectedItem = sampleItemList[0],
+ onClickItem = {},
+ getTitleFromItem = { it },
+ modifier = Modifier.fillMaxWidth(),
+ style = style
+ )
+
+ PokitSwitchRadio(
+ itemList = sampleItemList,
+ selectedItem = sampleItemList[0],
+ onClickItem = {},
+ getTitleFromItem = { it },
+ modifier = Modifier.fillMaxWidth(),
+ style = style,
+ enabled = false
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/attributes/PokitSwitchRadioStyle.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/attributes/PokitSwitchRadioStyle.kt
new file mode 100644
index 00000000..a99e18f0
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/attributes/PokitSwitchRadioStyle.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.core.ui.components.block.switchradio.attributes
+
+enum class PokitSwitchRadioStyle {
+ FILLED, STROKE
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/subcomponents/RadioItem.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/subcomponents/RadioItem.kt
new file mode 100644
index 00000000..00985b93
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/switchradio/subcomponents/RadioItem.kt
@@ -0,0 +1,133 @@
+package pokitmons.pokit.core.ui.components.block.switchradio.subcomponents
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.components.block.switchradio.attributes.PokitSwitchRadioStyle
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+internal fun SwitchRadioItem(
+ text: String,
+ data: T,
+ onClickItem: (T) -> Unit,
+ style: PokitSwitchRadioStyle,
+ selected: Boolean,
+ enabled: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val shape = RoundedCornerShape(8.dp)
+ val backgroundColor = getBackgroundColor(style = style, selected = selected, enabled = enabled)
+ val strokeColor = getStrokeColor(style = style, selected = selected, enabled = enabled)
+ val textColor = getTextColor(style = style, selected = selected, enabled = enabled)
+
+ Text(
+ text = text,
+ modifier = modifier
+ .clip(shape = shape)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ enabled = enabled,
+ onClick = { onClickItem(data) }
+ )
+ .background(
+ shape = shape,
+ color = backgroundColor
+ )
+ .border(
+ width = 1.dp,
+ shape = shape,
+ color = strokeColor
+ )
+ .padding(all = 16.dp),
+ style = PokitTheme.typography.body2Medium.copy(color = textColor),
+ textAlign = TextAlign.Center
+ )
+}
+
+@Composable
+private fun getBackgroundColor(
+ style: PokitSwitchRadioStyle,
+ selected: Boolean,
+ enabled: Boolean,
+): Color {
+ return when {
+ !enabled -> {
+ PokitTheme.colors.backgroundDisable
+ }
+
+ style == PokitSwitchRadioStyle.STROKE -> {
+ PokitTheme.colors.backgroundBase
+ }
+
+ style == PokitSwitchRadioStyle.FILLED && selected -> {
+ PokitTheme.colors.brand
+ }
+
+ style == PokitSwitchRadioStyle.FILLED && !selected -> {
+ PokitTheme.colors.backgroundPrimary
+ }
+
+ else -> {
+ Color.Unspecified
+ }
+ }
+}
+
+@Composable
+private fun getStrokeColor(
+ style: PokitSwitchRadioStyle,
+ selected: Boolean,
+ enabled: Boolean,
+): Color {
+ return when {
+ enabled && selected && style == PokitSwitchRadioStyle.STROKE -> {
+ PokitTheme.colors.brand
+ }
+
+ else -> {
+ Color.Unspecified
+ }
+ }
+}
+
+@Composable
+private fun getTextColor(
+ style: PokitSwitchRadioStyle,
+ selected: Boolean,
+ enabled: Boolean,
+): Color {
+ return when {
+ !enabled -> {
+ PokitTheme.colors.textDisable
+ }
+
+ !selected -> {
+ PokitTheme.colors.textTertiary
+ }
+
+ style == PokitSwitchRadioStyle.STROKE -> {
+ PokitTheme.colors.brand
+ }
+
+ style == PokitSwitchRadioStyle.FILLED -> {
+ PokitTheme.colors.inverseWh
+ }
+
+ else -> {
+ Color.Unspecified
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/tap/PokitTap.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/tap/PokitTap.kt
new file mode 100644
index 00000000..4109b575
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/tap/PokitTap.kt
@@ -0,0 +1,61 @@
+package pokitmons.pokit.core.ui.components.block.tap
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+fun PokitTap(
+ text: String,
+ data: T,
+ onClick: (T) -> Unit,
+ selectedItem: T,
+ modifier: Modifier = Modifier,
+) {
+ val textStyle = getTextStyle(selected = (data == selectedItem))
+
+ Box(
+ modifier = modifier
+ .noRippleClickable {
+ onClick(data)
+ }
+ ) {
+ Text(
+ text = text,
+ style = textStyle,
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .padding(bottom = 16.dp, top = 4.dp)
+ )
+
+ if (selectedItem == data) {
+ HorizontalDivider(
+ thickness = 2.dp,
+ color = PokitTheme.colors.brand,
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ )
+ }
+ }
+}
+
+@Composable
+private fun getTextStyle(
+ selected: Boolean,
+): TextStyle {
+ return if (selected) {
+ PokitTheme.typography.label1SemiBold.copy(color = PokitTheme.colors.textSecondary)
+ } else {
+ PokitTheme.typography.label1Regular.copy(color = PokitTheme.colors.textTertiary)
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/tap/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/tap/Preview.kt
new file mode 100644
index 00000000..4778a5bf
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/block/tap/Preview.kt
@@ -0,0 +1,50 @@
+package pokitmons.pokit.core.ui.components.block.tap
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun PokitSwitchRadioPreview() {
+ val scrollState = rememberScrollState()
+ val sampleTapList = listOf("tap1", "tap2")
+ var currentSelectedTap by remember { mutableStateOf(sampleTapList[0]) }
+
+ PokitTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(12.dp)
+ .verticalScroll(scrollState),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ sampleTapList.forEach { tap ->
+ PokitTap(
+ text = tap,
+ data = tap,
+ onClick = { currentSelectedTap = it },
+ selectedItem = currentSelectedTap
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/PokitBottomSheet.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/PokitBottomSheet.kt
new file mode 100644
index 00000000..53bb0d50
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/PokitBottomSheet.kt
@@ -0,0 +1,113 @@
+package pokitmons.pokit.core.ui.components.template.bottomsheet
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsIgnoringVisibility
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.windowInsetsBottomHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Surface
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
+@Composable
+fun PokitBottomSheet(
+ onHideBottomSheet: () -> Unit,
+ show: Boolean = false,
+ skipPartiallyExpanded: Boolean = true,
+ content: @Composable (ColumnScope.() -> Unit),
+) {
+ val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
+ var visibility by remember { mutableStateOf(show) }
+ val scope = rememberCoroutineScope()
+
+ LaunchedEffect(show) {
+ if (visibility && !show) {
+ scope.launch {
+ bottomSheetState.hide()
+ }.invokeOnCompletion {
+ onHideBottomSheet()
+ visibility = false
+ }
+ } else {
+ visibility = show
+ }
+ }
+
+ if (visibility) {
+ ModalBottomSheet(
+ onDismissRequest = remember {
+ {
+ onHideBottomSheet()
+ visibility = false
+ }
+ },
+ shape = RectangleShape,
+ sheetState = bottomSheetState,
+ scrimColor = Color.Transparent,
+ containerColor = Color.Transparent,
+ dragHandle = null
+ ) {
+ Spacer(modifier = Modifier.height(8.dp).background(Color.Transparent))
+
+ Surface(
+ shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
+ color = PokitTheme.colors.backgroundBase,
+ shadowElevation = 8.dp
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Box(
+ Modifier
+ .clip(RoundedCornerShape(4.dp))
+ .width(36.dp)
+ .height(4.dp)
+ .background(color = PokitTheme.colors.iconTertiary)
+ )
+
+ Spacer(modifier = Modifier.height(12.dp))
+ }
+
+ content()
+
+ Spacer(
+ Modifier.windowInsetsBottomHeight(
+ WindowInsets.navigationBarsIgnoringVisibility
+ )
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/Preview.kt
new file mode 100644
index 00000000..b66f5e21
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/Preview.kt
@@ -0,0 +1,61 @@
+package pokitmons.pokit.core.ui.components.template.bottomsheet
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Button
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.template.bottomsheet.subcomponents.PokitBottomSheetItem
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun PokitSwitchRadioPreview() {
+ var showBottomSheet by remember { mutableStateOf(false) }
+
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = PokitTheme.colors.backgroundBase
+ ) {
+ Button(onClick = { showBottomSheet = true }) {
+ Text(text = "Click!")
+ }
+ }
+
+ PokitBottomSheet(
+ onHideBottomSheet = { showBottomSheet = false },
+ show = showBottomSheet
+ ) {
+ PokitBottomSheetItem(
+ text = "즐겨찾기",
+ resourceId = R.drawable.icon_24_star_1,
+ data = "즐겨찾기",
+ onClick = { }
+ )
+
+ HorizontalDivider(
+ thickness = 1.dp,
+ color = PokitTheme.colors.borderTertiary
+ )
+
+ PokitBottomSheetItem(
+ text = "공유하기",
+ resourceId = R.drawable.icon_24_share,
+ data = "공유하기",
+ onClick = { showBottomSheet = false }
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/subcomponents/PokitBottomSheetItem.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/subcomponents/PokitBottomSheetItem.kt
new file mode 100644
index 00000000..3e2ddca5
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/bottomsheet/subcomponents/PokitBottomSheetItem.kt
@@ -0,0 +1,48 @@
+package pokitmons.pokit.core.ui.components.template.bottomsheet.subcomponents
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+fun PokitBottomSheetItem(
+ text: String,
+ resourceId: Int,
+ data: T,
+ onClick: (T) -> Unit,
+) {
+ Row(
+ modifier = Modifier
+ .noRippleClickable {
+ onClick(data)
+ }
+ .padding(horizontal = 24.dp, vertical = 20.dp)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = text,
+ style = PokitTheme.typography.body1Medium.copy(color = PokitTheme.colors.textSecondary)
+ )
+
+ Image(
+ painter = painterResource(id = resourceId),
+ contentDescription = null,
+ modifier = Modifier.size(24.dp),
+ colorFilter = ColorFilter.tint(color = PokitTheme.colors.iconPrimary)
+ )
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/linkdetailbottomsheet/LinkDetailBottomSheet.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/linkdetailbottomsheet/LinkDetailBottomSheet.kt
new file mode 100644
index 00000000..4b11f0bc
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/linkdetailbottomsheet/LinkDetailBottomSheet.kt
@@ -0,0 +1,242 @@
+package pokitmons.pokit.core.ui.components.template.linkdetailbottomsheet
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.block.linkurlcard.LinkUrlCard
+import pokitmons.pokit.core.ui.components.template.bottomsheet.PokitBottomSheet
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.theme.color.Orange50
+
+@Composable
+fun LinkDetailBottomSheet(
+ title: String,
+ memo: String,
+ url: String,
+ thumbnailPainter: Painter,
+ bookmark: Boolean,
+ openWebBrowserByClick: Boolean,
+ pokitName: String,
+ dateString: String,
+ onHideBottomSheet: () -> Unit,
+ show: Boolean = false,
+ useRemind: Boolean = false,
+ onClickBookmark: (() -> Unit)? = null,
+ onClickRemoveLink: (() -> Unit)? = null,
+ onClickModifyLink: (() -> Unit)? = null,
+ onClickShareLink: (() -> Unit)? = null,
+) {
+ PokitBottomSheet(
+ onHideBottomSheet = onHideBottomSheet,
+ show = show
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (useRemind) {
+ Image(
+ painter = painterResource(id = R.drawable.icon_24_bell),
+ contentDescription = null,
+ modifier = Modifier
+ .size(20.dp)
+ .background(
+ color = PokitTheme.colors.brand,
+ shape = CircleShape
+ )
+ .padding(2.dp),
+ colorFilter = ColorFilter.tint(PokitTheme.colors.inverseWh)
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+ }
+
+ Text(
+ text = pokitName,
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = PokitTheme.colors.borderTertiary,
+ shape = RoundedCornerShape(4.dp)
+ )
+ .background(
+ color = PokitTheme.colors.backgroundBase,
+ shape = RoundedCornerShape(4.dp)
+ )
+ .padding(horizontal = 8.dp, vertical = 4.dp),
+ style = PokitTheme.typography.label4.copy(color = PokitTheme.colors.textTertiary)
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = title,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
+ style = PokitTheme.typography.title3.copy(color = PokitTheme.colors.textPrimary)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = dateString,
+ style = PokitTheme.typography.detail2.copy(color = PokitTheme.colors.textTertiary),
+ textAlign = TextAlign.End
+ )
+ }
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ HorizontalDivider(
+ thickness = 1.dp,
+ color = PokitTheme.colors.borderTertiary
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp, vertical = 24.dp)
+ ) {
+ LinkUrlCard(
+ thumbnailPainter = thumbnailPainter,
+ url = url,
+ title = title,
+ openWebBrowserByClick = openWebBrowserByClick
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = memo,
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = Orange50,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(16.dp),
+ style = PokitTheme.typography.body3Regular.copy(color = PokitTheme.colors.textPrimary),
+ maxLines = 4,
+ minLines = 4
+ )
+ }
+
+ HorizontalDivider(
+ thickness = 1.dp,
+ color = PokitTheme.colors.borderTertiary
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 10.dp, start = 10.dp, end = 10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ modifier = Modifier
+ .size(36.dp)
+ .padding(6.dp)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = {
+ onClickBookmark?.invoke()
+ }
+ ),
+ painter = painterResource(id = R.drawable.icon_24_star),
+ contentDescription = "bookmark",
+ colorFilter = ColorFilter.tint(
+ color = if (bookmark) PokitTheme.colors.brand else PokitTheme.colors.iconTertiary
+ )
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ onClickShareLink?.let {
+ Image(
+ modifier = Modifier
+ .size(36.dp)
+ .padding(6.dp)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClickShareLink
+ ),
+ painter = painterResource(id = R.drawable.icon_24_share),
+ contentDescription = "share",
+ colorFilter = ColorFilter.tint(
+ color = PokitTheme.colors.iconSecondary
+ )
+ )
+ }
+
+ onClickModifyLink?.let {
+ Image(
+ modifier = Modifier
+ .size(36.dp)
+ .padding(6.dp)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClickModifyLink
+ ),
+ painter = painterResource(id = R.drawable.icon_24_edit),
+ contentDescription = "edit",
+ colorFilter = ColorFilter.tint(
+ color = PokitTheme.colors.iconSecondary
+ )
+ )
+ }
+
+ onClickRemoveLink?.let {
+ Image(
+ modifier = Modifier
+ .size(36.dp)
+ .padding(6.dp)
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() },
+ onClick = onClickRemoveLink
+ ),
+ painter = painterResource(id = R.drawable.icon_24_trash),
+ contentDescription = "remove",
+ colorFilter = ColorFilter.tint(
+ color = PokitTheme.colors.iconSecondary
+ )
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/linkdetailbottomsheet/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/linkdetailbottomsheet/Preview.kt
new file mode 100644
index 00000000..3d162637
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/linkdetailbottomsheet/Preview.kt
@@ -0,0 +1,35 @@
+package pokitmons.pokit.core.ui.components.template.linkdetailbottomsheet
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun LinkDetailBottomSheetPreview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ LinkDetailBottomSheet(
+ title = "title",
+ memo = "some memo",
+ url = "https://naver.com",
+ thumbnailPainter = painterResource(id = R.drawable.icon_24_google),
+ bookmark = true,
+ openWebBrowserByClick = false,
+ show = true,
+ pokitName = "TEXT",
+ dateString = "2024.08.27",
+ onHideBottomSheet = { },
+ onClickBookmark = { },
+ onClickModifyLink = { },
+ onClickRemoveLink = { },
+ onClickShareLink = { }
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/ModifyBottomSheetContent.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/ModifyBottomSheetContent.kt
new file mode 100644
index 00000000..4ea40913
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/ModifyBottomSheetContent.kt
@@ -0,0 +1,45 @@
+package pokitmons.pokit.core.ui.components.template.modifybottomsheet
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.template.modifybottomsheet.subcomponents.ModifyBottomSheetItem
+
+@Composable
+fun ModifyBottomSheetContent(
+ onClickShare: (() -> Unit)? = null,
+ onClickModify: (() -> Unit)? = null,
+ onClickRemove: (() -> Unit)? = null,
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ onClickShare?.let { onClickShare ->
+ ModifyBottomSheetItem(
+ onClick = onClickShare,
+ title = stringResource(id = R.string.share),
+ painter = painterResource(id = R.drawable.icon_24_share)
+ )
+ }
+
+ onClickModify?.let { onClickModify ->
+ ModifyBottomSheetItem(
+ onClick = onClickModify,
+ title = stringResource(id = R.string.modify),
+ painter = painterResource(id = R.drawable.icon_24_edit)
+ )
+ }
+
+ onClickRemove?.let { onClickRemove ->
+ ModifyBottomSheetItem(
+ onClick = onClickRemove,
+ title = stringResource(id = R.string.remove),
+ painter = painterResource(id = R.drawable.icon_24_trash)
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/Preview.kt
new file mode 100644
index 00000000..ec0be10f
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/Preview.kt
@@ -0,0 +1,22 @@
+package pokitmons.pokit.core.ui.components.template.modifybottomsheet
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun ModifyBottomSheetContentPreview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ ModifyBottomSheetContent(
+ onClickRemove = {},
+ onClickModify = {},
+ onClickShare = {}
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/subcomponents/ModifyBottomSheetItem.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/subcomponents/ModifyBottomSheetItem.kt
new file mode 100644
index 00000000..8a0d35eb
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/modifybottomsheet/subcomponents/ModifyBottomSheetItem.kt
@@ -0,0 +1,45 @@
+package pokitmons.pokit.core.ui.components.template.modifybottomsheet.subcomponents
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+import pokitmons.pokit.core.ui.utils.noRippleClickable
+
+@Composable
+internal fun ModifyBottomSheetItem(
+ onClick: () -> Unit,
+ title: String,
+ painter: Painter,
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .noRippleClickable {
+ onClick()
+ }
+ .padding(horizontal = 24.dp, vertical = 20.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = title,
+ style = PokitTheme.typography.body1Medium.copy(color = PokitTheme.colors.textSecondary)
+ )
+
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painter,
+ contentDescription = null
+ )
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/onebuttonbottomsheet/OneButtonBottomSheetContent.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/onebuttonbottomsheet/OneButtonBottomSheetContent.kt
new file mode 100644
index 00000000..6011bca5
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/onebuttonbottomsheet/OneButtonBottomSheetContent.kt
@@ -0,0 +1,76 @@
+package pokitmons.pokit.core.ui.components.template.onebuttonbottomsheet
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.button.PokitButton
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonShape
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun OneButtonBottomSheetContent(
+ title: String,
+ subText: String? = null,
+ buttonText: String = stringResource(id = R.string.confirmation),
+ onClickButton: () -> Unit = {},
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Spacer(modifier = Modifier.height(36.dp))
+
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp),
+ text = title,
+ textAlign = TextAlign.Center,
+ style = PokitTheme.typography.title2.copy(color = PokitTheme.colors.textPrimary)
+ )
+
+ subText?.let { subText ->
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp),
+ text = subText,
+ textAlign = TextAlign.Center,
+ style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary)
+ )
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp, start = 20.dp, end = 20.dp, bottom = 28.dp)
+ ) {
+ PokitButton(
+ text = buttonText,
+ icon = null,
+ onClick = onClickButton,
+ shape = PokitButtonShape.RECTANGLE,
+ type = PokitButtonType.PRIMARY,
+ size = PokitButtonSize.LARGE,
+ style = PokitButtonStyle.FILLED,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/onebuttonbottomsheet/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/onebuttonbottomsheet/Preview.kt
new file mode 100644
index 00000000..6d84b526
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/onebuttonbottomsheet/Preview.kt
@@ -0,0 +1,39 @@
+package pokitmons.pokit.core.ui.components.template.onebuttonbottomsheet
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ OneButtonBottomSheetContent(
+ title = "로그인 오류",
+ subText = "현재 서버 오류로 로그인에 실패했습니다.\n잠시 후에 다시 시도해 주세요."
+ )
+
+ HorizontalDivider(
+ modifier = Modifier.height(16.dp).background(PokitTheme.colors.borderTertiary)
+ )
+
+ OneButtonBottomSheetContent(
+ title = "여기에 타이틀이 들어갑니다"
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pooki/Pooki.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pooki/Pooki.kt
new file mode 100644
index 00000000..d811e8ec
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pooki/Pooki.kt
@@ -0,0 +1,47 @@
+package pokitmons.pokit.core.ui.components.template.pooki
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+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.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun Pooki(
+ modifier: Modifier = Modifier,
+ title: String,
+ sub: String,
+) {
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.Center
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier
+ .size(180.dp),
+ painter = painterResource(id = R.drawable.pooki),
+ contentDescription = "pooki"
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(text = title, style = PokitTheme.typography.title2.copy(color = PokitTheme.colors.textPrimary))
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(text = sub, style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary))
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pooki/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pooki/Preview.kt
new file mode 100644
index 00000000..1a33b261
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pooki/Preview.kt
@@ -0,0 +1,18 @@
+package pokitmons.pokit.core.ui.components.template.pooki
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Pooki(title = "저장된 포킷이 없어요!", sub = "포킷을 생성해 링크를 저장해보세요")
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookiempty/EmptyPooki.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookiempty/EmptyPooki.kt
new file mode 100644
index 00000000..67d7f820
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookiempty/EmptyPooki.kt
@@ -0,0 +1,47 @@
+package pokitmons.pokit.core.ui.components.template.pookiempty
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+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.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun EmptyPooki(
+ modifier: Modifier = Modifier,
+ title: String,
+ sub: String,
+) {
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.Center
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier
+ .size(180.dp),
+ painter = painterResource(id = R.drawable.empty_pooki),
+ contentDescription = "empty"
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(text = title, style = PokitTheme.typography.title2.copy(color = PokitTheme.colors.textPrimary))
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(text = sub, style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary))
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookiempty/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookiempty/Preview.kt
new file mode 100644
index 00000000..175f345b
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookiempty/Preview.kt
@@ -0,0 +1,18 @@
+package pokitmons.pokit.core.ui.components.template.pookiempty
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ EmptyPooki(title = "저장된 포킷이 없어요!", sub = "포킷을 생성해 링크를 저장해보세요")
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookierror/ErrorPokki.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookierror/ErrorPokki.kt
new file mode 100644
index 00000000..67429cc2
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookierror/ErrorPokki.kt
@@ -0,0 +1,68 @@
+package pokitmons.pokit.core.ui.components.template.pookierror
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+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.res.stringResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.button.PokitButton
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun ErrorPooki(
+ modifier: Modifier = Modifier,
+ pookiSize: Dp = 180.dp,
+ title: String,
+ sub: String,
+ onClickRetry: (() -> Unit)? = null,
+) {
+ Box(
+ modifier = modifier,
+ contentAlignment = Alignment.Center
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier
+ .size(pookiSize),
+ painter = painterResource(id = R.drawable.cry_pooki),
+ contentDescription = "empty"
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(text = title, style = PokitTheme.typography.title2.copy(color = PokitTheme.colors.textPrimary))
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(text = sub, style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary))
+
+ onClickRetry?.let { onClick ->
+ Spacer(modifier = Modifier.height(16.dp))
+
+ PokitButton(
+ type = PokitButtonType.SECONDARY,
+ size = PokitButtonSize.SMALL,
+ style = PokitButtonStyle.DEFAULT,
+ text = stringResource(id = R.string.retry),
+ icon = null,
+ onClick = onClick
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookierror/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookierror/Preview.kt
new file mode 100644
index 00000000..80673356
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/pookierror/Preview.kt
@@ -0,0 +1,19 @@
+package pokitmons.pokit.core.ui.components.template.pookierror
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ ErrorPooki(title = "오류가 발생했어요", sub = "조금 뒤 다시 접속해주세요", onClickRetry = null)
+ // ErrorPooki(title = "오류가 발생했어요", sub = "조금 뒤 다시 접속해주세요", onClickRetry = {})
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/removeItemBottomSheet/Preview.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/removeItemBottomSheet/Preview.kt
new file mode 100644
index 00000000..530009e7
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/removeItemBottomSheet/Preview.kt
@@ -0,0 +1,41 @@
+package pokitmons.pokit.core.ui.components.template.removeItemBottomSheet
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Preview(showBackground = true)
+@Composable
+private fun RemoveItemBottomSheetContentPreview() {
+ PokitTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ TwoButtonBottomSheetContent(
+ title = "포킷을 정말 삭제하시겠습니까?",
+ subText = "함께 저장한 모든 링크가 삭제되며,\n복구하실 수 없습니다.",
+ onClickLeftButton = {},
+ onClickRightButton = {}
+ )
+
+ HorizontalDivider(
+ modifier = Modifier.height(16.dp).background(PokitTheme.colors.borderTertiary)
+ )
+
+ TwoButtonBottomSheetContent(
+ title = "로그아웃 하시겠습니까?",
+ onClickLeftButton = {},
+ onClickRightButton = {}
+ )
+ }
+ }
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/removeItemBottomSheet/TwoButtonBottomSheetContent.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/removeItemBottomSheet/TwoButtonBottomSheetContent.kt
new file mode 100644
index 00000000..a5adac04
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/components/template/removeItemBottomSheet/TwoButtonBottomSheetContent.kt
@@ -0,0 +1,84 @@
+package pokitmons.pokit.core.ui.components.template.removeItemBottomSheet
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+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.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import pokitmons.pokit.core.ui.R
+import pokitmons.pokit.core.ui.components.atom.button.PokitButton
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonShape
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonSize
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonStyle
+import pokitmons.pokit.core.ui.components.atom.button.attributes.PokitButtonType
+import pokitmons.pokit.core.ui.theme.PokitTheme
+
+@Composable
+fun TwoButtonBottomSheetContent(
+ title: String,
+ subText: String? = null,
+ leftButtonText: String = stringResource(id = R.string.cancellation),
+ rightButtonText: String = stringResource(id = R.string.removal),
+ onClickLeftButton: () -> Unit,
+ onClickRightButton: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 36.dp, start = 20.dp, end = 20.dp, bottom = 20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = title,
+ style = PokitTheme.typography.title2.copy(color = PokitTheme.colors.textPrimary)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ subText?.let { subString ->
+ Text(
+ text = subString,
+ style = PokitTheme.typography.body2Medium.copy(color = PokitTheme.colors.textSecondary),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp, start = 20.dp, end = 20.dp, bottom = 28.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ PokitButton(
+ text = leftButtonText,
+ icon = null,
+ onClick = onClickLeftButton,
+ shape = PokitButtonShape.RECTANGLE,
+ type = PokitButtonType.SECONDARY,
+ size = PokitButtonSize.LARGE,
+ style = PokitButtonStyle.STROKE,
+ modifier = Modifier.weight(1f)
+ )
+
+ PokitButton(
+ text = rightButtonText,
+ icon = null,
+ onClick = onClickRightButton,
+ shape = PokitButtonShape.RECTANGLE,
+ type = PokitButtonType.PRIMARY,
+ size = PokitButtonSize.LARGE,
+ style = PokitButtonStyle.FILLED,
+ modifier = Modifier.weight(1f)
+ )
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Font.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Font.kt
new file mode 100644
index 00000000..a54bb92e
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Font.kt
@@ -0,0 +1,28 @@
+package pokitmons.pokit.core.ui.theme
+
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import pokitmons.pokit.core.ui.R
+
+val pretendard = FontFamily(
+ Font(R.font.pretendard_black, FontWeight.Black, FontStyle.Normal),
+ Font(R.font.pretendard_bold, FontWeight.Bold, FontStyle.Normal),
+ Font(R.font.pretendard_extra_bold, FontWeight.ExtraBold, FontStyle.Normal),
+ Font(R.font.pretendard_extra_light, FontWeight.ExtraLight, FontStyle.Normal),
+ Font(R.font.pretendard_light, FontWeight.Light, FontStyle.Normal),
+ Font(R.font.pretendard_medium, FontWeight.Medium, FontStyle.Normal),
+ Font(R.font.pretendard_semi_bold, FontWeight.SemiBold, FontStyle.Normal),
+ Font(R.font.pretendard_thin, FontWeight.Thin, FontStyle.Normal),
+
+ Font(R.font.pretendard_black, FontWeight.W900, FontStyle.Normal),
+ Font(R.font.pretendard_extra_bold, FontWeight.W800, FontStyle.Normal),
+ Font(R.font.pretendard_bold, FontWeight.W700, FontStyle.Normal),
+ Font(R.font.pretendard_semi_bold, FontWeight.W600, FontStyle.Normal),
+ Font(R.font.pretendard_medium, FontWeight.W500, FontStyle.Normal),
+ Font(R.font.pretendard_regular, FontWeight.W400, FontStyle.Normal),
+ Font(R.font.pretendard_light, FontWeight.W300, FontStyle.Normal),
+ Font(R.font.pretendard_extra_light, FontWeight.W200, FontStyle.Normal),
+ Font(R.font.pretendard_thin, FontWeight.W100, FontStyle.Normal)
+)
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Theme.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Theme.kt
new file mode 100644
index 00000000..ea2d0427
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Theme.kt
@@ -0,0 +1,55 @@
+package pokitmons.pokit.core.ui.theme
+
+import android.app.Activity
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+import pokitmons.pokit.core.ui.theme.color.LocalPokitColorDarkScheme
+import pokitmons.pokit.core.ui.theme.color.LocalPokitColorScheme
+import pokitmons.pokit.core.ui.theme.color.PokitColors
+
+object PokitTheme {
+ val colors: PokitColors
+ @Composable
+ @ReadOnlyComposable
+ get() = LocalPokitColorScheme.current
+
+ val typography: PokitTypography
+ @Composable
+ @ReadOnlyComposable
+ get() = LocalPokitTypography.current
+}
+
+@Composable
+fun PokitTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ typography: PokitTypography = PokitTheme.typography,
+ content: @Composable () -> Unit,
+) {
+ val colorScheme = when {
+ darkTheme -> LocalPokitColorDarkScheme.current
+ else -> LocalPokitColorScheme.current
+ }
+
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.backgroundBase.toArgb()
+ // dark theme 지원시 true 대신 !darkTheme를 할당하도록 수정 예정
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = true
+ }
+ }
+
+ CompositionLocalProvider(
+ LocalPokitColorScheme provides colorScheme,
+ LocalPokitTypography provides typography
+ ) {
+ content()
+ }
+}
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Type.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Type.kt
new file mode 100644
index 00000000..8fd5c4e5
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/Type.kt
@@ -0,0 +1,166 @@
+package pokitmons.pokit.core.ui.theme
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import pokitmons.pokit.core.ui.theme.typography.PokitTypo
+
+@Immutable
+data class PokitTypography(
+ private val _title1: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 24,
+ fontWeight = FontWeight.Bold,
+ lineHeight = 32,
+ letterSpacing = (-0.26f)
+ ),
+ private val _title2: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 20,
+ fontWeight = FontWeight.Bold,
+ lineHeight = 28,
+ letterSpacing = (-0.6f)
+ ),
+ private val _title3: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 18,
+ fontWeight = FontWeight.Medium,
+ lineHeight = 24
+ ),
+ private val _body1Bold: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 18,
+ fontWeight = FontWeight.Bold,
+ lineHeight = 24,
+ letterSpacing = (-0.54f)
+ ),
+ private val _body1SemiBold: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 18,
+ fontWeight = FontWeight.SemiBold,
+ lineHeight = 24,
+ letterSpacing = (-0.54f)
+ ),
+ private val _body1Medium: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 18,
+ fontWeight = FontWeight.Medium,
+ lineHeight = 24,
+ letterSpacing = (-0.54f)
+ ),
+ private val _body2Bold: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 16,
+ fontWeight = FontWeight.Bold,
+ lineHeight = 20,
+ letterSpacing = (-0.176f)
+ ),
+ private val _body2Medium: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 16,
+ fontWeight = FontWeight.Medium,
+ lineHeight = 20,
+ letterSpacing = (-0.176f)
+ ),
+ private val _body3Medium: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 14,
+ fontWeight = FontWeight.Medium,
+ lineHeight = 18,
+ letterSpacing = (-0.154f)
+ ),
+ private val _body3Regular: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 14,
+ fontWeight = FontWeight.W400,
+ lineHeight = 24,
+ letterSpacing = (-0.42f)
+ ),
+ private val _detail1: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 14,
+ fontWeight = FontWeight.Medium,
+ lineHeight = 20,
+ letterSpacing = (-0.154f)
+ ),
+ private val _detail2: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 12,
+ fontWeight = FontWeight.W400,
+ lineHeight = 16,
+ letterSpacing = (-0.132f)
+ ),
+ private val _label1SemiBold: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 18,
+ fontWeight = FontWeight.SemiBold,
+ lineHeight = 24,
+ letterSpacing = (-0.2f)
+ ),
+ private val _label1Regular: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 18,
+ fontWeight = FontWeight.W400,
+ lineHeight = 24,
+ letterSpacing = (-0.2f)
+ ),
+ private val _label2SemiBold: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 16,
+ fontWeight = FontWeight.SemiBold,
+ lineHeight = 20,
+ letterSpacing = (-0.18f)
+ ),
+ private val _label2Regular: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 16,
+ fontWeight = FontWeight.W400,
+ lineHeight = 20,
+ letterSpacing = (-0.18f)
+ ),
+ private val _label3SemiBold: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 14,
+ fontWeight = FontWeight.SemiBold,
+ lineHeight = 16,
+ letterSpacing = (-0.154f)
+ ),
+ private val _label3Regular: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 14,
+ fontWeight = FontWeight.W400,
+ lineHeight = 16,
+ letterSpacing = (-0.154f)
+ ),
+ private val _label4: PokitTypo = PokitTypo(
+ fontFamily = pretendard,
+ fontSize = 10,
+ fontWeight = FontWeight.W400,
+ lineHeight = 12,
+ letterSpacing = (-0.11f)
+ ),
+) {
+ val title1: TextStyle @Composable get() = _title1.toDpTextStyle
+ val title2: TextStyle @Composable get() = _title2.toDpTextStyle
+ val title3: TextStyle @Composable get() = _title3.toDpTextStyle
+ val body1Bold: TextStyle @Composable get() = _body1Bold.toDpTextStyle
+ val body1SemiBold: TextStyle @Composable get() = _body1SemiBold.toDpTextStyle
+ val body1Medium: TextStyle @Composable get() = _body1Medium.toDpTextStyle
+ val body2Bold: TextStyle @Composable get() = _body2Bold.toDpTextStyle
+ val body2Medium: TextStyle @Composable get() = _body2Medium.toDpTextStyle
+ val body3Medium: TextStyle @Composable get() = _body3Medium.toDpTextStyle
+ val body3Regular: TextStyle @Composable get() = _body3Regular.toDpTextStyle
+ val detail1: TextStyle @Composable get() = _detail1.toDpTextStyle
+ val detail2: TextStyle @Composable get() = _detail2.toDpTextStyle
+ val label1SemiBold: TextStyle @Composable get() = _label1SemiBold.toDpTextStyle
+ val label1Regular: TextStyle @Composable get() = _label1Regular.toDpTextStyle
+ val label2SemiBold: TextStyle @Composable get() = _label2SemiBold.toDpTextStyle
+ val label2Regular: TextStyle @Composable get() = _label2Regular.toDpTextStyle
+ val label3SemiBold: TextStyle @Composable get() = _label3SemiBold.toDpTextStyle
+ val label3Regular: TextStyle @Composable get() = _label3Regular.toDpTextStyle
+ val label4: TextStyle @Composable get() = _label4.toDpTextStyle
+}
+
+internal val LocalPokitTypography = staticCompositionLocalOf { PokitTypography() }
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/color/ColorSystem.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/color/ColorSystem.kt
new file mode 100644
index 00000000..ba175484
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/color/ColorSystem.kt
@@ -0,0 +1,40 @@
+package pokitmons.pokit.core.ui.theme.color
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+
+@Immutable
+data class PokitColors(
+ val textPrimary: Color = Gray900,
+ val textSecondary: Color = Gray700,
+ val textTertiary: Color = Gray400,
+ val textDisable: Color = Gray500,
+ val iconPrimary: Color = Gray800,
+ val iconSecondary: Color = Gray400,
+ val iconTertiary: Color = Gray200,
+ val iconDisable: Color = Gray500,
+ val backgroundBase: Color = White,
+ val backgroundBaseIcon: Color = Color(0xCCD9D9D9),
+ val backgroundPrimary: Color = Gray50,
+ val backgroundSecondary: Color = Gray100,
+ val backgroundTertiary: Color = Gray700,
+ val backgroundDisable: Color = Gray200,
+ val borderPrimary: Color = Gray700,
+ val borderSecondary: Color = Gray200,
+ val borderTertiary: Color = Gray100,
+ val borderDisable: Color = Gray200,
+ val brand: Color = Brand,
+ val brandBold: Color = Orange700,
+ val info: Color = Blue700,
+ val warning: Color = Yellow400,
+ val success: Color = Green400,
+ val error: Color = Red500,
+ val inverseWh: Color = White,
+)
+
+internal fun pokitColorsLight(): PokitColors = PokitColors()
+internal fun pokitColorsDark(): PokitColors = PokitColors(error = Red100)
+
+internal val LocalPokitColorScheme = staticCompositionLocalOf { pokitColorsLight() }
+internal val LocalPokitColorDarkScheme = staticCompositionLocalOf { pokitColorsDark() }
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/color/Colors.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/color/Colors.kt
new file mode 100644
index 00000000..f8cafcee
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/color/Colors.kt
@@ -0,0 +1,73 @@
+package pokitmons.pokit.core.ui.theme.color
+
+import androidx.compose.ui.graphics.Color
+
+val Black = Color.Black
+val Gray900 = Color(0xFF080808)
+val Gray800 = Color(0xFF262626)
+val Gray700 = Color(0xFF434343)
+val Gray600 = Color(0xFF555555)
+val Gray500 = Color(0xFF7B7B7B)
+val Gray400 = Color(0xFF9D9D9D)
+val Gray300 = Color(0xFFC4C4C4)
+val Gray200 = Color(0xFFD9D9D9)
+val Gray100 = Color(0xFFE9E9E9)
+val Gray50 = Color(0xFFF5F5F5)
+val White = Color.White
+
+val Orange900 = Color(0xFFE35B16)
+val Orange800 = Color(0xFFED7419)
+val Orange700 = Color(0xFFF3831B)
+val Orange600 = Color(0xFFFa921E)
+val Orange500 = Color(0xFFFE8422)
+val Orange400 = Color(0xFFFEAC36)
+val Orange300 = Color(0xFFFEBB57)
+val Orange200 = Color(0xFFFECF87)
+val Orange100 = Color(0xFFFEE2B6)
+val Orange50 = Color(0xFFFFF4E2)
+
+val Red900 = Color(0xFFC2191F)
+val Red800 = Color(0xFFD0272B)
+val Red700 = Color(0xFFDD2F33)
+val Red600 = Color(0xFFF03939)
+val Red500 = Color(0xFFFF443A)
+val Red400 = Color(0xFFF85454)
+val Red300 = Color(0xFFED7577)
+val Red200 = Color(0xFFF59C9D)
+val Red100 = Color(0xFFFFCED4)
+val Red50 = Color(0xFFFFECEF)
+
+val Yellow900 = Color(0xFFFF6D00)
+val Yellow800 = Color(0xFFFF8F00)
+val Yellow700 = Color(0xFFFFA100)
+val Yellow600 = Color(0xFFFFB500)
+val Yellow500 = Color(0xFFFFC300)
+val Yellow400 = Color(0xFFFFCC00)
+val Yellow300 = Color(0xFFFFD743)
+val Yellow200 = Color(0xFFFFE17C)
+val Yellow100 = Color(0xFFFFEDB0)
+val Yellow50 = Color(0xFFFFF8E0)
+
+val Green900 = Color(0xFF006900)
+val Green800 = Color(0xFF008911)
+val Green700 = Color(0xFF009A20)
+val Green600 = Color(0xFF00Ad2D)
+val Green500 = Color(0xFF00BC37)
+val Green400 = Color(0xFF34C759)
+val Green300 = Color(0xFF64D278)
+val Green200 = Color(0xFF93DEA0)
+val Green100 = Color(0xFFC0EBC5)
+val Green50 = Color(0xFFE5F7E8)
+
+val Blue900 = Color(0xFF1F47CD)
+val Blue800 = Color(0xFF1269Ec)
+val Blue700 = Color(0xFF007BFF)
+val Blue600 = Color(0xFF008FFF)
+val Blue500 = Color(0xFF009Eff)
+val Blue400 = Color(0xFF1DADFF)
+val Blue300 = Color(0xFF56BDFF)
+val Blue200 = Color(0xFF8DD0FF)
+val Blue100 = Color(0xFFBBE2FF)
+val Blue50 = Color(0xFFE3F1FF)
+
+val Brand = Color(0xFFFE8422)
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/typography/PokitTypo.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/typography/PokitTypo.kt
new file mode 100644
index 00000000..bdca7aba
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/theme/typography/PokitTypo.kt
@@ -0,0 +1,55 @@
+package pokitmons.pokit.core.ui.theme.typography
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+data class PokitTypo(
+ val fontWeight: FontWeight,
+ val fontFamily: FontFamily,
+ val fontSize: Int,
+ val lineHeight: Int,
+ val letterSpacing: Float = 0f,
+) {
+ val toDpTextStyle: TextStyle
+ @Composable get() =
+ TextStyle(
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ fontSize = fontSize.textDp,
+ letterSpacing = letterSpacing.textDp,
+ lineHeight = lineHeight.textDp
+ )
+
+ val toSpTextStyle: TextStyle
+ @Composable get() =
+ TextStyle(
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ fontSize = fontSize.sp,
+ letterSpacing = letterSpacing.sp,
+ lineHeight = lineHeight.sp
+ )
+}
+
+private fun Int.textDp(density: Density): TextUnit =
+ with(density) {
+ this@textDp.dp.toSp()
+ }
+
+private val Int.textDp: TextUnit
+ @Composable get() = this.textDp(density = LocalDensity.current)
+
+private fun Float.textDp(density: Density): TextUnit =
+ with(density) {
+ this@textDp.dp.toSp()
+ }
+
+private val Float.textDp: TextUnit
+ @Composable get() = this.textDp(density = LocalDensity.current)
diff --git a/core/ui/src/main/java/pokitmons/pokit/core/ui/utils/ModifierUtils.kt b/core/ui/src/main/java/pokitmons/pokit/core/ui/utils/ModifierUtils.kt
new file mode 100644
index 00000000..b390060c
--- /dev/null
+++ b/core/ui/src/main/java/pokitmons/pokit/core/ui/utils/ModifierUtils.kt
@@ -0,0 +1,24 @@
+package pokitmons.pokit.core.ui.utils
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+
+internal fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier): Modifier {
+ return if (condition) {
+ then(modifier(Modifier))
+ } else {
+ this
+ }
+}
+
+@Composable
+fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier {
+ return this.clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = onClick
+ )
+}
diff --git a/core/ui/src/main/res/drawable-hdpi/big_pooki.png b/core/ui/src/main/res/drawable-hdpi/big_pooki.png
new file mode 100644
index 00000000..a1dc2b36
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/big_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-hdpi/cry_pooki.png b/core/ui/src/main/res/drawable-hdpi/cry_pooki.png
new file mode 100644
index 00000000..7dbe473c
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/cry_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-hdpi/empty_pooki.png b/core/ui/src/main/res/drawable-hdpi/empty_pooki.png
new file mode 100644
index 00000000..f76c39ce
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/empty_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-hdpi/party_popper.png b/core/ui/src/main/res/drawable-hdpi/party_popper.png
new file mode 100644
index 00000000..395d288f
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/party_popper.png differ
diff --git a/core/ui/src/main/res/drawable-hdpi/pooki.png b/core/ui/src/main/res/drawable-hdpi/pooki.png
new file mode 100644
index 00000000..8cc25883
Binary files /dev/null and b/core/ui/src/main/res/drawable-hdpi/pooki.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/big_pooki.png b/core/ui/src/main/res/drawable-mdpi/big_pooki.png
new file mode 100644
index 00000000..ab47481e
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/big_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/cry_pooki.png b/core/ui/src/main/res/drawable-mdpi/cry_pooki.png
new file mode 100644
index 00000000..72b0b0d5
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/cry_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/empty_pooki.png b/core/ui/src/main/res/drawable-mdpi/empty_pooki.png
new file mode 100644
index 00000000..cc886afc
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/empty_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/party_popper.png b/core/ui/src/main/res/drawable-mdpi/party_popper.png
new file mode 100644
index 00000000..68649dfe
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/party_popper.png differ
diff --git a/core/ui/src/main/res/drawable-mdpi/pooki.png b/core/ui/src/main/res/drawable-mdpi/pooki.png
new file mode 100644
index 00000000..a33a2181
Binary files /dev/null and b/core/ui/src/main/res/drawable-mdpi/pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/big_pooki.png b/core/ui/src/main/res/drawable-xhdpi/big_pooki.png
new file mode 100644
index 00000000..c129ea2f
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/big_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/cry_pooki.png b/core/ui/src/main/res/drawable-xhdpi/cry_pooki.png
new file mode 100644
index 00000000..14bcf0df
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/cry_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/empty_pooki.png b/core/ui/src/main/res/drawable-xhdpi/empty_pooki.png
new file mode 100644
index 00000000..357179b8
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/empty_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/party_popper.png b/core/ui/src/main/res/drawable-xhdpi/party_popper.png
new file mode 100644
index 00000000..47ab9426
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/party_popper.png differ
diff --git a/core/ui/src/main/res/drawable-xhdpi/pooki.png b/core/ui/src/main/res/drawable-xhdpi/pooki.png
new file mode 100644
index 00000000..c17d09fc
Binary files /dev/null and b/core/ui/src/main/res/drawable-xhdpi/pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/big_pooki.png b/core/ui/src/main/res/drawable-xxhdpi/big_pooki.png
new file mode 100644
index 00000000..f89d6f9a
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/big_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/cry_pooki.png b/core/ui/src/main/res/drawable-xxhdpi/cry_pooki.png
new file mode 100644
index 00000000..c2ec17c9
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/cry_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/empty_pooki.png b/core/ui/src/main/res/drawable-xxhdpi/empty_pooki.png
new file mode 100644
index 00000000..752d8ce4
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/empty_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/party_popper.png b/core/ui/src/main/res/drawable-xxhdpi/party_popper.png
new file mode 100644
index 00000000..38e75d86
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/party_popper.png differ
diff --git a/core/ui/src/main/res/drawable-xxhdpi/pooki.png b/core/ui/src/main/res/drawable-xxhdpi/pooki.png
new file mode 100644
index 00000000..3fd0412c
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxhdpi/pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/big_pooki.png b/core/ui/src/main/res/drawable-xxxhdpi/big_pooki.png
new file mode 100644
index 00000000..29377d0d
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/big_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/cry_pooki.png b/core/ui/src/main/res/drawable-xxxhdpi/cry_pooki.png
new file mode 100644
index 00000000..830fd495
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/cry_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/empty_pooki.png b/core/ui/src/main/res/drawable-xxxhdpi/empty_pooki.png
new file mode 100644
index 00000000..da4c5ba3
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/empty_pooki.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/party_popper.png b/core/ui/src/main/res/drawable-xxxhdpi/party_popper.png
new file mode 100644
index 00000000..be739b2a
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/party_popper.png differ
diff --git a/core/ui/src/main/res/drawable-xxxhdpi/pooki.png b/core/ui/src/main/res/drawable-xxxhdpi/pooki.png
new file mode 100644
index 00000000..6a2fcade
Binary files /dev/null and b/core/ui/src/main/res/drawable-xxxhdpi/pooki.png differ
diff --git a/core/ui/src/main/res/drawable/icon_18_align.xml b/core/ui/src/main/res/drawable/icon_18_align.xml
new file mode 100644
index 00000000..d0de91a0
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_18_align.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24__discover.xml b/core/ui/src/main/res/drawable/icon_24__discover.xml
new file mode 100644
index 00000000..40daba7f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24__discover.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_align.xml b/core/ui/src/main/res/drawable/icon_24_align.xml
new file mode 100644
index 00000000..8550f6f1
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_align.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_apple.xml b/core/ui/src/main/res/drawable/icon_24_apple.xml
new file mode 100644
index 00000000..5897d736
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_apple.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_arrow.xml b/core/ui/src/main/res/drawable/icon_24_arrow.xml
new file mode 100644
index 00000000..3dc23d78
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_arrow.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_arrow_down.xml b/core/ui/src/main/res/drawable/icon_24_arrow_down.xml
new file mode 100644
index 00000000..1ee77ced
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_arrow_down.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_arrow_left.xml b/core/ui/src/main/res/drawable/icon_24_arrow_left.xml
new file mode 100644
index 00000000..a68b0c47
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_arrow_left.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_arrow_right.xml b/core/ui/src/main/res/drawable/icon_24_arrow_right.xml
new file mode 100644
index 00000000..e0abb1fc
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_arrow_right.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_bell.xml b/core/ui/src/main/res/drawable/icon_24_bell.xml
new file mode 100644
index 00000000..4b920822
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_bell.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_check.xml b/core/ui/src/main/res/drawable/icon_24_check.xml
new file mode 100644
index 00000000..01c0684f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_check.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_dash.xml b/core/ui/src/main/res/drawable/icon_24_dash.xml
new file mode 100644
index 00000000..d1a74b5b
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_dash.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_edit.xml b/core/ui/src/main/res/drawable/icon_24_edit.xml
new file mode 100644
index 00000000..6ef11daf
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_edit.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_filter.xml b/core/ui/src/main/res/drawable/icon_24_filter.xml
new file mode 100644
index 00000000..5923a12a
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_filter.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_folder.xml b/core/ui/src/main/res/drawable/icon_24_folder.xml
new file mode 100644
index 00000000..73b4786f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_folder.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_folderline.xml b/core/ui/src/main/res/drawable/icon_24_folderline.xml
new file mode 100644
index 00000000..8e7eadcc
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_folderline.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_google.xml b/core/ui/src/main/res/drawable/icon_24_google.xml
new file mode 100644
index 00000000..58c3c545
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_google.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_home.xml b/core/ui/src/main/res/drawable/icon_24_home.xml
new file mode 100644
index 00000000..537da895
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_home.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_info.xml b/core/ui/src/main/res/drawable/icon_24_info.xml
new file mode 100644
index 00000000..42177b55
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_info.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_kebab.xml b/core/ui/src/main/res/drawable/icon_24_kebab.xml
new file mode 100644
index 00000000..e7a9293f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_kebab.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_link.xml b/core/ui/src/main/res/drawable/icon_24_link.xml
new file mode 100644
index 00000000..13e51e90
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_link.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_linkline.xml b/core/ui/src/main/res/drawable/icon_24_linkline.xml
new file mode 100644
index 00000000..2fcef5ac
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_linkline.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_plus.xml b/core/ui/src/main/res/drawable/icon_24_plus.xml
new file mode 100644
index 00000000..35df2c39
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_plus.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_plus_r.xml b/core/ui/src/main/res/drawable/icon_24_plus_r.xml
new file mode 100644
index 00000000..2bd9c4ca
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_plus_r.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_remind.xml b/core/ui/src/main/res/drawable/icon_24_remind.xml
new file mode 100644
index 00000000..7d6f06bf
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_remind.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_reminder.xml b/core/ui/src/main/res/drawable/icon_24_reminder.xml
new file mode 100644
index 00000000..2b450e01
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_reminder.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_search.xml b/core/ui/src/main/res/drawable/icon_24_search.xml
new file mode 100644
index 00000000..2b0892bb
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_search.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_setup.xml b/core/ui/src/main/res/drawable/icon_24_setup.xml
new file mode 100644
index 00000000..f2f29495
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_setup.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_share.xml b/core/ui/src/main/res/drawable/icon_24_share.xml
new file mode 100644
index 00000000..8b83c1e9
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_share.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_star.xml b/core/ui/src/main/res/drawable/icon_24_star.xml
new file mode 100644
index 00000000..b7728d42
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_star.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_star_1.xml b/core/ui/src/main/res/drawable/icon_24_star_1.xml
new file mode 100644
index 00000000..c8a0cb9b
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_star_1.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_trash.xml b/core/ui/src/main/res/drawable/icon_24_trash.xml
new file mode 100644
index 00000000..4d8818d4
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_trash.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/icon_24_x.xml b/core/ui/src/main/res/drawable/icon_24_x.xml
new file mode 100644
index 00000000..fc53b875
--- /dev/null
+++ b/core/ui/src/main/res/drawable/icon_24_x.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/image_floating.xml b/core/ui/src/main/res/drawable/image_floating.xml
new file mode 100644
index 00000000..f1183b4b
--- /dev/null
+++ b/core/ui/src/main/res/drawable/image_floating.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/logo_brand.xml b/core/ui/src/main/res/drawable/logo_brand.xml
new file mode 100644
index 00000000..b7819c49
--- /dev/null
+++ b/core/ui/src/main/res/drawable/logo_brand.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/logo_pokit.xml b/core/ui/src/main/res/drawable/logo_pokit.xml
new file mode 100644
index 00000000..fbdaba70
--- /dev/null
+++ b/core/ui/src/main/res/drawable/logo_pokit.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/logo_remind.xml b/core/ui/src/main/res/drawable/logo_remind.xml
new file mode 100644
index 00000000..25bcb6f7
--- /dev/null
+++ b/core/ui/src/main/res/drawable/logo_remind.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/logo_white.xml b/core/ui/src/main/res/drawable/logo_white.xml
new file mode 100644
index 00000000..fdd54bec
--- /dev/null
+++ b/core/ui/src/main/res/drawable/logo_white.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/sign_up_icon.png b/core/ui/src/main/res/drawable/sign_up_icon.png
new file mode 100644
index 00000000..97cb8796
Binary files /dev/null and b/core/ui/src/main/res/drawable/sign_up_icon.png differ
diff --git a/core/ui/src/main/res/font/pretendard_black.otf b/core/ui/src/main/res/font/pretendard_black.otf
new file mode 100644
index 00000000..a0d849e7
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_black.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_bold.otf b/core/ui/src/main/res/font/pretendard_bold.otf
new file mode 100644
index 00000000..8e5e30a2
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_bold.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_extra_bold.otf b/core/ui/src/main/res/font/pretendard_extra_bold.otf
new file mode 100644
index 00000000..388f3ca4
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_extra_bold.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_extra_light.otf b/core/ui/src/main/res/font/pretendard_extra_light.otf
new file mode 100644
index 00000000..40c8b69c
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_extra_light.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_light.otf b/core/ui/src/main/res/font/pretendard_light.otf
new file mode 100644
index 00000000..228679e9
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_light.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_medium.otf b/core/ui/src/main/res/font/pretendard_medium.otf
new file mode 100644
index 00000000..05750698
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_medium.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_regular.otf b/core/ui/src/main/res/font/pretendard_regular.otf
new file mode 100644
index 00000000..08bf4cfc
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_regular.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_semi_bold.otf b/core/ui/src/main/res/font/pretendard_semi_bold.otf
new file mode 100644
index 00000000..e7e36abc
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_semi_bold.otf differ
diff --git a/core/ui/src/main/res/font/pretendard_thin.otf b/core/ui/src/main/res/font/pretendard_thin.otf
new file mode 100644
index 00000000..77e792d7
Binary files /dev/null and b/core/ui/src/main/res/font/pretendard_thin.otf differ
diff --git a/core/ui/src/main/res/values/string.xml b/core/ui/src/main/res/values/string.xml
new file mode 100644
index 00000000..cd43f1d7
--- /dev/null
+++ b/core/ui/src/main/res/values/string.xml
@@ -0,0 +1,37 @@
+
+
+ 안읽음
+ 링크 %d개
+
+ 취소
+ 삭제
+
+ 공유하기
+ 수정하기
+ 삭제하기
+
+ 확인
+
+ 다시 시도하기
+
+ 오류가 발생했어요
+ 조금 뒤 다시 접속해주세요
+
+ 저장된 포킷이 없어요!
+ 포킷을 생성해 링크를 저장해보세요
+
+ 저장된 링크가 없어요!
+ 다양한 링크를 한 곳에 저장해보세요
+
+ 링크가 부족해요!
+ 링크를 5개 이상 저장하고 추천을 받아보세요
+
+ 즐겨찾기 링크가 없어요!
+ 링크를 즐겨찾기로 관리해보세요
+
+ 검색된 링크가 없어요
+ 검색어를 다시 확인해주세요
+
+ 아직 알람이 없어요
+ 리마인드 알림을 설정하세요
+
\ No newline at end of file
diff --git a/core/ui/src/test/java/pokitmons/pokit/core/ui/ExampleUnitTest.kt b/core/ui/src/test/java/pokitmons/pokit/core/ui/ExampleUnitTest.kt
new file mode 100644
index 00000000..41b8d8e3
--- /dev/null
+++ b/core/ui/src/test/java/pokitmons/pokit/core/ui/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.core.ui
+
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/data/.gitignore b/data/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
new file mode 100644
index 00000000..05066de1
--- /dev/null
+++ b/data/build.gradle.kts
@@ -0,0 +1,85 @@
+plugins {
+ alias(libs.plugins.com.android.library)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.hilt)
+ id("kotlin-kapt")
+}
+
+android {
+ tasks.withType().configureEach {
+ useJUnitPlatform()
+ }
+
+ namespace = "pokitmons.pokit.data"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ testImplementation(project(":feature:login"))
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+
+ // room
+ implementation(libs.room.runtime)
+ annotationProcessor(libs.room.compiler)
+ kapt(libs.room.compiler)
+
+ // kotest
+ testImplementation(libs.kotest.runner.junit5)
+ testImplementation(libs.kotlin.reflect)
+
+ // hilt
+ implementation(libs.hilt)
+ kapt(libs.hilt.compiler)
+
+ // serialization
+ implementation(libs.kotlinx.serialization.json)
+
+ // retrofit
+ implementation(libs.retrofit)
+ implementation(libs.retrofit.converter.serialization)
+ implementation(libs.okhttp)
+ implementation(libs.logging.interceptor)
+
+ // datastore
+ implementation("androidx.datastore:datastore-preferences:1.0.0")
+
+ implementation(project(":domain"))
+
+ // jsoup
+ implementation(libs.jsoup)
+
+ // mockk
+ testImplementation(libs.mockk)
+ androidTestImplementation(libs.mockk.android)
+}
diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/data/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/data/src/androidTest/java/pokitmons/pokit/data/ExampleInstrumentedTest.kt b/data/src/androidTest/java/pokitmons/pokit/data/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..f82d44c6
--- /dev/null
+++ b/data/src/androidTest/java/pokitmons/pokit/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package pokitmons.pokit.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.TestCase.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("pokitmons.pokit.data.test", appContext.packageName)
+ }
+}
diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/data/src/main/java/pokitmons/pokit/data/api/AlertApi.kt b/data/src/main/java/pokitmons/pokit/data/api/AlertApi.kt
new file mode 100644
index 00000000..f7a45050
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/AlertApi.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.alert.GetAlertsResponse
+import pokitmons.pokit.domain.model.link.LinksSort
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.PUT
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface AlertApi {
+ @GET("alert")
+ suspend fun getAlerts(
+ @Query("page") page: Int,
+ @Query("size") size: Int,
+ @Query("sort") sort: List = listOf(LinksSort.RECENT.value),
+ ): GetAlertsResponse
+
+ @PUT("alert/{alertId}")
+ suspend fun deleteAlert(
+ @Path("alertId") alertId: Int,
+ ): Response
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/api/AuthApi.kt b/data/src/main/java/pokitmons/pokit/data/api/AuthApi.kt
new file mode 100644
index 00000000..5c3ef7fa
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/AuthApi.kt
@@ -0,0 +1,36 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.auth.request.SNSLoginRequest
+import pokitmons.pokit.data.model.auth.request.SignUpRequest
+import pokitmons.pokit.data.model.auth.request.WithdrawRequest
+import pokitmons.pokit.data.model.auth.response.DuplicateNicknameResponse
+import pokitmons.pokit.data.model.auth.response.SNSLoginResponse
+import pokitmons.pokit.data.model.auth.response.SignUpResponse
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.PUT
+import retrofit2.http.Path
+
+interface AuthApi {
+ @POST("auth/signin")
+ suspend fun snsLogin(
+ @Body snsLoginRequest: SNSLoginRequest,
+ ): SNSLoginResponse
+
+ @GET("user/duplicate/{nickname}")
+ suspend fun checkDuplicateNickname(
+ @Path(value = "nickname") nickname: String,
+ ): DuplicateNicknameResponse
+
+ @POST("user/signup")
+ suspend fun signUp(
+ @Body signUpRequest: SignUpRequest,
+ ): SignUpResponse
+
+ @PUT("auth/withdraw")
+ suspend fun withdraw(
+ @Body withdrawRequest: WithdrawRequest,
+ ): Response
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt b/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt
new file mode 100644
index 00000000..005032c5
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/LinkApi.kt
@@ -0,0 +1,78 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.link.request.ModifyLinkRequest
+import pokitmons.pokit.data.model.link.response.ApplyBookmarkResponse
+import pokitmons.pokit.data.model.link.response.GetLinkResponse
+import pokitmons.pokit.data.model.link.response.GetLinksResponse
+import pokitmons.pokit.data.model.link.response.ModifyLinkResponse
+import pokitmons.pokit.domain.model.link.LinksSort
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.PUT
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface LinkApi {
+ @GET("content/{categoryId}")
+ suspend fun getLinks(
+ @Path("categoryId") categoryId: Int = 0,
+ @Query("page") page: Int = 0,
+ @Query("size") size: Int = 10,
+ @Query("sort") sort: List = listOf(LinksSort.RECENT.value),
+ @Query("isRead") isRead: Boolean? = null,
+ @Query("favorites") favorites: Boolean? = null,
+ @Query("startDate") startDate: String? = null,
+ @Query("endDate") endDate: String? = null,
+ @Query("categoryIds") categoryIds: List? = null,
+ ): GetLinksResponse
+
+ @GET("content")
+ suspend fun searchLinks(
+ @Query("page") page: Int = 0,
+ @Query("size") size: Int = 10,
+ @Query("sort") sort: List = listOf(LinksSort.RECENT.value),
+ @Query("isRead") isRead: Boolean? = null,
+ @Query("favorites") favorites: Boolean? = null,
+ @Query("startDate") startDate: String? = null,
+ @Query("endDate") endDate: String? = null,
+ @Query("categoryIds") categoryIds: List? = null,
+ @Query("searchWord") searchWord: String = "",
+ ): GetLinksResponse
+
+ @PUT("content/{contentId}")
+ suspend fun deleteLink(
+ @Path("contentId") contentId: Int = 0,
+ ): Response
+
+ @POST("content/{contentId}")
+ suspend fun getLink(
+ @Path("contentId") contentId: Int = 0,
+ ): GetLinkResponse
+
+ @PATCH("content/{contentId}")
+ suspend fun modifyLink(
+ @Path("contentId") contentId: Int,
+ @Body modifyLinkRequest: ModifyLinkRequest,
+ ): ModifyLinkResponse
+
+ @POST("content")
+ suspend fun createLink(
+ @Body createLinkRequest: ModifyLinkRequest,
+ ): ModifyLinkResponse
+
+ @PUT("content/{contentId}/bookmark")
+ suspend fun cancelBookmark(@Path("contentId") contentId: Int): Response
+
+ @POST("content/{contentId}/bookmark")
+ suspend fun applyBookmark(@Path("contentId") contentId: Int): ApplyBookmarkResponse
+
+ @GET("content/uncategorized")
+ suspend fun getUncategorizedLinks(
+ @Query("page") page: Int = 0,
+ @Query("size") size: Int = 10,
+ @Query("sort") sort: List = listOf(LinksSort.RECENT.value),
+ ): GetLinksResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/api/PokitApi.kt b/data/src/main/java/pokitmons/pokit/data/api/PokitApi.kt
new file mode 100644
index 00000000..b3540e3a
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/PokitApi.kt
@@ -0,0 +1,56 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.pokit.request.CreatePokitRequest
+import pokitmons.pokit.data.model.pokit.request.ModifyPokitRequest
+import pokitmons.pokit.data.model.pokit.response.CreatePokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitCountResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitImagesResponseItem
+import pokitmons.pokit.data.model.pokit.response.GetPokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitsResponse
+import pokitmons.pokit.data.model.pokit.response.ModifyPokitResponse
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.PUT
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface PokitApi {
+ @GET("category")
+ suspend fun getPokits(
+ @Query("filterUncategorized") filterUncategorized: Boolean = true,
+ @Query("size") size: Int = 10,
+ @Query("page") page: Int = 0,
+ @Query("sort") sort: String = PokitsSort.RECENT.value,
+ ): GetPokitsResponse
+
+ @POST("category")
+ suspend fun createPokit(
+ @Body createPokitRequest: CreatePokitRequest,
+ ): CreatePokitResponse
+
+ @PATCH("category/{categoryId}")
+ suspend fun modifyPokit(
+ @Path("categoryId") categoryId: Int,
+ @Body modifyPokitRequest: ModifyPokitRequest,
+ ): ModifyPokitResponse
+
+ @GET("category/images")
+ suspend fun getPokitImages(): List
+
+ @GET("category/{categoryId}")
+ suspend fun getPokit(
+ @Path("categoryId") categoryId: Int,
+ ): GetPokitResponse
+
+ @PUT("category/{categoryId}")
+ suspend fun deletePokit(
+ @Path("categoryId") categoryId: Int,
+ ): Response
+
+ @GET("category/count")
+ suspend fun getPokitCount(): GetPokitCountResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/api/RemindApi.kt b/data/src/main/java/pokitmons/pokit/data/api/RemindApi.kt
new file mode 100644
index 00000000..b912b7f8
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/RemindApi.kt
@@ -0,0 +1,30 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.home.remind.Remind
+import pokitmons.pokit.data.model.home.remind.RemindResponse
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface RemindApi {
+ @GET("remind/unread")
+ suspend fun getUnreadContents(
+ @Query("size") size: Int = 10,
+ @Query("page") page: Int = 0,
+ @Query("sort") sort: String = PokitsSort.RECENT.value,
+ ): RemindResponse
+
+ @GET("remind/today")
+ suspend fun getTodayContents(
+ @Query("size") size: Int = 10,
+ @Query("page") page: Int = 0,
+ @Query("sort") sort: String = PokitsSort.RECENT.value,
+ ): List
+
+ @GET("remind/bookmark")
+ suspend fun getBookmarkContents(
+ @Query("size") size: Int = 10,
+ @Query("page") page: Int = 0,
+ @Query("sort") sort: String = PokitsSort.RECENT.value,
+ ): RemindResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/api/SettingApi.kt b/data/src/main/java/pokitmons/pokit/data/api/SettingApi.kt
new file mode 100644
index 00000000..a9140129
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/SettingApi.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.setting.reqeust.EditNicknameRequest
+import pokitmons.pokit.data.model.setting.response.EditNicknameResponse
+import retrofit2.http.Body
+import retrofit2.http.PUT
+
+interface SettingApi {
+ @PUT("user/nickname")
+ suspend fun editNickname(
+ @Body editNicknameRequest: EditNicknameRequest,
+ ): EditNicknameResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/api/TokenApi.kt b/data/src/main/java/pokitmons/pokit/data/api/TokenApi.kt
new file mode 100644
index 00000000..c23dc2a8
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/api/TokenApi.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.data.api
+
+import pokitmons.pokit.data.model.auth.request.TokenRequest
+import pokitmons.pokit.data.model.auth.response.TokenResponse
+import retrofit2.http.Body
+import retrofit2.http.POST
+
+interface TokenApi {
+ @POST("auth/reissue")
+ suspend fun reissue(
+ @Body tokenRequest: TokenRequest,
+ ): TokenResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/DataStore.kt b/data/src/main/java/pokitmons/pokit/data/datasource/DataStore.kt
new file mode 100644
index 00000000..af9d015a
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/DataStore.kt
@@ -0,0 +1,24 @@
+package pokitmons.pokit.data.datasource
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.preferencesDataStore
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+val Context.dataStore by preferencesDataStore(name = "token")
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DataStoreModule {
+ @Provides
+ @Singleton
+ fun provideDataStore(@ApplicationContext context: Context): DataStore {
+ return context.dataStore
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/local/AuthAuthenticator.kt b/data/src/main/java/pokitmons/pokit/data/datasource/local/AuthAuthenticator.kt
new file mode 100644
index 00000000..a882493c
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/local/AuthAuthenticator.kt
@@ -0,0 +1,37 @@
+package pokitmons.pokit.data.datasource.local
+
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import okhttp3.Authenticator
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.Route
+import pokitmons.pokit.data.api.TokenApi
+import pokitmons.pokit.data.model.auth.request.TokenRequest
+import javax.inject.Inject
+
+class AuthAuthenticator @Inject constructor(
+ private val tokenManager: TokenManager,
+ private val tokenApi: TokenApi,
+) : Authenticator {
+ override fun authenticate(route: Route?, response: Response): Request {
+ val refreshToken = runBlocking {
+ tokenManager.getRefreshToken().first()
+ }
+
+ val accessToken = runBlocking {
+ tokenApi.reissue(TokenRequest(refreshToken ?: "")).also { response ->
+ tokenManager.saveAccessToken(response.accessToken)
+ }
+ }
+ return newRequestWithToken(accessToken.accessToken, response.request)
+ }
+
+ private fun newRequestWithToken(token: String, request: Request): Request =
+ request.newBuilder()
+ .header(
+ "Authorization",
+ "Bearer $token"
+ )
+ .build()
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/local/TokenManager.kt b/data/src/main/java/pokitmons/pokit/data/datasource/local/TokenManager.kt
new file mode 100644
index 00000000..d167f348
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/local/TokenManager.kt
@@ -0,0 +1,54 @@
+package pokitmons.pokit.data.datasource.local
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.stringPreferencesKey
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class TokenManager @Inject constructor(
+ private val dataStore: DataStore,
+) {
+ companion object {
+ val ACCESS_TOKEN = stringPreferencesKey("access_token")
+ val REFRESH_TOKEN = stringPreferencesKey("refresh_token")
+ val AUTH_TYPE = stringPreferencesKey("auth_type")
+ }
+
+ fun getAccessToken(): Flow {
+ return dataStore.data.map { prefs ->
+ prefs[ACCESS_TOKEN]
+ }
+ }
+
+ suspend fun setAuthType(type: String) {
+ dataStore.edit { prefs ->
+ prefs[AUTH_TYPE] = type
+ }
+ }
+
+ fun getAuthType(): Flow {
+ return dataStore.data.map { prefs ->
+ prefs[AUTH_TYPE] ?: ""
+ }
+ }
+
+ suspend fun saveAccessToken(token: String) {
+ dataStore.edit { prefs ->
+ prefs[ACCESS_TOKEN] = token
+ }
+ }
+
+ fun getRefreshToken(): Flow {
+ return dataStore.data.map { prefs ->
+ prefs[REFRESH_TOKEN]
+ }
+ }
+
+ suspend fun saveRefreshToken(token: String) {
+ dataStore.edit { prefs ->
+ prefs[REFRESH_TOKEN] = token
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/local/search/LocalSearchWordDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/local/search/LocalSearchWordDataSource.kt
new file mode 100644
index 00000000..34b39370
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/local/search/LocalSearchWordDataSource.kt
@@ -0,0 +1,54 @@
+package pokitmons.pokit.data.datasource.local.search
+
+import android.content.SharedPreferences
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import pokitmons.pokit.data.room.dao.SearchWordDao
+import pokitmons.pokit.data.room.entity.SearchWord
+import java.util.Calendar
+import javax.inject.Inject
+
+class LocalSearchWordDataSource @Inject constructor(
+ private val searchWordDao: SearchWordDao,
+ private val sharedPreferences: SharedPreferences,
+) : SearchDataSource {
+ companion object {
+ const val USE_RECENT_WORD_SP_KEY = "use_recent_word"
+ }
+
+ private val useRecentSearchWords = MutableStateFlow(
+ sharedPreferences.getBoolean(USE_RECENT_WORD_SP_KEY, false)
+ )
+
+ override fun getSearchWord(): Flow> {
+ return searchWordDao.getRecentSearchWords()
+ }
+
+ override suspend fun addSearchWord(searchWord: String) {
+ val currentDateString = Calendar.getInstance()
+ val searchWordEntity = SearchWord(
+ word = searchWord,
+ searchedAt = currentDateString.timeInMillis.toString()
+ )
+ searchWordDao.addSearchWord(searchWord = searchWordEntity)
+ }
+
+ override suspend fun removeSearchWord(searchWord: String) {
+ searchWordDao.removeSearchWord(searchWord)
+ }
+
+ override suspend fun removeAllSearchWords() {
+ searchWordDao.removeAllSearchWords()
+ }
+
+ override suspend fun setUseRecentSearchWord(use: Boolean): Boolean {
+ sharedPreferences.edit().putBoolean(USE_RECENT_WORD_SP_KEY, use).apply()
+ useRecentSearchWords.update { use }
+ return use
+ }
+
+ override fun getUseRecentSearchWord(): Flow {
+ return useRecentSearchWords
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/local/search/SearchDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/local/search/SearchDataSource.kt
new file mode 100644
index 00000000..77e8f99c
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/local/search/SearchDataSource.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.data.datasource.local.search
+
+import kotlinx.coroutines.flow.Flow
+
+interface SearchDataSource {
+ fun getSearchWord(): Flow>
+ suspend fun addSearchWord(searchWord: String)
+ suspend fun removeSearchWord(searchWord: String)
+ suspend fun removeAllSearchWords()
+ suspend fun setUseRecentSearchWord(use: Boolean): Boolean
+ fun getUseRecentSearchWord(): Flow
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/alert/AlertDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/alert/AlertDataSource.kt
new file mode 100644
index 00000000..8cbaeeec
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/alert/AlertDataSource.kt
@@ -0,0 +1,8 @@
+package pokitmons.pokit.data.datasource.remote.alert
+
+import pokitmons.pokit.data.model.alert.GetAlertsResponse
+
+interface AlertDataSource {
+ suspend fun getAlerts(page: Int, size: Int): GetAlertsResponse
+ suspend fun deleteAlert(alertId: Int)
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/alert/RemoteAlertDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/alert/RemoteAlertDataSource.kt
new file mode 100644
index 00000000..d5125ef5
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/alert/RemoteAlertDataSource.kt
@@ -0,0 +1,17 @@
+package pokitmons.pokit.data.datasource.remote.alert
+
+import pokitmons.pokit.data.api.AlertApi
+import pokitmons.pokit.data.model.alert.GetAlertsResponse
+import javax.inject.Inject
+
+class RemoteAlertDataSource @Inject constructor(
+ private val api: AlertApi,
+) : AlertDataSource {
+ override suspend fun getAlerts(page: Int, size: Int): GetAlertsResponse {
+ return api.getAlerts(page = page, size = size)
+ }
+
+ override suspend fun deleteAlert(alertId: Int) {
+ api.deleteAlert(alertId)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/auth/AuthDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/auth/AuthDataSource.kt
new file mode 100644
index 00000000..42f83502
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/auth/AuthDataSource.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.data.datasource.remote.auth
+
+import pokitmons.pokit.data.model.auth.request.SNSLoginRequest
+import pokitmons.pokit.data.model.auth.request.SignUpRequest
+import pokitmons.pokit.data.model.auth.request.WithdrawRequest
+import pokitmons.pokit.data.model.auth.response.DuplicateNicknameResponse
+import pokitmons.pokit.data.model.auth.response.SNSLoginResponse
+import pokitmons.pokit.data.model.auth.response.SignUpResponse
+import retrofit2.Response
+
+interface AuthDataSource {
+ suspend fun signUp(signUpRequest: SignUpRequest): SignUpResponse
+ suspend fun snsLogin(snsLoginRequest: SNSLoginRequest): SNSLoginResponse
+ suspend fun checkDuplicateNickname(nickname: String): DuplicateNicknameResponse
+ suspend fun withdraw(withdrawRequest: WithdrawRequest): Response
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/auth/RemoteAuthDataSourceImpl.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/auth/RemoteAuthDataSourceImpl.kt
new file mode 100644
index 00000000..ac60eada
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/auth/RemoteAuthDataSourceImpl.kt
@@ -0,0 +1,29 @@
+package pokitmons.pokit.data.datasource.remote.auth
+
+import pokitmons.pokit.data.api.AuthApi
+import pokitmons.pokit.data.model.auth.request.SNSLoginRequest
+import pokitmons.pokit.data.model.auth.request.SignUpRequest
+import pokitmons.pokit.data.model.auth.request.WithdrawRequest
+import pokitmons.pokit.data.model.auth.response.DuplicateNicknameResponse
+import pokitmons.pokit.data.model.auth.response.SNSLoginResponse
+import pokitmons.pokit.data.model.auth.response.SignUpResponse
+import retrofit2.Response
+import javax.inject.Inject
+
+class RemoteAuthDataSourceImpl @Inject constructor(private val authApi: AuthApi) : AuthDataSource {
+ override suspend fun snsLogin(snsLoginRequest: SNSLoginRequest): SNSLoginResponse {
+ return authApi.snsLogin(snsLoginRequest)
+ }
+
+ override suspend fun checkDuplicateNickname(nickname: String): DuplicateNicknameResponse {
+ return authApi.checkDuplicateNickname(nickname)
+ }
+
+ override suspend fun withdraw(withdrawRequest: WithdrawRequest): Response {
+ return authApi.withdraw(withdrawRequest)
+ }
+
+ override suspend fun signUp(signUpRequest: SignUpRequest): SignUpResponse {
+ return authApi.signUp(signUpRequest)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/home/remind/RemindDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/home/remind/RemindDataSource.kt
new file mode 100644
index 00000000..dbf1ee25
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/home/remind/RemindDataSource.kt
@@ -0,0 +1,11 @@
+package pokitmons.pokit.data.datasource.remote.home.remind
+
+import pokitmons.pokit.data.model.home.remind.Remind
+import pokitmons.pokit.data.model.home.remind.RemindRequest
+import pokitmons.pokit.data.model.home.remind.RemindResponse
+
+interface RemindDataSource {
+ suspend fun getUnreadContents(remindRequest: RemindRequest): RemindResponse
+ suspend fun getTodayContents(remindRequest: RemindRequest): List
+ suspend fun getBookmarkContents(remindRequest: RemindRequest): RemindResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/home/remind/RemindDataSourceImpl.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/home/remind/RemindDataSourceImpl.kt
new file mode 100644
index 00000000..0b88996f
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/home/remind/RemindDataSourceImpl.kt
@@ -0,0 +1,21 @@
+package pokitmons.pokit.data.datasource.remote.home.remind
+
+import pokitmons.pokit.data.api.RemindApi
+import pokitmons.pokit.data.model.home.remind.Remind
+import pokitmons.pokit.data.model.home.remind.RemindRequest
+import pokitmons.pokit.data.model.home.remind.RemindResponse
+import javax.inject.Inject
+
+class RemindDataSourceImpl @Inject constructor(private val remindApi: RemindApi) : RemindDataSource {
+ override suspend fun getUnreadContents(remindRequest: RemindRequest): RemindResponse {
+ return remindApi.getUnreadContents()
+ }
+
+ override suspend fun getTodayContents(remindRequest: RemindRequest): List {
+ return remindApi.getTodayContents()
+ }
+
+ override suspend fun getBookmarkContents(remindRequest: RemindRequest): RemindResponse {
+ return remindApi.getBookmarkContents()
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt
new file mode 100644
index 00000000..fa8ca060
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/LinkDataSource.kt
@@ -0,0 +1,57 @@
+package pokitmons.pokit.data.datasource.remote.link
+
+import pokitmons.pokit.data.model.link.request.ModifyLinkRequest
+import pokitmons.pokit.data.model.link.response.GetLinkResponse
+import pokitmons.pokit.data.model.link.response.GetLinksResponse
+import pokitmons.pokit.data.model.link.response.LinkCardResponse
+import pokitmons.pokit.data.model.link.response.ModifyLinkResponse
+import pokitmons.pokit.domain.model.link.LinksSort
+
+interface LinkDataSource {
+ suspend fun getLinks(
+ categoryId: Int = 0,
+ page: Int = 0,
+ size: Int = 10,
+ sort: List = listOf(LinksSort.RECENT.value),
+ isRead: Boolean? = null,
+ favorites: Boolean? = null,
+ startDate: String? = null,
+ endDate: String? = null,
+ categoryIds: List? = null,
+ ): GetLinksResponse
+
+ suspend fun searchLinks(
+ page: Int = 0,
+ size: Int = 10,
+ sort: List = listOf(LinksSort.RECENT.value),
+ isRead: Boolean? = null,
+ favorites: Boolean? = null,
+ startDate: String? = null,
+ endDate: String? = null,
+ categoryIds: List? = null,
+ searchWord: String = "",
+ ): GetLinksResponse
+
+ suspend fun deleteLink(contentId: Int)
+
+ suspend fun getLink(contentId: Int): GetLinkResponse
+
+ suspend fun modifyLink(
+ contentId: Int,
+ modifyLinkRequest: ModifyLinkRequest,
+ ): ModifyLinkResponse
+
+ suspend fun createLink(
+ createLinkRequest: ModifyLinkRequest,
+ ): ModifyLinkResponse
+
+ suspend fun setBookmark(contentId: Int, bookmarked: Boolean)
+
+ suspend fun getLinkCard(url: String): LinkCardResponse
+
+ suspend fun getUncategorizedLinks(
+ page: Int = 0,
+ size: Int = 10,
+ sort: List = listOf(LinksSort.RECENT.value),
+ ): GetLinksResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt
new file mode 100644
index 00000000..c060f074
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/link/RemoteLinkDataSource.kt
@@ -0,0 +1,109 @@
+package pokitmons.pokit.data.datasource.remote.link
+
+import org.jsoup.Jsoup
+import pokitmons.pokit.data.api.LinkApi
+import pokitmons.pokit.data.model.link.request.ModifyLinkRequest
+import pokitmons.pokit.data.model.link.response.GetLinkResponse
+import pokitmons.pokit.data.model.link.response.GetLinksResponse
+import pokitmons.pokit.data.model.link.response.LinkCardResponse
+import pokitmons.pokit.data.model.link.response.ModifyLinkResponse
+import javax.inject.Inject
+
+class RemoteLinkDataSource @Inject constructor(
+ private val linkApi: LinkApi,
+) : LinkDataSource {
+ override suspend fun getLinks(
+ categoryId: Int,
+ page: Int,
+ size: Int,
+ sort: List,
+ isRead: Boolean?,
+ favorites: Boolean?,
+ startDate: String?,
+ endDate: String?,
+ categoryIds: List?,
+ ): GetLinksResponse {
+ return linkApi.getLinks(
+ categoryId = categoryId,
+ page = page,
+ size = size,
+ sort = sort,
+ isRead = isRead,
+ favorites = favorites,
+ startDate = startDate,
+ endDate = endDate,
+ categoryIds = categoryIds
+ )
+ }
+
+ override suspend fun searchLinks(
+ page: Int,
+ size: Int,
+ sort: List,
+ isRead: Boolean?,
+ favorites: Boolean?,
+ startDate: String?,
+ endDate: String?,
+ categoryIds: List?,
+ searchWord: String,
+ ): GetLinksResponse {
+ return linkApi.searchLinks(
+ page = page,
+ size = size,
+ sort = sort,
+ isRead = isRead,
+ favorites = favorites,
+ startDate = startDate,
+ endDate = endDate,
+ categoryIds = categoryIds,
+ searchWord = searchWord
+ )
+ }
+
+ override suspend fun deleteLink(contentId: Int) {
+ linkApi.deleteLink(contentId = contentId)
+ }
+
+ override suspend fun getLink(contentId: Int): GetLinkResponse {
+ return linkApi.getLink(contentId)
+ }
+
+ override suspend fun modifyLink(
+ contentId: Int,
+ modifyLinkRequest: ModifyLinkRequest,
+ ): ModifyLinkResponse {
+ return linkApi.modifyLink(
+ contentId = contentId,
+ modifyLinkRequest = modifyLinkRequest
+ )
+ }
+
+ override suspend fun createLink(createLinkRequest: ModifyLinkRequest): ModifyLinkResponse {
+ return linkApi.createLink(
+ createLinkRequest = createLinkRequest
+ )
+ }
+
+ override suspend fun setBookmark(contentId: Int, bookmarked: Boolean) {
+ if (bookmarked) {
+ linkApi.applyBookmark(contentId)
+ } else {
+ linkApi.cancelBookmark(contentId)
+ }
+ }
+
+ override suspend fun getLinkCard(url: String): LinkCardResponse {
+ val document = Jsoup.connect(url).get()
+ val image = document.select("meta[property=og:image]").attr("content").ifEmpty { null }
+ val title = document.select("meta[property=og:title]").attr("content")
+ return LinkCardResponse(
+ url = url,
+ image = image,
+ title = title
+ )
+ }
+
+ override suspend fun getUncategorizedLinks(page: Int, size: Int, sort: List): GetLinksResponse {
+ return linkApi.getUncategorizedLinks(page = page, size = size, sort = sort)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/pokit/PokitDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/pokit/PokitDataSource.kt
new file mode 100644
index 00000000..1032b7e8
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/pokit/PokitDataSource.kt
@@ -0,0 +1,21 @@
+package pokitmons.pokit.data.datasource.remote.pokit
+
+import pokitmons.pokit.data.model.pokit.request.CreatePokitRequest
+import pokitmons.pokit.data.model.pokit.request.GetPokitsRequest
+import pokitmons.pokit.data.model.pokit.request.ModifyPokitRequest
+import pokitmons.pokit.data.model.pokit.response.CreatePokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitCountResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitImagesResponseItem
+import pokitmons.pokit.data.model.pokit.response.GetPokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitsResponse
+import pokitmons.pokit.data.model.pokit.response.ModifyPokitResponse
+
+interface PokitDataSource {
+ suspend fun getPokits(getPokitsRequest: GetPokitsRequest): GetPokitsResponse
+ suspend fun createPokit(createPokitRequest: CreatePokitRequest): CreatePokitResponse
+ suspend fun modifyPokit(pokitId: Int, modifyPokitRequest: ModifyPokitRequest): ModifyPokitResponse
+ suspend fun getPokitImages(): List
+ suspend fun getPokit(pokitId: Int): GetPokitResponse
+ suspend fun deletePokit(pokitId: Int)
+ suspend fun getPokitCount(): GetPokitCountResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/pokit/RemotePokitDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/pokit/RemotePokitDataSource.kt
new file mode 100644
index 00000000..32c1a829
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/pokit/RemotePokitDataSource.kt
@@ -0,0 +1,50 @@
+package pokitmons.pokit.data.datasource.remote.pokit
+
+import pokitmons.pokit.data.api.PokitApi
+import pokitmons.pokit.data.model.pokit.request.CreatePokitRequest
+import pokitmons.pokit.data.model.pokit.request.GetPokitsRequest
+import pokitmons.pokit.data.model.pokit.request.ModifyPokitRequest
+import pokitmons.pokit.data.model.pokit.response.CreatePokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitCountResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitImagesResponseItem
+import pokitmons.pokit.data.model.pokit.response.GetPokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitsResponse
+import pokitmons.pokit.data.model.pokit.response.ModifyPokitResponse
+import javax.inject.Inject
+
+class RemotePokitDataSource @Inject constructor(
+ private val pokitApi: PokitApi,
+) : PokitDataSource {
+ override suspend fun getPokits(getPokitsRequest: GetPokitsRequest): GetPokitsResponse {
+ return pokitApi.getPokits(
+ filterUncategorized = getPokitsRequest.filterUncategoriezd,
+ size = getPokitsRequest.size,
+ page = getPokitsRequest.page,
+ sort = getPokitsRequest.sort.value
+ )
+ }
+
+ override suspend fun createPokit(createPokitRequest: CreatePokitRequest): CreatePokitResponse {
+ return pokitApi.createPokit(createPokitRequest = createPokitRequest)
+ }
+
+ override suspend fun modifyPokit(pokitId: Int, modifyPokitRequest: ModifyPokitRequest): ModifyPokitResponse {
+ return pokitApi.modifyPokit(categoryId = pokitId, modifyPokitRequest = modifyPokitRequest)
+ }
+
+ override suspend fun getPokitImages(): List {
+ return pokitApi.getPokitImages()
+ }
+
+ override suspend fun getPokit(pokitId: Int): GetPokitResponse {
+ return pokitApi.getPokit(pokitId)
+ }
+
+ override suspend fun deletePokit(pokitId: Int) {
+ pokitApi.deletePokit(pokitId)
+ }
+
+ override suspend fun getPokitCount(): GetPokitCountResponse {
+ return pokitApi.getPokitCount()
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/setting/RemoteSettingDataSourceImpl.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/setting/RemoteSettingDataSourceImpl.kt
new file mode 100644
index 00000000..604dbb52
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/setting/RemoteSettingDataSourceImpl.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.data.datasource.remote.setting
+
+import pokitmons.pokit.data.api.SettingApi
+import pokitmons.pokit.data.model.setting.reqeust.EditNicknameRequest
+import pokitmons.pokit.data.model.setting.response.EditNicknameResponse
+import javax.inject.Inject
+
+class RemoteSettingDataSourceImpl @Inject constructor(private val settingApi: SettingApi) : SettingDataSource {
+ override suspend fun editNickname(editNicknameRequest: EditNicknameRequest): EditNicknameResponse {
+ return settingApi.editNickname(editNicknameRequest)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/datasource/remote/setting/SettingDataSource.kt b/data/src/main/java/pokitmons/pokit/data/datasource/remote/setting/SettingDataSource.kt
new file mode 100644
index 00000000..f9edb38e
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/datasource/remote/setting/SettingDataSource.kt
@@ -0,0 +1,8 @@
+package pokitmons.pokit.data.datasource.remote.setting
+
+import pokitmons.pokit.data.model.setting.reqeust.EditNicknameRequest
+import pokitmons.pokit.data.model.setting.response.EditNicknameResponse
+
+interface SettingDataSource {
+ suspend fun editNickname(editNicknameRequest: EditNicknameRequest): EditNicknameResponse
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/alert/AlertModule.kt b/data/src/main/java/pokitmons/pokit/data/di/alert/AlertModule.kt
new file mode 100644
index 00000000..748138e7
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/alert/AlertModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.alert
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.remote.alert.AlertDataSource
+import pokitmons.pokit.data.datasource.remote.alert.RemoteAlertDataSource
+import pokitmons.pokit.data.repository.alert.AlertRepositoryImpl
+import pokitmons.pokit.domain.repository.alert.AlertRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class AlertModule {
+ @Binds
+ @Singleton
+ abstract fun bindAlertRepository(alertRepositoryImpl: AlertRepositoryImpl): AlertRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindAlertDataSource(alertDataSourceImpl: RemoteAlertDataSource): AlertDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/auth/AuthModule.kt b/data/src/main/java/pokitmons/pokit/data/di/auth/AuthModule.kt
new file mode 100644
index 00000000..62a641bf
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/auth/AuthModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.auth
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.remote.auth.AuthDataSource
+import pokitmons.pokit.data.datasource.remote.auth.RemoteAuthDataSourceImpl
+import pokitmons.pokit.data.repository.auth.AuthRepositoryImpl
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class AuthModule {
+ @Binds
+ @Singleton
+ abstract fun bindAuthRepository(authRepositoryImpl: AuthRepositoryImpl): AuthRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindAuthDataSource(authDataSourceImpl: RemoteAuthDataSourceImpl): AuthDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/core/room/RoomModule.kt b/data/src/main/java/pokitmons/pokit/data/di/core/room/RoomModule.kt
new file mode 100644
index 00000000..cc7126ee
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/core/room/RoomModule.kt
@@ -0,0 +1,25 @@
+package pokitmons.pokit.data.di.core.room
+
+import android.content.Context
+import androidx.room.Room
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.room.database.AppDatabase
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object RoomModule {
+ @Provides
+ @Singleton
+ fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
+ return Room.databaseBuilder(context, AppDatabase::class.java, "pokitDatabase.db").build()
+ }
+
+ @Provides
+ @Singleton
+ fun providerSearchWordDao(database: AppDatabase) = database.searchWordDao()
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/core/sharedpreferences/SharedPreferencesModule.kt b/data/src/main/java/pokitmons/pokit/data/di/core/sharedpreferences/SharedPreferencesModule.kt
new file mode 100644
index 00000000..7d6c6419
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/core/sharedpreferences/SharedPreferencesModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.core.sharedpreferences
+
+import android.content.Context
+import android.content.Context.MODE_PRIVATE
+import android.content.SharedPreferences
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object SharedPreferencesModule {
+ @Provides
+ @Singleton
+ fun provideSharedPreferences(
+ @ApplicationContext context: Context,
+ ): SharedPreferences {
+ return context.getSharedPreferences("pokit_shared_preferences", MODE_PRIVATE)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/home/remind/RemindModule.kt b/data/src/main/java/pokitmons/pokit/data/di/home/remind/RemindModule.kt
new file mode 100644
index 00000000..3905e080
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/home/remind/RemindModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.home.remind
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.remote.home.remind.RemindDataSource
+import pokitmons.pokit.data.datasource.remote.home.remind.RemindDataSourceImpl
+import pokitmons.pokit.data.repository.home.remind.RemindRepositoryImpl
+import pokitmons.pokit.domain.repository.home.remind.RemindRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class RemindModule {
+ @Binds
+ @Singleton
+ abstract fun bindRemindRepository(remindRepositoryImpl: RemindRepositoryImpl): RemindRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindRemindDataSource(remindDataSourceImpl: RemindDataSourceImpl): RemindDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/link/LinkModule.kt b/data/src/main/java/pokitmons/pokit/data/di/link/LinkModule.kt
new file mode 100644
index 00000000..61eadde8
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/link/LinkModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.link
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.remote.link.LinkDataSource
+import pokitmons.pokit.data.datasource.remote.link.RemoteLinkDataSource
+import pokitmons.pokit.data.repository.link.LinkRepositoryImpl
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class LinkModule {
+ @Binds
+ @Singleton
+ abstract fun bindLinkRepository(linkRepositoryImpl: LinkRepositoryImpl): LinkRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindLinkDataSource(linkDataSourceImpl: RemoteLinkDataSource): LinkDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/network/BearerTokenInterceptor.kt b/data/src/main/java/pokitmons/pokit/data/di/network/BearerTokenInterceptor.kt
new file mode 100644
index 00000000..7110eb95
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/network/BearerTokenInterceptor.kt
@@ -0,0 +1,28 @@
+package pokitmons.pokit.data.di.network
+
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+import pokitmons.pokit.data.datasource.local.TokenManager
+import javax.inject.Inject
+
+// 토큰 api 수정될 때 까지 사용
+class BearerTokenInterceptor @Inject constructor(private val tokenManager: TokenManager) : Interceptor {
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val token: String = runBlocking {
+ tokenManager.getAccessToken().first()
+ } ?: ""
+
+ val originalRequest: Request = chain.request()
+ val requestWithToken: Request = originalRequest.newBuilder()
+ .header(
+ "Authorization",
+ "Bearer $token"
+ )
+ .build()
+
+ return chain.proceed(requestWithToken)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/network/NetworkModule.kt b/data/src/main/java/pokitmons/pokit/data/di/network/NetworkModule.kt
new file mode 100644
index 00000000..dc080a1c
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/network/NetworkModule.kt
@@ -0,0 +1,171 @@
+package pokitmons.pokit.data.di.network
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import pokitmons.pokit.data.api.AlertApi
+import pokitmons.pokit.data.api.AuthApi
+import pokitmons.pokit.data.api.LinkApi
+import pokitmons.pokit.data.api.PokitApi
+import pokitmons.pokit.data.api.RemindApi
+import pokitmons.pokit.data.api.SettingApi
+import pokitmons.pokit.data.api.TokenApi
+import pokitmons.pokit.data.datasource.local.AuthAuthenticator
+import pokitmons.pokit.data.datasource.local.TokenManager
+import retrofit2.Retrofit
+import java.util.concurrent.TimeUnit
+import javax.inject.Named
+import javax.inject.Singleton
+
+private const val BASE_URL = "https://pokit.site"
+private const val API = "api"
+private const val VERSION = "v1"
+
+private const val READ_TIME_OUT = 20000L
+private const val WRITE_TIME_OUT = 20000L
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+ @Named("BaseOkHttpClient")
+ @Singleton
+ @Provides
+ fun provideOkHttpClient(
+ interceptor: BearerTokenInterceptor,
+ ): OkHttpClient {
+ return OkHttpClient.Builder()
+ .addInterceptor(interceptor)
+ .addInterceptor(
+ HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+ )
+ .readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)
+ .writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)
+ .build()
+ }
+
+ @Named("ReissueOkHttpClient")
+ @Singleton
+ @Provides
+ fun provideReissueOkHttpClient(
+ authAuthenticator: AuthAuthenticator,
+ interceptor: BearerTokenInterceptor,
+ ): OkHttpClient {
+ return OkHttpClient.Builder()
+ .addInterceptor(interceptor)
+ .authenticator(authAuthenticator)
+ .addInterceptor(
+ HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+ )
+ .readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)
+ .writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS)
+ .build()
+ }
+
+ @Singleton
+ @Provides
+ fun provideTokenManager(dataStore: DataStore): TokenManager {
+ return TokenManager(dataStore)
+ }
+
+ @Singleton
+ @Provides
+ fun provideAuthenticator(
+ tokenManager: TokenManager,
+ tokenApi: TokenApi,
+ ): AuthAuthenticator {
+ return AuthAuthenticator(tokenManager, tokenApi)
+ }
+
+ @Singleton
+ @Provides
+ fun provideInterceptor(tokenManager: TokenManager): BearerTokenInterceptor {
+ return BearerTokenInterceptor(tokenManager)
+ }
+
+ @Singleton
+ @Provides
+ fun provideJson(): Json {
+ return Json {
+ ignoreUnknownKeys = true
+ coerceInputValues = true
+ prettyPrint = true
+ }
+ }
+
+ @Named("ReissueRetrofit")
+ @Singleton
+ @Provides
+ fun provideReissueRetrofit(
+ @Named("ReissueOkHttpClient") okHttpClient: OkHttpClient,
+ json: Json,
+ ): Retrofit {
+ val converterFactory = json.asConverterFactory("application/json; charset=UTF8".toMediaType())
+ return Retrofit.Builder()
+ .baseUrl("$BASE_URL/$API/$VERSION/")
+ .addConverterFactory(converterFactory)
+ .client(okHttpClient)
+ .build()
+ }
+
+ @Named("BaseRetrofit")
+ @Singleton
+ @Provides
+ fun provideRetrofit(
+ @Named("BaseOkHttpClient") okHttpClient: OkHttpClient,
+ json: Json,
+ ): Retrofit {
+ val converterFactory = json.asConverterFactory("application/json; charset=UTF8".toMediaType())
+ return Retrofit.Builder()
+ .baseUrl("$BASE_URL/$API/$VERSION/")
+ .addConverterFactory(converterFactory)
+ .client(okHttpClient)
+ .build()
+ }
+
+ @Provides
+ fun provideAuthService(@Named("BaseRetrofit") retrofit: Retrofit): AuthApi {
+ return retrofit.create(AuthApi::class.java)
+ }
+
+ @Provides
+ fun provideTokenService(@Named("ReissueRetrofit") retrofit: Retrofit): TokenApi {
+ return retrofit.create(TokenApi::class.java)
+ }
+
+ @Provides
+ fun providePokitService(@Named("BaseRetrofit") retrofit: Retrofit): PokitApi {
+ return retrofit.create(PokitApi::class.java)
+ }
+
+ @Provides
+ fun provideLinkService(@Named("BaseRetrofit") retrofit: Retrofit): LinkApi {
+ return retrofit.create(LinkApi::class.java)
+ }
+
+ @Provides
+ fun provideSettingService(@Named("BaseRetrofit") retrofit: Retrofit): SettingApi {
+ return retrofit.create(SettingApi::class.java)
+ }
+
+ @Provides
+ fun provideRemindService(@Named("BaseRetrofit") retrofit: Retrofit): RemindApi {
+ return retrofit.create(RemindApi::class.java)
+ }
+
+ @Provides
+ fun provideAlertService(@Named("BaseRetrofit") retrofit: Retrofit): AlertApi {
+ return retrofit.create(AlertApi::class.java)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/pokit/PokitModule.kt b/data/src/main/java/pokitmons/pokit/data/di/pokit/PokitModule.kt
new file mode 100644
index 00000000..32c1014a
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/pokit/PokitModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.pokit
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.remote.pokit.PokitDataSource
+import pokitmons.pokit.data.datasource.remote.pokit.RemotePokitDataSource
+import pokitmons.pokit.data.repository.pokit.PokitRepositoryImpl
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class PokitModule {
+ @Binds
+ @Singleton
+ abstract fun bindPokitRepository(pokitRepositoryImpl: PokitRepositoryImpl): PokitRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindPokitDataSource(pokitDataSourceImpl: RemotePokitDataSource): PokitDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/search/SearchModule.kt b/data/src/main/java/pokitmons/pokit/data/di/search/SearchModule.kt
new file mode 100644
index 00000000..dbd7a4a4
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/search/SearchModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.search
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.local.search.LocalSearchWordDataSource
+import pokitmons.pokit.data.datasource.local.search.SearchDataSource
+import pokitmons.pokit.data.repository.search.SearchRepositoryImpl
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class SearchModule {
+ @Binds
+ @Singleton
+ abstract fun bindSearchRepository(searchRepositoryImpl: SearchRepositoryImpl): SearchRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindSearchDataSource(searchDataSourceImpl: LocalSearchWordDataSource): SearchDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/di/setting/SettingModule.kt b/data/src/main/java/pokitmons/pokit/data/di/setting/SettingModule.kt
new file mode 100644
index 00000000..ec050258
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/di/setting/SettingModule.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.di.setting
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import pokitmons.pokit.data.datasource.remote.setting.RemoteSettingDataSourceImpl
+import pokitmons.pokit.data.datasource.remote.setting.SettingDataSource
+import pokitmons.pokit.data.repository.setting.SettingRepositoryImpl
+import pokitmons.pokit.domain.repository.setting.SettingRepository
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class SettingModule {
+ @Binds
+ @Singleton
+ abstract fun bindSettingRepository(settingRepositoryImpl: SettingRepositoryImpl): SettingRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindSettingDataSource(settingDataSourceImpl: RemoteSettingDataSourceImpl): SettingDataSource
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/alert/AlertMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/alert/AlertMapper.kt
new file mode 100644
index 00000000..196af500
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/mapper/alert/AlertMapper.kt
@@ -0,0 +1,18 @@
+package pokitmons.pokit.data.mapper.alert
+
+import pokitmons.pokit.data.model.alert.GetAlertsResponse
+import pokitmons.pokit.domain.model.alert.Alarm
+
+object AlertMapper {
+ fun mapperToAlarmList(response: GetAlertsResponse): List {
+ return response.data.map { data ->
+ Alarm(
+ id = data.id,
+ contentId = data.contentId,
+ thumbnail = data.thumbNail,
+ title = data.title,
+ createdAt = data.createdAt
+ )
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/auth/AuthMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/auth/AuthMapper.kt
new file mode 100644
index 00000000..ebee74f7
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/mapper/auth/AuthMapper.kt
@@ -0,0 +1,32 @@
+package pokitmons.pokit.data.mapper.auth
+
+import pokitmons.pokit.data.model.auth.response.DuplicateNicknameResponse
+import pokitmons.pokit.data.model.auth.response.SNSLoginResponse
+import pokitmons.pokit.data.model.auth.response.SignUpResponse
+import pokitmons.pokit.domain.model.auth.DuplicateNicknameResult
+import pokitmons.pokit.domain.model.auth.SNSLoginResult
+import pokitmons.pokit.domain.model.auth.SignUpResult
+
+object AuthMapper {
+ fun mapperToSNSLogin(snsLoginResponse: SNSLoginResponse): SNSLoginResult {
+ return SNSLoginResult(
+ accessToken = snsLoginResponse.accessToken,
+ refreshToken = snsLoginResponse.refreshToken,
+ isRegistered = snsLoginResponse.isRegistered
+ )
+ }
+
+ fun mapperToDuplicateNickname(checkDuplicateNicknameResponse: DuplicateNicknameResponse): DuplicateNicknameResult {
+ return DuplicateNicknameResult(
+ isDuplicate = checkDuplicateNicknameResponse.isDuplicate
+ )
+ }
+
+ fun mapperToSignUp(signUpResponse: SignUpResponse): SignUpResult {
+ return SignUpResult(
+ id = signUpResponse.id,
+ email = signUpResponse.email,
+ nickname = signUpResponse.nickname
+ )
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt
new file mode 100644
index 00000000..f798c2db
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/mapper/home/home/RemindMapper.kt
@@ -0,0 +1,35 @@
+package pokitmons.pokit.data.mapper.home.home
+
+import pokitmons.pokit.data.model.home.remind.Remind
+import pokitmons.pokit.data.model.home.remind.RemindResponse
+import pokitmons.pokit.domain.model.home.remind.RemindResult
+
+object RemindMapper {
+ fun mapperToRemind(remindResponse: RemindResponse): List {
+ return remindResponse.data.map { remind ->
+ RemindResult(
+ id = remind.contentId,
+ title = remind.title,
+ domain = remind.domain,
+ createdAt = remind.createdAt,
+ isRead = remind.isRead,
+ thumbNail = remind.thumbNail,
+ data = remind.data
+ )
+ }
+ }
+
+ fun mapperToTodayContents(remindResponse: List): List {
+ return remindResponse.map { remind ->
+ RemindResult(
+ id = remind.contentId,
+ title = remind.title,
+ domain = remind.domain,
+ createdAt = remind.createdAt,
+ isRead = remind.isRead,
+ thumbNail = remind.thumbNail,
+ data = remind.data
+ )
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt
new file mode 100644
index 00000000..b7ead641
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/mapper/link/LinkMapper.kt
@@ -0,0 +1,56 @@
+package pokitmons.pokit.data.mapper.link
+
+import pokitmons.pokit.data.model.link.response.GetLinkResponse
+import pokitmons.pokit.data.model.link.response.GetLinksResponse
+import pokitmons.pokit.data.model.link.response.ModifyLinkResponse
+import pokitmons.pokit.domain.model.link.Link
+
+object LinkMapper {
+ fun mapperToLinks(linksResponse: GetLinksResponse): List {
+ return linksResponse.data.map { data ->
+ Link(
+ id = data.contentId,
+ categoryId = data.category.categoryId,
+ categoryName = data.category.categoryName,
+ data = data.data,
+ domain = data.domain,
+ title = data.title,
+ memo = data.memo,
+ alertYn = data.alertYn,
+ createdAt = data.createdAt,
+ isRead = data.isRead,
+ thumbnail = data.thumbNail
+ )
+ }
+ }
+
+ fun mapperToLink(linkResponse: GetLinkResponse): Link {
+ return Link(
+ id = linkResponse.contentId,
+ categoryId = linkResponse.category.categoryId,
+ categoryName = linkResponse.category.categoryName,
+ data = linkResponse.data,
+ domain = "",
+ title = linkResponse.title,
+ memo = linkResponse.memo,
+ alertYn = linkResponse.alertYn,
+ createdAt = linkResponse.createdAt,
+ favorites = linkResponse.favorites
+ )
+ }
+
+ fun mapperToLink(modifyLinkResponse: ModifyLinkResponse): Link {
+ return Link(
+ id = modifyLinkResponse.contentId,
+ categoryId = modifyLinkResponse.category.categoryId,
+ categoryName = modifyLinkResponse.category.categoryName,
+ data = modifyLinkResponse.data,
+ domain = "",
+ title = modifyLinkResponse.title,
+ memo = modifyLinkResponse.memo,
+ alertYn = modifyLinkResponse.alertYn,
+ createdAt = modifyLinkResponse.createdAt,
+ favorites = modifyLinkResponse.favorites
+ )
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/pokit/PokitMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/pokit/PokitMapper.kt
new file mode 100644
index 00000000..fdeddcbd
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/mapper/pokit/PokitMapper.kt
@@ -0,0 +1,46 @@
+package pokitmons.pokit.data.mapper.pokit
+
+import pokitmons.pokit.data.model.pokit.response.GetPokitImagesResponseItem
+import pokitmons.pokit.data.model.pokit.response.GetPokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitsResponse
+import pokitmons.pokit.domain.model.pokit.Pokit
+
+object PokitMapper {
+ private const val NOT_USE = 0
+
+ fun mapperToPokits(pokitsResponse: GetPokitsResponse): List {
+ return pokitsResponse.data.map { data ->
+ Pokit(
+ categoryId = data.categoryId,
+ userId = data.userId,
+ name = data.categoryName,
+ image = Pokit.Image(
+ id = data.categoryImage.imageId,
+ url = data.categoryImage.imageUrl
+ ),
+ createdAt = data.createdAt,
+ linkCount = data.contentCount
+ )
+ }
+ }
+
+ fun mapperToPokitImages(getPokitImagesResponse: List): List {
+ return getPokitImagesResponse.map { image ->
+ Pokit.Image(id = image.imageId, url = image.imageUrl)
+ }
+ }
+
+ fun mapperToPokit(pokitResponse: GetPokitResponse): Pokit {
+ return Pokit(
+ categoryId = pokitResponse.categoryId,
+ userId = NOT_USE,
+ name = pokitResponse.categoryName,
+ image = Pokit.Image(
+ id = pokitResponse.categoryImage.imageId,
+ url = pokitResponse.categoryImage.imageUrl
+ ),
+ linkCount = NOT_USE,
+ createdAt = pokitResponse.createdAt
+ )
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/mapper/setting/SettingMapper.kt b/data/src/main/java/pokitmons/pokit/data/mapper/setting/SettingMapper.kt
new file mode 100644
index 00000000..3080207b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/mapper/setting/SettingMapper.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.data.mapper.setting
+
+import pokitmons.pokit.data.model.setting.response.EditNicknameResponse
+import pokitmons.pokit.domain.model.setting.EditNicknameResult
+
+object SettingMapper {
+ fun mapperToEditNickname(editNicknameResponse: EditNicknameResponse): EditNicknameResult {
+ return EditNicknameResult(
+ id = editNicknameResponse.id,
+ nickname = editNicknameResponse.nickname,
+ email = editNicknameResponse.email
+ )
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/alert/GetAlertsResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/alert/GetAlertsResponse.kt
new file mode 100644
index 00000000..e55692a1
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/alert/GetAlertsResponse.kt
@@ -0,0 +1,32 @@
+package pokitmons.pokit.data.model.alert
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetAlertsResponse(
+ val data: List = emptyList(),
+ val page: Int = 0,
+ val size: Int = 10,
+ val sort: List = emptyList(),
+ val hasNext: Boolean = true,
+) {
+ @Serializable
+ data class Data(
+ val id: Int,
+ val userId: Int,
+ val contentId: Int,
+ val thumbNail: String,
+ val title: String,
+ val body: String,
+ val createdAt: String,
+ )
+
+ @Serializable
+ data class Sort(
+ val direction: String,
+ val nullHandling: String,
+ val ascending: Boolean,
+ val property: String,
+ val ignoreCase: Boolean,
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/request/SNSLoginRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/request/SNSLoginRequest.kt
new file mode 100644
index 00000000..e52e5e4a
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/request/SNSLoginRequest.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.auth.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SNSLoginRequest(
+ val authPlatform: String,
+ val idToken: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/request/SignUpRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/request/SignUpRequest.kt
new file mode 100644
index 00000000..e1ddc46b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/request/SignUpRequest.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.auth.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignUpRequest(
+ val nickName: String,
+ val interests: List,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/request/TokenRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/request/TokenRequest.kt
new file mode 100644
index 00000000..d3a245da
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/request/TokenRequest.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.data.model.auth.request
+
+data class TokenRequest(
+ val refreshToken: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/request/WithdrawRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/request/WithdrawRequest.kt
new file mode 100644
index 00000000..1cdc5c7f
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/request/WithdrawRequest.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.auth.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class WithdrawRequest(
+ val refreshToken: String,
+ val authPlatform: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/response/DuplicateNicknameResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/response/DuplicateNicknameResponse.kt
new file mode 100644
index 00000000..3638dda8
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/response/DuplicateNicknameResponse.kt
@@ -0,0 +1,8 @@
+package pokitmons.pokit.data.model.auth.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class DuplicateNicknameResponse(
+ val isDuplicate: Boolean,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/response/SNSLoginResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/response/SNSLoginResponse.kt
new file mode 100644
index 00000000..963f69a7
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/response/SNSLoginResponse.kt
@@ -0,0 +1,10 @@
+package pokitmons.pokit.data.model.auth.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SNSLoginResponse(
+ val accessToken: String,
+ val refreshToken: String,
+ val isRegistered: Boolean,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/response/SignUpResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/response/SignUpResponse.kt
new file mode 100644
index 00000000..c86dc03a
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/response/SignUpResponse.kt
@@ -0,0 +1,10 @@
+package pokitmons.pokit.data.model.auth.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SignUpResponse(
+ val id: Int,
+ val email: String,
+ val nickname: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/auth/response/TokenResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/auth/response/TokenResponse.kt
new file mode 100644
index 00000000..fd780f1b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/auth/response/TokenResponse.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.data.model.auth.response
+
+data class TokenResponse(
+ val accessToken: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/common/ParseErrorResult.kt b/data/src/main/java/pokitmons/pokit/data/model/common/ParseErrorResult.kt
new file mode 100644
index 00000000..878e1220
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/common/ParseErrorResult.kt
@@ -0,0 +1,18 @@
+package pokitmons.pokit.data.model.common
+
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import pokitmons.pokit.domain.commom.PokitError
+import pokitmons.pokit.domain.commom.PokitResult
+
+fun parseErrorResult(throwable: Throwable): PokitResult {
+ return try {
+ val error: PokitErrorResponse = throwable.message?.let { errorBody ->
+ Json.decodeFromString(errorBody)
+ } ?: PokitErrorResponse()
+ val pokitError = PokitError(message = error.message, code = error.code)
+ PokitResult.Error(pokitError)
+ } catch (e: Exception) {
+ PokitResult.Error(PokitError())
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/common/PokitErrorResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/common/PokitErrorResponse.kt
new file mode 100644
index 00000000..93f89d8c
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/common/PokitErrorResponse.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.common
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PokitErrorResponse(
+ val message: String = "그 외",
+ val code: String = "U_0000",
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/home/remind/Category.kt b/data/src/main/java/pokitmons/pokit/data/model/home/remind/Category.kt
new file mode 100644
index 00000000..5c119e7b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/home/remind/Category.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.home.remind
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Category(
+ val categoryId: Int,
+ val categoryName: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/home/remind/Remind.kt b/data/src/main/java/pokitmons/pokit/data/model/home/remind/Remind.kt
new file mode 100644
index 00000000..b1f8c21e
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/home/remind/Remind.kt
@@ -0,0 +1,15 @@
+package pokitmons.pokit.data.model.home.remind
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Remind(
+ val category: Category,
+ val contentId: Int,
+ val createdAt: String,
+ val data: String,
+ val domain: String,
+ val isRead: Boolean = false, // today의 경우 isRead가 빠져서 오기에, 기본값 설정 필요
+ val thumbNail: String,
+ val title: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/home/remind/RemindRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/home/remind/RemindRequest.kt
new file mode 100644
index 00000000..6aec9476
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/home/remind/RemindRequest.kt
@@ -0,0 +1,11 @@
+package pokitmons.pokit.data.model.home.remind
+
+import kotlinx.serialization.Serializable
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+
+@Serializable
+data class RemindRequest(
+ val size: Int = 10,
+ val page: Int = 0,
+ val sort: PokitsSort = PokitsSort.RECENT,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/home/remind/RemindResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/home/remind/RemindResponse.kt
new file mode 100644
index 00000000..d2789fbf
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/home/remind/RemindResponse.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.data.model.home.remind
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RemindResponse(
+ val data: List = emptyList(),
+ val hasNext: Boolean = false,
+ val page: Int = 0,
+ val size: Int = 10,
+ val sort: List = emptyList(),
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/home/remind/Sort.kt b/data/src/main/java/pokitmons/pokit/data/model/home/remind/Sort.kt
new file mode 100644
index 00000000..48e4f07b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/home/remind/Sort.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.data.model.home.remind
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Sort(
+ val ascending: Boolean,
+ val direction: String,
+ val ignoreCase: Boolean,
+ val nullHandling: String,
+ val property: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt
new file mode 100644
index 00000000..508ed63b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/link/request/ModifyLinkRequest.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.data.model.link.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ModifyLinkRequest(
+ val data: String,
+ val title: String,
+ val categoryId: Int,
+ val memo: String,
+ val alertYn: String,
+ val thumbNail: String?,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/response/ApplyBookmarkResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/link/response/ApplyBookmarkResponse.kt
new file mode 100644
index 00000000..bf54df13
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/link/response/ApplyBookmarkResponse.kt
@@ -0,0 +1,6 @@
+package pokitmons.pokit.data.model.link.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ApplyBookmarkResponse(val contentId: Int = 0)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt
new file mode 100644
index 00000000..514e3e76
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinkResponse.kt
@@ -0,0 +1,21 @@
+package pokitmons.pokit.data.model.link.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetLinkResponse(
+ val contentId: Int = 0,
+ val category: Category = Category(),
+ val data: String = "",
+ val title: String = "",
+ val memo: String = "",
+ val alertYn: String = "",
+ val createdAt: String = "",
+ val favorites: Boolean = true,
+) {
+ @Serializable
+ data class Category(
+ val categoryId: Int = 0,
+ val categoryName: String = "",
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinksResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinksResponse.kt
new file mode 100644
index 00000000..1b7f2320
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/link/response/GetLinksResponse.kt
@@ -0,0 +1,41 @@
+package pokitmons.pokit.data.model.link.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetLinksResponse(
+ val data: List = emptyList(),
+ val page: Int = 0,
+ val size: Int = 10,
+ val sort: List = emptyList(),
+ val hasNext: Boolean = true,
+) {
+ @Serializable
+ data class Data(
+ val contentId: Int,
+ val category: Category,
+ val data: String,
+ val domain: String,
+ val title: String,
+ val memo: String,
+ val alertYn: String,
+ val createdAt: String,
+ val isRead: Boolean,
+ val thumbNail: String,
+ )
+
+ @Serializable
+ data class Category(
+ val categoryId: Int,
+ val categoryName: String,
+ )
+
+ @Serializable
+ data class Sort(
+ val direction: String,
+ val nullHandling: String,
+ val ascending: Boolean,
+ val property: String,
+ val ignoreCase: Boolean,
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/response/LinkCardResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/link/response/LinkCardResponse.kt
new file mode 100644
index 00000000..41abd42d
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/link/response/LinkCardResponse.kt
@@ -0,0 +1,7 @@
+package pokitmons.pokit.data.model.link.response
+
+data class LinkCardResponse(
+ val url: String = "",
+ val title: String = "",
+ val image: String? = null,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/link/response/ModifyLinkResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/link/response/ModifyLinkResponse.kt
new file mode 100644
index 00000000..4f5436b1
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/link/response/ModifyLinkResponse.kt
@@ -0,0 +1,21 @@
+package pokitmons.pokit.data.model.link.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ModifyLinkResponse(
+ val contentId: Int = 0,
+ val category: Category = Category(),
+ val data: String = "",
+ val title: String = "",
+ val memo: String = "",
+ val alertYn: String = "",
+ val createdAt: String = "",
+ val favorites: Boolean = true,
+) {
+ @Serializable
+ data class Category(
+ val categoryId: Int = 0,
+ val categoryName: String = "",
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/request/CreatePokitRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/request/CreatePokitRequest.kt
new file mode 100644
index 00000000..93cf3847
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/request/CreatePokitRequest.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.pokit.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class CreatePokitRequest(
+ val categoryName: String = "",
+ val categoryImageId: Int = 0,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/request/GetPokitsRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/request/GetPokitsRequest.kt
new file mode 100644
index 00000000..0142195b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/request/GetPokitsRequest.kt
@@ -0,0 +1,10 @@
+package pokitmons.pokit.data.model.pokit.request
+
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+
+data class GetPokitsRequest(
+ val filterUncategoriezd: Boolean = true,
+ val size: Int = 10,
+ val page: Int = 0,
+ val sort: PokitsSort = PokitsSort.RECENT,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/request/ModifyPokitRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/request/ModifyPokitRequest.kt
new file mode 100644
index 00000000..79bce0ef
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/request/ModifyPokitRequest.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.pokit.request
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ModifyPokitRequest(
+ val categoryName: String = "",
+ val categoryImageId: Int = 0,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/response/CreatePokitResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/CreatePokitResponse.kt
new file mode 100644
index 00000000..513a2bda
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/CreatePokitResponse.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.data.model.pokit.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class CreatePokitResponse(
+ val categoryId: Int = 0,
+ val categoryName: String = "",
+ val categoryImage: Image = Image(),
+) {
+ @Serializable
+ data class Image(
+ val imageId: Int = 0,
+ val imageUrl: String = "",
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitCountResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitCountResponse.kt
new file mode 100644
index 00000000..bd49c046
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitCountResponse.kt
@@ -0,0 +1,8 @@
+package pokitmons.pokit.data.model.pokit.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetPokitCountResponse(
+ val categoryTotalCount: Int = 0,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitImagesResponseItem.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitImagesResponseItem.kt
new file mode 100644
index 00000000..a0e89792
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitImagesResponseItem.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.data.model.pokit.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetPokitImagesResponseItem(
+ val imageId: Int,
+ val imageUrl: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitResponse.kt
new file mode 100644
index 00000000..2ea6ccb3
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitResponse.kt
@@ -0,0 +1,17 @@
+package pokitmons.pokit.data.model.pokit.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetPokitResponse(
+ val categoryId: Int = 0,
+ val categoryName: String = "",
+ val categoryImage: Image = Image(),
+ val createdAt: String = "",
+) {
+ @Serializable
+ data class Image(
+ val imageId: Int = 0,
+ val imageUrl: String = "",
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitsResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitsResponse.kt
new file mode 100644
index 00000000..87790c1b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/GetPokitsResponse.kt
@@ -0,0 +1,37 @@
+package pokitmons.pokit.data.model.pokit.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class GetPokitsResponse(
+ val data: List = emptyList(),
+ val page: Int = 10,
+ val size: Int = 0,
+ val sort: List = emptyList(),
+ val hasNext: Boolean = false,
+) {
+ @Serializable
+ data class Data(
+ val categoryId: Int,
+ val userId: Int,
+ val categoryName: String,
+ val categoryImage: PokitImage,
+ val contentCount: Int,
+ val createdAt: String,
+ )
+
+ @Serializable
+ data class PokitImage(
+ val imageId: Int,
+ val imageUrl: String,
+ )
+
+ @Serializable
+ data class Sort(
+ val direction: String,
+ val nullHandling: String,
+ val ascending: Boolean,
+ val property: String,
+ val ignoreCase: Boolean,
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/pokit/response/ModifyPokitResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/ModifyPokitResponse.kt
new file mode 100644
index 00000000..81c9293b
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/pokit/response/ModifyPokitResponse.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.data.model.pokit.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ModifyPokitResponse(
+ val categoryId: Int = 0,
+ val categoryName: String = "",
+ val categoryImage: Image = Image(),
+) {
+ @Serializable
+ data class Image(
+ val imageId: Int = 0,
+ val imageUrl: String = "",
+ )
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/model/setting/reqeust/EditNicknameRequest.kt b/data/src/main/java/pokitmons/pokit/data/model/setting/reqeust/EditNicknameRequest.kt
new file mode 100644
index 00000000..edfa5e55
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/setting/reqeust/EditNicknameRequest.kt
@@ -0,0 +1,8 @@
+package pokitmons.pokit.data.model.setting.reqeust
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class EditNicknameRequest(
+ val nickname: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/model/setting/response/EditNicknameResponse.kt b/data/src/main/java/pokitmons/pokit/data/model/setting/response/EditNicknameResponse.kt
new file mode 100644
index 00000000..75fdd27e
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/model/setting/response/EditNicknameResponse.kt
@@ -0,0 +1,10 @@
+package pokitmons.pokit.data.model.setting.response
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class EditNicknameResponse(
+ val email: String,
+ val id: Int,
+ val nickname: String,
+)
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/alert/AlertRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/alert/AlertRepositoryImpl.kt
new file mode 100644
index 00000000..258b9ed0
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/alert/AlertRepositoryImpl.kt
@@ -0,0 +1,32 @@
+package pokitmons.pokit.data.repository.alert
+
+import pokitmons.pokit.data.datasource.remote.alert.AlertDataSource
+import pokitmons.pokit.data.mapper.alert.AlertMapper
+import pokitmons.pokit.data.model.common.parseErrorResult
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.alert.Alarm
+import pokitmons.pokit.domain.repository.alert.AlertRepository
+import javax.inject.Inject
+
+class AlertRepositoryImpl @Inject constructor(
+ private val dataSource: AlertDataSource,
+) : AlertRepository {
+ override suspend fun getAlerts(page: Int, size: Int): PokitResult> {
+ return runCatching {
+ val response = dataSource.getAlerts(page = page, size = size)
+ val mappedResponse = AlertMapper.mapperToAlarmList(response)
+ PokitResult.Success(result = mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun deleteAlert(alertId: Int): PokitResult {
+ return runCatching {
+ dataSource.deleteAlert(alertId)
+ PokitResult.Success(result = Unit)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/auth/AuthRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/auth/AuthRepositoryImpl.kt
new file mode 100644
index 00000000..f8975ea5
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/auth/AuthRepositoryImpl.kt
@@ -0,0 +1,88 @@
+package pokitmons.pokit.data.repository.auth
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import pokitmons.pokit.data.datasource.local.TokenManager
+import pokitmons.pokit.data.datasource.remote.auth.AuthDataSource
+import pokitmons.pokit.data.mapper.auth.AuthMapper
+import pokitmons.pokit.data.model.auth.request.SNSLoginRequest
+import pokitmons.pokit.data.model.auth.request.SignUpRequest
+import pokitmons.pokit.data.model.auth.request.WithdrawRequest
+import pokitmons.pokit.data.model.auth.response.DuplicateNicknameResponse
+import pokitmons.pokit.data.model.auth.response.SNSLoginResponse
+import pokitmons.pokit.data.model.auth.response.SignUpResponse
+import pokitmons.pokit.data.model.common.parseErrorResult
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.auth.DuplicateNicknameResult
+import pokitmons.pokit.domain.model.auth.SNSLoginResult
+import pokitmons.pokit.domain.model.auth.SignUpResult
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class AuthRepositoryImpl @Inject constructor(
+ private val remoteAuthDataSource: AuthDataSource,
+ private val tokenManager: TokenManager,
+) : AuthRepository {
+ override suspend fun snsLogin(
+ authPlatform: String,
+ idToken: String,
+ ): PokitResult {
+ return runCatching {
+ val snsLoginResponse: SNSLoginResponse = remoteAuthDataSource.snsLogin(SNSLoginRequest(authPlatform = authPlatform, idToken = idToken))
+ val snsLoginMapper = AuthMapper.mapperToSNSLogin(snsLoginResponse)
+ PokitResult.Success(snsLoginMapper)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun checkDuplicateNickname(nickname: String): PokitResult {
+ return runCatching {
+ val checkDuplicateNicknameResponse: DuplicateNicknameResponse = remoteAuthDataSource.checkDuplicateNickname(nickname)
+ val checkDuplicateMapper = AuthMapper.mapperToDuplicateNickname(checkDuplicateNicknameResponse)
+ PokitResult.Success(checkDuplicateMapper)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun signUp(nickname: String, categories: List): PokitResult {
+ return runCatching {
+ val signUpResponse: SignUpResponse = remoteAuthDataSource.signUp(SignUpRequest(nickName = nickname, interests = categories))
+ val signUpMapper: SignUpResult = AuthMapper.mapperToSignUp(signUpResponse)
+ PokitResult.Success(signUpMapper)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun withdraw(): PokitResult {
+ return runCatching {
+ remoteAuthDataSource.withdraw(
+ WithdrawRequest(
+ refreshToken = "",
+ authPlatform = tokenManager.getAuthType().first()
+ )
+ )
+ PokitResult.Success(Unit)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun setAccessToken(token: String) {
+ tokenManager.saveAccessToken(token)
+ }
+
+ override suspend fun setRefreshToken(token: String) {
+ tokenManager.saveRefreshToken(token)
+ }
+
+ override suspend fun setAuthType(type: String) {
+ tokenManager.setAuthType(type)
+ }
+
+ override suspend fun getAuthType(): Flow {
+ return tokenManager.getAuthType()
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/home/remind/RemindRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/home/remind/RemindRepositoryImpl.kt
new file mode 100644
index 00000000..5cf6ddce
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/home/remind/RemindRepositoryImpl.kt
@@ -0,0 +1,58 @@
+package pokitmons.pokit.data.repository.home.remind
+
+import pokitmons.pokit.data.datasource.remote.home.remind.RemindDataSource
+import pokitmons.pokit.data.mapper.home.home.RemindMapper
+import pokitmons.pokit.data.model.common.parseErrorResult
+import pokitmons.pokit.data.model.home.remind.RemindRequest
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.home.remind.RemindResult
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+import pokitmons.pokit.domain.repository.home.remind.RemindRepository
+import javax.inject.Inject
+
+class RemindRepositoryImpl @Inject constructor(private val remindDataSource: RemindDataSource) : RemindRepository {
+ override suspend fun getUnReadContents(
+ filterUncategorized: Boolean,
+ size: Int,
+ page: Int,
+ sort: PokitsSort,
+ ): PokitResult> {
+ return runCatching {
+ val response = remindDataSource.getUnreadContents(RemindRequest())
+ val remindResponse = RemindMapper.mapperToRemind(response)
+ PokitResult.Success(remindResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getTodayContents(
+ filterUncategorized: Boolean,
+ size: Int,
+ page: Int,
+ sort: PokitsSort,
+ ): PokitResult> {
+ return runCatching {
+ val response = remindDataSource.getTodayContents(RemindRequest())
+ val remindResponse = RemindMapper.mapperToTodayContents(response)
+ PokitResult.Success(remindResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getBookmarkContents(
+ filterUncategorized: Boolean,
+ size: Int,
+ page: Int,
+ sort: PokitsSort,
+ ): PokitResult> {
+ return runCatching {
+ val response = remindDataSource.getBookmarkContents(RemindRequest())
+ val remindResponse = RemindMapper.mapperToRemind(response)
+ PokitResult.Success(remindResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt
new file mode 100644
index 00000000..2bb81de1
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/link/LinkRepositoryImpl.kt
@@ -0,0 +1,172 @@
+package pokitmons.pokit.data.repository.link
+
+import pokitmons.pokit.data.datasource.remote.link.LinkDataSource
+import pokitmons.pokit.data.mapper.link.LinkMapper
+import pokitmons.pokit.data.model.common.parseErrorResult
+import pokitmons.pokit.data.model.link.request.ModifyLinkRequest
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.model.link.LinkCard
+import pokitmons.pokit.domain.model.link.LinksSort
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class LinkRepositoryImpl @Inject constructor(
+ private val dataSource: LinkDataSource,
+) : LinkRepository {
+ override suspend fun getLinks(
+ categoryId: Int,
+ size: Int,
+ page: Int,
+ sort: LinksSort,
+ isRead: Boolean?,
+ favorite: Boolean?,
+ startDate: String?,
+ endDate: String?,
+ categoryIds: List?,
+ ): PokitResult> {
+ return runCatching {
+ val response = dataSource.getLinks(
+ categoryId = categoryId,
+ size = size,
+ page = page,
+ sort = listOf(sort.value),
+ isRead = isRead,
+ favorites = favorite,
+ startDate = startDate,
+ endDate = endDate,
+ categoryIds = categoryIds
+ )
+ val mappedResponse = LinkMapper.mapperToLinks(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun searchLinks(
+ page: Int,
+ size: Int,
+ sort: List,
+ isRead: Boolean?,
+ favorites: Boolean?,
+ startDate: String?,
+ endDate: String?,
+ categoryIds: List?,
+ searchWord: String,
+ ): PokitResult> {
+ return runCatching {
+ val response = dataSource.searchLinks(
+ page = page,
+ size = size,
+ sort = sort,
+ isRead = isRead,
+ favorites = favorites,
+ startDate = startDate,
+ endDate = endDate,
+ categoryIds = categoryIds,
+ searchWord = searchWord
+ )
+ val mappedResponse = LinkMapper.mapperToLinks(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun deleteLink(linkId: Int): PokitResult {
+ return runCatching {
+ dataSource.deleteLink(linkId)
+ PokitResult.Success(linkId)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getLink(linkId: Int): PokitResult {
+ return runCatching {
+ val response = dataSource.getLink(linkId)
+ val mappedResponse = LinkMapper.mapperToLink(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun modifyLink(
+ linkId: Int,
+ data: String,
+ title: String,
+ categoryId: Int,
+ memo: String,
+ alertYn: String,
+ thumbNail: String,
+ ): PokitResult {
+ return runCatching {
+ val modifyLinkRequest = ModifyLinkRequest(
+ data = data,
+ title = title,
+ categoryId = categoryId,
+ memo = memo,
+ alertYn = alertYn,
+ thumbNail = thumbNail.ifEmpty { null }
+ )
+ val response = dataSource.modifyLink(contentId = linkId, modifyLinkRequest = modifyLinkRequest)
+ val mappedResponse = LinkMapper.mapperToLink(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun createLink(data: String, title: String, categoryId: Int, memo: String, alertYn: String, thumbNail: String): PokitResult {
+ return runCatching {
+ val createLinkRequest = ModifyLinkRequest(
+ data = data,
+ title = title,
+ categoryId = categoryId,
+ memo = memo,
+ alertYn = alertYn,
+ thumbNail = thumbNail.ifEmpty { null }
+ )
+ val response = dataSource.createLink(createLinkRequest = createLinkRequest)
+ val mappedResponse = LinkMapper.mapperToLink(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun setBookmark(linkId: Int, bookmarked: Boolean): PokitResult {
+ return runCatching {
+ dataSource.setBookmark(contentId = linkId, bookmarked = bookmarked)
+ PokitResult.Success(Unit)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getLinkCard(url: String): PokitResult {
+ return runCatching {
+ val response = dataSource.getLinkCard(url)
+ val mappedResponse = LinkCard(
+ url = response.url,
+ title = response.title,
+ thumbnailUrl = response.image
+ )
+ PokitResult.Success(result = mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getUncategorizedLinks(size: Int, page: Int, sort: LinksSort): PokitResult> {
+ return runCatching {
+ val response = dataSource.getUncategorizedLinks(size = size, page = page, sort = listOf(sort.value))
+ val mappedResponse = LinkMapper.mapperToLinks(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/pokit/PokitRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/pokit/PokitRepositoryImpl.kt
new file mode 100644
index 00000000..3ea46299
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/pokit/PokitRepositoryImpl.kt
@@ -0,0 +1,97 @@
+package pokitmons.pokit.data.repository.pokit
+
+import pokitmons.pokit.data.datasource.remote.pokit.PokitDataSource
+import pokitmons.pokit.data.mapper.pokit.PokitMapper
+import pokitmons.pokit.data.model.common.parseErrorResult
+import pokitmons.pokit.data.model.pokit.request.CreatePokitRequest
+import pokitmons.pokit.data.model.pokit.request.GetPokitsRequest
+import pokitmons.pokit.data.model.pokit.request.ModifyPokitRequest
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class PokitRepositoryImpl @Inject constructor(
+ private val pokitDataSource: PokitDataSource,
+) : PokitRepository {
+ override suspend fun getPokits(
+ filterUncategorized: Boolean,
+ size: Int,
+ page: Int,
+ sort: PokitsSort,
+ ): PokitResult> {
+ return runCatching {
+ val request = GetPokitsRequest(
+ filterUncategoriezd = filterUncategorized,
+ size = size,
+ page = page,
+ sort = sort
+ )
+ val response = pokitDataSource.getPokits(request)
+ val mappedResponse = PokitMapper.mapperToPokits(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun createPokit(name: String, imageId: Int): PokitResult {
+ return runCatching {
+ val request = CreatePokitRequest(categoryName = name, categoryImageId = imageId)
+ val response = pokitDataSource.createPokit(request)
+ val pokitId = response.categoryId
+ PokitResult.Success(pokitId)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun modifyPokit(pokitId: Int, name: String, imageId: Int): PokitResult {
+ return runCatching {
+ val request = ModifyPokitRequest(categoryName = name, categoryImageId = imageId)
+ val response = pokitDataSource.modifyPokit(pokitId, request)
+ PokitResult.Success(response.categoryId)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getPokitImages(): PokitResult> {
+ return runCatching {
+ val response = pokitDataSource.getPokitImages()
+ val mappedResponse = PokitMapper.mapperToPokitImages(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getPokit(pokitId: Int): PokitResult {
+ return runCatching {
+ val response = pokitDataSource.getPokit(pokitId)
+ val mappedResponse = PokitMapper.mapperToPokit(response)
+ PokitResult.Success(mappedResponse)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun deletePokit(pokitId: Int): PokitResult {
+ return runCatching {
+ pokitDataSource.deletePokit(pokitId)
+ PokitResult.Success(result = Unit)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+
+ override suspend fun getPokitCount(): PokitResult {
+ return kotlin.runCatching {
+ val response = pokitDataSource.getPokitCount()
+ PokitResult.Success(result = response.categoryTotalCount)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/search/SearchRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/search/SearchRepositoryImpl.kt
new file mode 100644
index 00000000..5ce7c2b8
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/search/SearchRepositoryImpl.kt
@@ -0,0 +1,34 @@
+package pokitmons.pokit.data.repository.search
+
+import kotlinx.coroutines.flow.Flow
+import pokitmons.pokit.data.datasource.local.search.SearchDataSource
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Inject
+
+class SearchRepositoryImpl @Inject constructor(
+ private val dataSource: SearchDataSource,
+) : SearchRepository {
+ override fun getRecentSearchWords(): Flow> {
+ return dataSource.getSearchWord()
+ }
+
+ override suspend fun removeSearchWord(word: String) {
+ dataSource.removeSearchWord(word)
+ }
+
+ override suspend fun removeAllSearchWords() {
+ dataSource.removeAllSearchWords()
+ }
+
+ override suspend fun setUseRecentSearchWord(use: Boolean): Boolean {
+ return dataSource.setUseRecentSearchWord(use = use)
+ }
+
+ override fun getUseRecentSearchWord(): Flow {
+ return dataSource.getUseRecentSearchWord()
+ }
+
+ override suspend fun addRecentSearchWord(word: String) {
+ dataSource.addSearchWord(word)
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/repository/setting/SettingRepositoryImpl.kt b/data/src/main/java/pokitmons/pokit/data/repository/setting/SettingRepositoryImpl.kt
new file mode 100644
index 00000000..aea7447f
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/repository/setting/SettingRepositoryImpl.kt
@@ -0,0 +1,25 @@
+package pokitmons.pokit.data.repository.setting
+
+import pokitmons.pokit.data.datasource.remote.setting.SettingDataSource
+import pokitmons.pokit.data.mapper.setting.SettingMapper
+import pokitmons.pokit.data.model.common.parseErrorResult
+import pokitmons.pokit.data.model.setting.reqeust.EditNicknameRequest
+import pokitmons.pokit.data.model.setting.response.EditNicknameResponse
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.setting.EditNicknameResult
+import pokitmons.pokit.domain.repository.setting.SettingRepository
+import javax.inject.Inject
+
+class SettingRepositoryImpl @Inject constructor(
+ private val remoteSettingDataSource: SettingDataSource,
+) : SettingRepository {
+ override suspend fun editNickname(nickname: String): PokitResult {
+ return runCatching {
+ val editNicknameResponse: EditNicknameResponse = remoteSettingDataSource.editNickname(EditNicknameRequest(nickname))
+ val editNicknameMapper = SettingMapper.mapperToEditNickname(editNicknameResponse)
+ PokitResult.Success(editNicknameMapper)
+ }.getOrElse { throwable ->
+ parseErrorResult(throwable)
+ }
+ }
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/room/dao/SearchWordDao.kt b/data/src/main/java/pokitmons/pokit/data/room/dao/SearchWordDao.kt
new file mode 100644
index 00000000..d1506b5f
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/room/dao/SearchWordDao.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.data.room.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import kotlinx.coroutines.flow.Flow
+import pokitmons.pokit.data.room.entity.SearchWord
+
+@Dao
+interface SearchWordDao {
+ @Query("SELECT word from SearchWord order by searchedAt desc limit 10")
+ fun getRecentSearchWords(): Flow>
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun addSearchWord(searchWord: SearchWord)
+
+ @Query("DELETE from SearchWord where word = :word")
+ suspend fun removeSearchWord(word: String)
+
+ @Query("DELETE from SearchWord")
+ suspend fun removeAllSearchWords()
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/room/database/AppDatabase.kt b/data/src/main/java/pokitmons/pokit/data/room/database/AppDatabase.kt
new file mode 100644
index 00000000..0f3e4b39
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/room/database/AppDatabase.kt
@@ -0,0 +1,11 @@
+package pokitmons.pokit.data.room.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import pokitmons.pokit.data.room.dao.SearchWordDao
+import pokitmons.pokit.data.room.entity.SearchWord
+
+@Database(entities = [SearchWord::class], version = 1)
+abstract class AppDatabase : RoomDatabase() {
+ abstract fun searchWordDao(): SearchWordDao
+}
diff --git a/data/src/main/java/pokitmons/pokit/data/room/entity/SearchWord.kt b/data/src/main/java/pokitmons/pokit/data/room/entity/SearchWord.kt
new file mode 100644
index 00000000..aedcc90f
--- /dev/null
+++ b/data/src/main/java/pokitmons/pokit/data/room/entity/SearchWord.kt
@@ -0,0 +1,10 @@
+package pokitmons.pokit.data.room.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["word"])
+data class SearchWord(
+ @ColumnInfo("word") val word: String,
+ @ColumnInfo("searchedAt") val searchedAt: String,
+)
diff --git a/data/src/test/java/pokitmons/pokit/data/datasource/RemoteAlertDataSourceTest.kt b/data/src/test/java/pokitmons/pokit/data/datasource/RemoteAlertDataSourceTest.kt
new file mode 100644
index 00000000..a830d7f6
--- /dev/null
+++ b/data/src/test/java/pokitmons/pokit/data/datasource/RemoteAlertDataSourceTest.kt
@@ -0,0 +1,48 @@
+package pokitmons.pokit.data.datasource
+
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.types.shouldBeInstanceOf
+import io.mockk.coEvery
+import io.mockk.mockk
+import pokitmons.pokit.data.api.AlertApi
+import pokitmons.pokit.data.datasource.remote.alert.RemoteAlertDataSource
+import pokitmons.pokit.data.model.alert.GetAlertsResponse
+
+val alertApi: AlertApi = mockk()
+
+class RemoteAlertDataSourceTest : DescribeSpec({
+ val remoteAlertDataSource = RemoteAlertDataSource(alertApi)
+ describe("알림 목록 조회시") {
+ context("알림 목록 조회가 정상적으로 수행되면") {
+ coEvery { alertApi.getAlerts(page = 0, size = 0) } returns GetAlertsResponse()
+ it("알림 목록이 반환된다.") {
+ val response = remoteAlertDataSource.getAlerts(page = 0, size = 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { alertApi.getAlerts(page = 0, size = 0) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remoteAlertDataSource.getAlerts(page = 0, size = 0)
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("알림 제거시") {
+ context("에러가 발생했다면") {
+ coEvery { alertApi.deleteAlert(alertId = 0) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remoteAlertDataSource.deleteAlert(alertId = 0)
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+})
diff --git a/data/src/test/java/pokitmons/pokit/data/datasource/RemoteLinkDataSourceTest.kt b/data/src/test/java/pokitmons/pokit/data/datasource/RemoteLinkDataSourceTest.kt
new file mode 100644
index 00000000..011fcc28
--- /dev/null
+++ b/data/src/test/java/pokitmons/pokit/data/datasource/RemoteLinkDataSourceTest.kt
@@ -0,0 +1,58 @@
+package pokitmons.pokit.data.datasource
+
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.types.shouldBeInstanceOf
+import io.mockk.coEvery
+import io.mockk.mockk
+import pokitmons.pokit.data.api.LinkApi
+import pokitmons.pokit.data.datasource.remote.link.RemoteLinkDataSource
+import pokitmons.pokit.data.model.link.response.GetLinksResponse
+
+val linkApi: LinkApi = mockk()
+
+class RemoteLinkDataSourceTest : DescribeSpec({
+ val remoteLinkDataSource = RemoteLinkDataSource(linkApi)
+ describe("링크 목록 조회시") {
+ context("링크 목록 조회가 정상적으로 수신되면") {
+ coEvery { linkApi.getLinks() } returns GetLinksResponse()
+ it("링크 목록이 반환된다.") {
+ val response = remoteLinkDataSource.getLinks()
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { linkApi.getLinks() } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remoteLinkDataSource.getLinks()
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("북마크 변경") {
+ context("북마크 취소 도중 예외가 발생했다면") {
+ coEvery { linkApi.cancelBookmark(0) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remoteLinkDataSource.setBookmark(contentId = 0, bookmarked = false)
+ }
+ exception.message shouldBe "error"
+ }
+ }
+
+ context("북마크 등록 도중 예외가 발생했다면") {
+ coEvery { linkApi.applyBookmark(0) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remoteLinkDataSource.setBookmark(contentId = 0, bookmarked = true)
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+})
diff --git a/data/src/test/java/pokitmons/pokit/data/datasource/RemotePokitDataSourceTest.kt b/data/src/test/java/pokitmons/pokit/data/datasource/RemotePokitDataSourceTest.kt
new file mode 100644
index 00000000..3bd15f56
--- /dev/null
+++ b/data/src/test/java/pokitmons/pokit/data/datasource/RemotePokitDataSourceTest.kt
@@ -0,0 +1,156 @@
+package pokitmons.pokit.data.datasource
+
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.types.shouldBeInstanceOf
+import io.mockk.coEvery
+import io.mockk.mockk
+import pokitmons.pokit.data.api.PokitApi
+import pokitmons.pokit.data.datasource.remote.pokit.RemotePokitDataSource
+import pokitmons.pokit.data.model.pokit.request.CreatePokitRequest
+import pokitmons.pokit.data.model.pokit.request.GetPokitsRequest
+import pokitmons.pokit.data.model.pokit.request.ModifyPokitRequest
+import pokitmons.pokit.data.model.pokit.response.CreatePokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitCountResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitImagesResponseItem
+import pokitmons.pokit.data.model.pokit.response.GetPokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitsResponse
+import pokitmons.pokit.data.model.pokit.response.ModifyPokitResponse
+
+val pokitApi: PokitApi = mockk()
+
+class RemotePokitDataSourceTest : DescribeSpec({
+ val remotePokitDataSource = RemotePokitDataSource(pokitApi)
+ describe("포킷 목록 조회시") {
+ context("포킷 목록이 정상적으로 수신되면") {
+ coEvery { pokitApi.getPokits() } returns GetPokitsResponse()
+ it("포킷 목록이 반환된다.") {
+ val response = remotePokitDataSource.getPokits(GetPokitsRequest())
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { pokitApi.getPokits() } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.getPokits(GetPokitsRequest())
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("포킷 추가시") {
+ context("추가가 성공적으로 수행되면") {
+ coEvery { pokitApi.createPokit(CreatePokitRequest()) } returns CreatePokitResponse()
+ it("추가된 포킷의 정보가 반환된다.") {
+ val response = remotePokitDataSource.createPokit(CreatePokitRequest())
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("추가도중 에러가 발생했다면") {
+ coEvery { pokitApi.createPokit(CreatePokitRequest()) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.createPokit(CreatePokitRequest())
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("포킷 수정시") {
+ context("수정이 성공적으로 수행되면") {
+ coEvery { pokitApi.modifyPokit(categoryId = 0, modifyPokitRequest = ModifyPokitRequest()) } returns ModifyPokitResponse()
+ it("수정된 포킷의 정보가 반환된다.") {
+ val response = remotePokitDataSource.modifyPokit(pokitId = 0, modifyPokitRequest = ModifyPokitRequest())
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("수정도중 에러가 발생했다면") {
+ coEvery { pokitApi.modifyPokit(categoryId = 0, modifyPokitRequest = ModifyPokitRequest()) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.modifyPokit(pokitId = 0, modifyPokitRequest = ModifyPokitRequest())
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("포킷 조회시") {
+ context("조회가 성공적으로 수행되면") {
+ coEvery { pokitApi.getPokit(0) } returns GetPokitResponse()
+ it("해당 포킷의 정보가 반환된다.") {
+ val response = remotePokitDataSource.getPokit(0)
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("조회 도중 에러가 발생했다면") {
+ coEvery { pokitApi.getPokit(0) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.getPokit(0)
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("포킷 이미지 목록 조회시") {
+ context("목록 조회가 성공적으로 수행되면") {
+ coEvery { pokitApi.getPokitImages() } returns emptyList()
+ it("해당 리스트가 반환된다.") {
+ val response = remotePokitDataSource.getPokitImages()
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("조회 도중 에러가 발생했다면") {
+ coEvery { pokitApi.getPokitImages() } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.getPokitImages()
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("포킷 삭제시") {
+ context("삭제가 실패한다면") {
+ coEvery { pokitApi.deletePokit(0) } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.deletePokit(0)
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+
+ describe("포킷 개수 조회시") {
+ context("개수 조회가 성공적으로 수행되면") {
+ coEvery { pokitApi.getPokitCount() } returns GetPokitCountResponse()
+ it("해당 리스트가 반환된다.") {
+ val response = remotePokitDataSource.getPokitCount()
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("개수 조회 도중 실패한다면") {
+ coEvery { pokitApi.getPokitCount() } throws IllegalArgumentException("error")
+ it("동일한 에러가 발생한다.") {
+ val exception = shouldThrow {
+ remotePokitDataSource.getPokitCount()
+ }
+ exception.message shouldBe "error"
+ }
+ }
+ }
+})
diff --git a/data/src/test/java/pokitmons/pokit/data/repository/AlertRepositoryImplTest.kt b/data/src/test/java/pokitmons/pokit/data/repository/AlertRepositoryImplTest.kt
new file mode 100644
index 00000000..10fadefc
--- /dev/null
+++ b/data/src/test/java/pokitmons/pokit/data/repository/AlertRepositoryImplTest.kt
@@ -0,0 +1,52 @@
+package pokitmons.pokit.data.repository
+
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.types.shouldBeInstanceOf
+import io.mockk.coEvery
+import io.mockk.mockk
+import pokitmons.pokit.data.datasource.remote.alert.AlertDataSource
+import pokitmons.pokit.data.model.alert.GetAlertsResponse
+import pokitmons.pokit.data.repository.alert.AlertRepositoryImpl
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.alert.Alarm
+
+val alertDataSource: AlertDataSource = mockk()
+
+class AlertRepositoryImplTest : DescribeSpec({
+ val alertRepository = AlertRepositoryImpl(alertDataSource)
+ describe("알림 목록 조회시") {
+ context("알림 목록 조회가 정상적으로 수행되면") {
+ coEvery { alertDataSource.getAlerts(page = 0, size = 0) } returns GetAlertsResponse()
+ it("PokitResult로 래핑된 알림 목록이 반환된다.") {
+ val response = alertRepository.getAlerts(page = 0, size = 0)
+ response.shouldBeInstanceOf>>()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { alertDataSource.getAlerts(page = 0, size = 0) } throws IllegalArgumentException()
+ it("PokitResult로 래핑된 에러 내용이 반환된다.") {
+ val response = alertRepository.getAlerts(page = 0, size = 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("알림 제거시") {
+ context("제거가 정상적으로 수행되면") {
+ coEvery { alertDataSource.deleteAlert(alertId = 0) } returns Unit
+ it("데이터가 없는 PokitResult.Success가 반환된다.") {
+ val response = alertRepository.deleteAlert(alertId = 0)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { alertDataSource.deleteAlert(alertId = 0) } throws IllegalArgumentException()
+ it("PokitResult로 래핑된 에러 내용이 반환된다.") {
+ val response = alertRepository.deleteAlert(alertId = 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+})
diff --git a/data/src/test/java/pokitmons/pokit/data/repository/LinkRepositoryImplTest.kt b/data/src/test/java/pokitmons/pokit/data/repository/LinkRepositoryImplTest.kt
new file mode 100644
index 00000000..9cee6ade
--- /dev/null
+++ b/data/src/test/java/pokitmons/pokit/data/repository/LinkRepositoryImplTest.kt
@@ -0,0 +1,68 @@
+package pokitmons.pokit.data.repository
+
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.types.shouldBeInstanceOf
+import io.mockk.coEvery
+import io.mockk.mockk
+import pokitmons.pokit.data.datasource.remote.link.LinkDataSource
+import pokitmons.pokit.data.model.link.response.GetLinksResponse
+import pokitmons.pokit.data.repository.link.LinkRepositoryImpl
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+
+val linkDataSource: LinkDataSource = mockk()
+
+class LinkRepositoryImplTest : DescribeSpec({
+ val linkRepository = LinkRepositoryImpl(linkDataSource)
+ describe("링크 목록 조회") {
+ context("링크 목록이 정상적으로 수신되면") {
+ coEvery { linkDataSource.getLinks() } returns GetLinksResponse()
+ it("링크 목록이 반환된다.") {
+ val response = linkRepository.getLinks()
+ response.shouldBeInstanceOf>>()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { linkDataSource.getLinks() } throws IllegalArgumentException()
+ it("에러코드, 메세지가 반환된다.") {
+ val response = linkRepository.getLinks()
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("북마크 변경") {
+ context("북마크 취소가 정상적으로 수행된다면") {
+ coEvery { linkDataSource.setBookmark(contentId = 0, bookmarked = false) } returns Unit
+ it("빈 성공 결과 인스턴스가 반환된다.") {
+ val response = linkRepository.setBookmark(linkId = 0, bookmarked = false)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("북마크 취소 도중 예외가 발생했다면") {
+ coEvery { linkDataSource.setBookmark(contentId = 0, bookmarked = false) } throws IllegalArgumentException()
+ it("에러코드, 메세지가 반환된다.") {
+ val response = linkRepository.setBookmark(linkId = 0, bookmarked = false)
+ response.shouldBeInstanceOf()
+ }
+ }
+
+ context("북마크 등록이 정상적으로 수행된다면") {
+ coEvery { linkDataSource.setBookmark(contentId = 0, bookmarked = true) } returns Unit
+ it("빈 성공 결과 인스턴스가 반환된다.") {
+ val response = linkRepository.setBookmark(linkId = 0, bookmarked = true)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("북마크 등록 도중 예외가 발생했다면") {
+ coEvery { linkDataSource.setBookmark(contentId = 0, bookmarked = true) } throws IllegalArgumentException()
+ it("에러코드, 메세지가 반환된다.") {
+ val response = linkRepository.setBookmark(linkId = 0, bookmarked = true)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+})
diff --git a/data/src/test/java/pokitmons/pokit/data/repository/PokitRepositoryImplTest.kt b/data/src/test/java/pokitmons/pokit/data/repository/PokitRepositoryImplTest.kt
new file mode 100644
index 00000000..6c7007ec
--- /dev/null
+++ b/data/src/test/java/pokitmons/pokit/data/repository/PokitRepositoryImplTest.kt
@@ -0,0 +1,151 @@
+package pokitmons.pokit.data.repository
+
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.types.shouldBeInstanceOf
+import io.mockk.coEvery
+import io.mockk.mockk
+import pokitmons.pokit.data.datasource.remote.pokit.PokitDataSource
+import pokitmons.pokit.data.model.pokit.request.CreatePokitRequest
+import pokitmons.pokit.data.model.pokit.request.GetPokitsRequest
+import pokitmons.pokit.data.model.pokit.request.ModifyPokitRequest
+import pokitmons.pokit.data.model.pokit.response.CreatePokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitCountResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitResponse
+import pokitmons.pokit.data.model.pokit.response.GetPokitsResponse
+import pokitmons.pokit.data.model.pokit.response.ModifyPokitResponse
+import pokitmons.pokit.data.repository.pokit.PokitRepositoryImpl
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+
+val pokitDataSource: PokitDataSource = mockk()
+
+class PokitRepositoryImplTest : DescribeSpec({
+ val pokitRepository = PokitRepositoryImpl(pokitDataSource)
+ describe("포킷 목록 조회") {
+ context("포킷 목록이 정상적으로 수신되면") {
+ coEvery { pokitDataSource.getPokits(GetPokitsRequest()) } returns GetPokitsResponse()
+
+ it("포킷 목록이 반환된다.") {
+ val response = pokitRepository.getPokits()
+ response.shouldBeInstanceOf>>()
+ }
+ }
+
+ context("에러가 발생했다면") {
+ coEvery { pokitDataSource.getPokits(GetPokitsRequest()) } throws IllegalArgumentException()
+
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.getPokits()
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("포킷 추가시") {
+ context("추가가 성공적으로 수행되면") {
+ coEvery { pokitDataSource.createPokit(CreatePokitRequest()) } returns CreatePokitResponse()
+ it("추가된 포킷의 id가 반환된다.") {
+ val response = pokitRepository.createPokit("", 0)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("추가도중 에러가 발생했다면") {
+ coEvery { pokitDataSource.createPokit(CreatePokitRequest()) } throws IllegalArgumentException()
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.createPokit("", 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("포킷 수정시") {
+ context("수정이 성공적으로 수행되면") {
+ coEvery { pokitDataSource.modifyPokit(pokitId = 0, modifyPokitRequest = ModifyPokitRequest()) } returns ModifyPokitResponse()
+ it("수정된 포킷의 id가 반환된다.") {
+ val response = pokitRepository.modifyPokit(pokitId = 0, name = "", imageId = 0)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("수정도중 에러가 발생했다면") {
+ coEvery { pokitDataSource.modifyPokit(pokitId = 0, modifyPokitRequest = ModifyPokitRequest()) } throws IllegalArgumentException()
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.modifyPokit(pokitId = 0, name = "", imageId = 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("포킷 조회시") {
+ context("조회가 성공적으로 수행되면") {
+ coEvery { pokitDataSource.getPokit(pokitId = 0) } returns GetPokitResponse()
+ it("해당 포킷의 정보가 반환된다.") {
+ val response = pokitRepository.getPokit(pokitId = 0)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("조회 도중 에러가 발생했다면") {
+ coEvery { pokitDataSource.getPokit(pokitId = 0) } throws IllegalArgumentException()
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.getPokit(pokitId = 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("포킷 이미지 목록 조회시") {
+ context("목록 조회가 성공적으로 수행되면") {
+ coEvery { pokitDataSource.getPokitImages() } returns emptyList()
+ it("해당 리스트가 반환된다.") {
+ val response = pokitRepository.getPokitImages()
+ response.shouldBeInstanceOf>>()
+ }
+ }
+
+ context("조회 도중 에러가 발생한다면") {
+ coEvery { pokitDataSource.getPokitImages() } throws IllegalArgumentException()
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.getPokitImages()
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("포킷 삭제시") {
+ context("삭제가 성공적으로 수행되면") {
+ coEvery { pokitDataSource.deletePokit(pokitId = 0) } returns Unit
+ it("해당 포킷의 정보가 반환된다.") {
+ val response = pokitRepository.deletePokit(pokitId = 0)
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("삭제 도중 에러가 발생했다면") {
+ coEvery { pokitDataSource.deletePokit(pokitId = 0) } throws IllegalArgumentException()
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.deletePokit(pokitId = 0)
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+
+ describe("포킷 개수 조회시") {
+ context("개수 조회가 성공적으로 수행되면") {
+ coEvery { pokitDataSource.getPokitCount() } returns GetPokitCountResponse()
+ it("개수가 반환된다.") {
+ val response = pokitRepository.getPokitCount()
+ response.shouldBeInstanceOf>()
+ }
+ }
+
+ context("개수 조회 도중 에러가 발생한다면") {
+ coEvery { pokitDataSource.getPokitCount() } throws IllegalArgumentException()
+ it("에러 코드, 메세지가 반환된다.") {
+ val response = pokitRepository.getPokitCount()
+ response.shouldBeInstanceOf()
+ }
+ }
+ }
+})
diff --git a/domain/.gitignore b/domain/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts
new file mode 100644
index 00000000..05310eff
--- /dev/null
+++ b/domain/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ id("java-library")
+ alias(libs.plugins.org.jetbrains.kotlin.jvm)
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+dependencies {
+ implementation(libs.javax.inject)
+ implementation(libs.coroutines.core)
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/commom/PokitError.kt b/domain/src/main/java/pokitmons/pokit/domain/commom/PokitError.kt
new file mode 100644
index 00000000..79e9632e
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/commom/PokitError.kt
@@ -0,0 +1,6 @@
+package pokitmons.pokit.domain.commom
+
+data class PokitError(
+ val message: String = "그 외",
+ val code: String = "U_0000",
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/commom/PokitResult.kt b/domain/src/main/java/pokitmons/pokit/domain/commom/PokitResult.kt
new file mode 100644
index 00000000..25ea983a
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/commom/PokitResult.kt
@@ -0,0 +1,6 @@
+package pokitmons.pokit.domain.commom
+
+sealed interface PokitResult {
+ data class Success(val result: T) : PokitResult
+ data class Error(val error: PokitError) : PokitResult
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/alert/Alarm.kt b/domain/src/main/java/pokitmons/pokit/domain/model/alert/Alarm.kt
new file mode 100644
index 00000000..e9d6164f
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/alert/Alarm.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.domain.model.alert
+
+data class Alarm(
+ val id: Int,
+ val contentId: Int,
+ val thumbnail: String? = null,
+ val title: String,
+ val createdAt: String,
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/auth/DuplicateNicknameResult.kt b/domain/src/main/java/pokitmons/pokit/domain/model/auth/DuplicateNicknameResult.kt
new file mode 100644
index 00000000..34c8618c
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/auth/DuplicateNicknameResult.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.domain.model.auth
+
+data class DuplicateNicknameResult(
+ val isDuplicate: Boolean,
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/auth/SNSLoginResult.kt b/domain/src/main/java/pokitmons/pokit/domain/model/auth/SNSLoginResult.kt
new file mode 100644
index 00000000..84da23db
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/auth/SNSLoginResult.kt
@@ -0,0 +1,7 @@
+package pokitmons.pokit.domain.model.auth
+
+data class SNSLoginResult(
+ val accessToken: String,
+ val refreshToken: String,
+ val isRegistered: Boolean,
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/auth/SignUpResult.kt b/domain/src/main/java/pokitmons/pokit/domain/model/auth/SignUpResult.kt
new file mode 100644
index 00000000..8abbc165
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/auth/SignUpResult.kt
@@ -0,0 +1,7 @@
+package pokitmons.pokit.domain.model.auth
+
+data class SignUpResult(
+ val id: Int,
+ val email: String,
+ val nickname: String,
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt b/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt
new file mode 100644
index 00000000..1e22c69d
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/home/remind/RemindResult.kt
@@ -0,0 +1,11 @@
+package pokitmons.pokit.domain.model.home.remind
+
+data class RemindResult(
+ val id: Int,
+ val title: String,
+ val domain: String,
+ val createdAt: String,
+ val isRead: Boolean,
+ val thumbNail: String,
+ val data: String,
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/link/Link.kt b/domain/src/main/java/pokitmons/pokit/domain/model/link/Link.kt
new file mode 100644
index 00000000..c4933dd0
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/link/Link.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.domain.model.link
+
+data class Link(
+ val id: Int,
+ val categoryId: Int,
+ val categoryName: String,
+ val data: String,
+ val domain: String,
+ val title: String,
+ val memo: String,
+ val alertYn: String,
+ val createdAt: String,
+ val isRead: Boolean = false,
+ val favorites: Boolean = false,
+ val thumbnail: String = "",
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/link/LinkCard.kt b/domain/src/main/java/pokitmons/pokit/domain/model/link/LinkCard.kt
new file mode 100644
index 00000000..29b4147d
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/link/LinkCard.kt
@@ -0,0 +1,7 @@
+package pokitmons.pokit.domain.model.link
+
+data class LinkCard(
+ val url: String = "",
+ val thumbnailUrl: String? = null,
+ val title: String = "",
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/link/LinksSort.kt b/domain/src/main/java/pokitmons/pokit/domain/model/link/LinksSort.kt
new file mode 100644
index 00000000..36499268
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/link/LinksSort.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.domain.model.link
+
+enum class LinksSort(val value: String) {
+ RECENT("createdAt,desc"), OLDER("createdAt,asc")
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/pokit/Cosnt.kt b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/Cosnt.kt
new file mode 100644
index 00000000..55e90547
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/Cosnt.kt
@@ -0,0 +1,3 @@
+package pokitmons.pokit.domain.model.pokit
+
+const val MAX_POKIT_COUNT = 30
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/pokit/Pokit.kt b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/Pokit.kt
new file mode 100644
index 00000000..06a376f0
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/Pokit.kt
@@ -0,0 +1,15 @@
+package pokitmons.pokit.domain.model.pokit
+
+data class Pokit(
+ val categoryId: Int,
+ val userId: Int,
+ val name: String,
+ val image: Image,
+ val linkCount: Int,
+ val createdAt: String,
+) {
+ data class Image(
+ val id: Int,
+ val url: String,
+ )
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/pokit/PokitErrorCode.kt b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/PokitErrorCode.kt
new file mode 100644
index 00000000..948ef749
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/PokitErrorCode.kt
@@ -0,0 +1,10 @@
+package pokitmons.pokit.domain.model.pokit
+
+object PokitErrorCode {
+ const val ALREADY_USED_POKIT_NAME = "CA_001"
+ const val CANNOT_FOUND_POKIT_INFO = "CA_002"
+ const val UNAVAILABLE_POKIT = "CA_003"
+ const val CANNOT_FOUND_POKIT_IMAGE = "CA_004"
+ const val TOO_MUCH_POKIT = "CA_005"
+ const val CANNOT_FOUND_UNCATEGORY_IMAGE = "CA_006"
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/pokit/PokitsSort.kt b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/PokitsSort.kt
new file mode 100644
index 00000000..afe21f35
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/pokit/PokitsSort.kt
@@ -0,0 +1,5 @@
+package pokitmons.pokit.domain.model.pokit
+
+enum class PokitsSort(val value: String) {
+ RECENT("createdAt,desc"), ALPHABETICAL("name,asc")
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/model/setting/EditNicknameResult.kt b/domain/src/main/java/pokitmons/pokit/domain/model/setting/EditNicknameResult.kt
new file mode 100644
index 00000000..09119047
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/model/setting/EditNicknameResult.kt
@@ -0,0 +1,7 @@
+package pokitmons.pokit.domain.model.setting
+
+data class EditNicknameResult(
+ val email: String,
+ val id: Int,
+ val nickname: String,
+)
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/alert/AlertRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/alert/AlertRepository.kt
new file mode 100644
index 00000000..5b836d9d
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/alert/AlertRepository.kt
@@ -0,0 +1,9 @@
+package pokitmons.pokit.domain.repository.alert
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.alert.Alarm
+
+interface AlertRepository {
+ suspend fun getAlerts(page: Int, size: Int): PokitResult>
+ suspend fun deleteAlert(alertId: Int): PokitResult
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/auth/AuthRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/auth/AuthRepository.kt
new file mode 100644
index 00000000..71944e41
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/auth/AuthRepository.kt
@@ -0,0 +1,21 @@
+package pokitmons.pokit.domain.repository.auth
+
+import kotlinx.coroutines.flow.Flow
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.auth.DuplicateNicknameResult
+import pokitmons.pokit.domain.model.auth.SNSLoginResult
+import pokitmons.pokit.domain.model.auth.SignUpResult
+
+interface AuthRepository {
+ suspend fun snsLogin(authPlatform: String, idToken: String): PokitResult
+ suspend fun checkDuplicateNickname(nickname: String): PokitResult
+ suspend fun signUp(nickname: String, categories: List): PokitResult
+ suspend fun withdraw(): PokitResult
+
+ suspend fun setAccessToken(token: String)
+ suspend fun setRefreshToken(token: String)
+
+ // TODO 리팩토링
+ suspend fun setAuthType(type: String)
+ suspend fun getAuthType(): Flow
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/auth/TokenRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/auth/TokenRepository.kt
new file mode 100644
index 00000000..110e8b91
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/auth/TokenRepository.kt
@@ -0,0 +1,6 @@
+package pokitmons.pokit.domain.repository.auth
+
+interface TokenRepository {
+ suspend fun setAccessToken(token: String)
+ suspend fun setRefreshToken(token: String)
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/home/remind/RemindRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/home/remind/RemindRepository.kt
new file mode 100644
index 00000000..d86b89f9
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/home/remind/RemindRepository.kt
@@ -0,0 +1,28 @@
+package pokitmons.pokit.domain.repository.home.remind
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.home.remind.RemindResult
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+
+interface RemindRepository {
+ suspend fun getUnReadContents(
+ filterUncategorized: Boolean = true,
+ size: Int = 10,
+ page: Int = 0,
+ sort: PokitsSort = PokitsSort.RECENT,
+ ): PokitResult>
+
+ suspend fun getTodayContents(
+ filterUncategorized: Boolean = true,
+ size: Int = 10,
+ page: Int = 0,
+ sort: PokitsSort = PokitsSort.RECENT,
+ ): PokitResult>
+
+ suspend fun getBookmarkContents(
+ filterUncategorized: Boolean = true,
+ size: Int = 10,
+ page: Int = 0,
+ sort: PokitsSort = PokitsSort.RECENT,
+ ): PokitResult>
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt
new file mode 100644
index 00000000..c2336195
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/link/LinkRepository.kt
@@ -0,0 +1,65 @@
+package pokitmons.pokit.domain.repository.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.model.link.LinkCard
+import pokitmons.pokit.domain.model.link.LinksSort
+
+interface LinkRepository {
+ suspend fun getLinks(
+ categoryId: Int = 0,
+ size: Int = 10,
+ page: Int = 0,
+ sort: LinksSort = LinksSort.RECENT,
+ isRead: Boolean? = null,
+ favorite: Boolean? = null,
+ startDate: String? = null,
+ endDate: String? = null,
+ categoryIds: List? = null,
+ ): PokitResult>
+
+ suspend fun searchLinks(
+ page: Int,
+ size: Int,
+ sort: List,
+ isRead: Boolean?,
+ favorites: Boolean?,
+ startDate: String?,
+ endDate: String?,
+ categoryIds: List?,
+ searchWord: String,
+ ): PokitResult>
+
+ suspend fun deleteLink(linkId: Int): PokitResult
+
+ suspend fun getLink(linkId: Int): PokitResult
+
+ suspend fun modifyLink(
+ linkId: Int,
+ data: String,
+ title: String,
+ categoryId: Int,
+ memo: String,
+ alertYn: String,
+ thumbNail: String,
+ ): PokitResult
+
+ suspend fun createLink(
+ data: String,
+ title: String,
+ categoryId: Int,
+ memo: String,
+ alertYn: String,
+ thumbNail: String,
+ ): PokitResult
+
+ suspend fun setBookmark(linkId: Int, bookmarked: Boolean): PokitResult
+
+ suspend fun getLinkCard(url: String): PokitResult
+
+ suspend fun getUncategorizedLinks(
+ size: Int = 10,
+ page: Int = 0,
+ sort: LinksSort = LinksSort.RECENT,
+ ): PokitResult>
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/pokit/PokitRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/pokit/PokitRepository.kt
new file mode 100644
index 00000000..c684f13f
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/pokit/PokitRepository.kt
@@ -0,0 +1,33 @@
+package pokitmons.pokit.domain.repository.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+
+interface PokitRepository {
+ suspend fun getPokits(
+ filterUncategorized: Boolean = true,
+ size: Int = 10,
+ page: Int = 0,
+ sort: PokitsSort = PokitsSort.RECENT,
+ ): PokitResult>
+
+ suspend fun createPokit(
+ name: String,
+ imageId: Int,
+ ): PokitResult
+
+ suspend fun modifyPokit(
+ pokitId: Int,
+ name: String,
+ imageId: Int,
+ ): PokitResult
+
+ suspend fun getPokitImages(): PokitResult>
+
+ suspend fun getPokit(pokitId: Int): PokitResult
+
+ suspend fun deletePokit(pokitId: Int): PokitResult
+
+ suspend fun getPokitCount(): PokitResult
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/search/SearchRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/search/SearchRepository.kt
new file mode 100644
index 00000000..26c7a6e6
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/search/SearchRepository.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.domain.repository.search
+
+import kotlinx.coroutines.flow.Flow
+
+interface SearchRepository {
+ fun getRecentSearchWords(): Flow>
+ suspend fun removeSearchWord(word: String)
+ suspend fun removeAllSearchWords()
+ suspend fun setUseRecentSearchWord(use: Boolean): Boolean
+ fun getUseRecentSearchWord(): Flow
+ suspend fun addRecentSearchWord(word: String)
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/repository/setting/SettingRepository.kt b/domain/src/main/java/pokitmons/pokit/domain/repository/setting/SettingRepository.kt
new file mode 100644
index 00000000..6cdd03ac
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/repository/setting/SettingRepository.kt
@@ -0,0 +1,8 @@
+package pokitmons.pokit.domain.repository.setting
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.setting.EditNicknameResult
+
+interface SettingRepository {
+ suspend fun editNickname(nickname: String): PokitResult
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/alert/DeleteAlertUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/alert/DeleteAlertUseCase.kt
new file mode 100644
index 00000000..848bcad8
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/alert/DeleteAlertUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.alert
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.alert.AlertRepository
+import javax.inject.Inject
+
+class DeleteAlertUseCase @Inject constructor(
+ private val repository: AlertRepository,
+) {
+ suspend fun deleteAlert(alertId: Int): PokitResult {
+ return repository.deleteAlert(alertId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/alert/GetAlertsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/alert/GetAlertsUseCase.kt
new file mode 100644
index 00000000..be03aec2
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/alert/GetAlertsUseCase.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.domain.usecase.alert
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.alert.Alarm
+import pokitmons.pokit.domain.repository.alert.AlertRepository
+import javax.inject.Inject
+
+class GetAlertsUseCase @Inject constructor(
+ private val repository: AlertRepository,
+) {
+ suspend fun getAlerts(page: Int, size: Int): PokitResult> {
+ return repository.getAlerts(page = page, size = size)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/InputNicknameUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/InputNicknameUseCase.kt
new file mode 100644
index 00000000..15881f7f
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/InputNicknameUseCase.kt
@@ -0,0 +1,15 @@
+package pokitmons.pokit.domain.usecase.auth
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.auth.DuplicateNicknameResult
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class InputNicknameUseCase @Inject constructor(private val authRepository: AuthRepository) {
+ suspend fun checkDuplicateNickname(nickname: String): PokitResult {
+ return when (val duplicateResult = authRepository.checkDuplicateNickname(nickname)) {
+ is PokitResult.Success -> PokitResult.Success(duplicateResult.result)
+ is PokitResult.Error -> PokitResult.Error(duplicateResult.error)
+ }
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/SNSLoginUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/SNSLoginUseCase.kt
new file mode 100644
index 00000000..b16da180
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/SNSLoginUseCase.kt
@@ -0,0 +1,15 @@
+package pokitmons.pokit.domain.usecase.auth
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.auth.SNSLoginResult
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class SNSLoginUseCase @Inject constructor(private val authRepository: AuthRepository) {
+ suspend fun snsLogin(authPlatform: String, idToken: String): PokitResult {
+ return when (val loginResult = authRepository.snsLogin(authPlatform, idToken)) {
+ is PokitResult.Success -> PokitResult.Success(loginResult.result)
+ is PokitResult.Error -> PokitResult.Error(loginResult.error)
+ }
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/SignUpUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/SignUpUseCase.kt
new file mode 100644
index 00000000..35e35f19
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/SignUpUseCase.kt
@@ -0,0 +1,18 @@
+package pokitmons.pokit.domain.usecase.auth
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.auth.SignUpResult
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class SignUpUseCase @Inject constructor(private val authRepository: AuthRepository) {
+ suspend fun signUp(
+ nickname: String,
+ categories: List,
+ ): PokitResult {
+ return when (val signUpResult = authRepository.signUp(nickname, categories)) {
+ is PokitResult.Success -> PokitResult.Success(signUpResult.result)
+ is PokitResult.Error -> PokitResult.Error(signUpResult.error)
+ }
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/TokenUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/TokenUseCase.kt
new file mode 100644
index 00000000..e64399c6
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/TokenUseCase.kt
@@ -0,0 +1,23 @@
+package pokitmons.pokit.domain.usecase.auth
+
+import kotlinx.coroutines.flow.Flow
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class TokenUseCase @Inject constructor(private val authRepository: AuthRepository) {
+ suspend fun setAccessToken(token: String) {
+ authRepository.setAccessToken(token)
+ }
+
+ suspend fun setRefreshToken(token: String) {
+ authRepository.setRefreshToken(token)
+ }
+
+ suspend fun setAuthType(type: String) {
+ authRepository.setAuthType(type)
+ }
+
+ suspend fun getAuthType(): Flow {
+ return authRepository.getAuthType()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/WithdrawUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/WithdrawUseCase.kt
new file mode 100644
index 00000000..d9d342d7
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/auth/WithdrawUseCase.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.domain.usecase.auth
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.auth.AuthRepository
+import javax.inject.Inject
+
+class WithdrawUseCase @Inject constructor(private val authRepository: AuthRepository) {
+ suspend fun withdraw(): PokitResult {
+ return when (val signUpResult = authRepository.withdraw()) {
+ is PokitResult.Success -> PokitResult.Success(Unit)
+ is PokitResult.Error -> PokitResult.Error(signUpResult.error)
+ }
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/BookMarkContentsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/BookMarkContentsUseCase.kt
new file mode 100644
index 00000000..4d589dc8
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/BookMarkContentsUseCase.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.domain.usecase.home.remind
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.home.remind.RemindResult
+import pokitmons.pokit.domain.repository.home.remind.RemindRepository
+import javax.inject.Inject
+
+class BookMarkContentsUseCase @Inject constructor(private val remindRepository: RemindRepository) {
+ suspend fun getBookmarkContents(): PokitResult> {
+ return remindRepository.getBookmarkContents()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/TodayContentsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/TodayContentsUseCase.kt
new file mode 100644
index 00000000..6a4cf317
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/TodayContentsUseCase.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.domain.usecase.home.remind
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.home.remind.RemindResult
+import pokitmons.pokit.domain.repository.home.remind.RemindRepository
+import javax.inject.Inject
+
+class TodayContentsUseCase @Inject constructor(private val remindRepository: RemindRepository) {
+ suspend fun getTodayContents(): PokitResult> {
+ return remindRepository.getTodayContents()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/UnReadContentsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/UnReadContentsUseCase.kt
new file mode 100644
index 00000000..5b449479
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/home/remind/UnReadContentsUseCase.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.domain.usecase.home.remind
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.home.remind.RemindResult
+import pokitmons.pokit.domain.repository.home.remind.RemindRepository
+import javax.inject.Inject
+
+class UnReadContentsUseCase @Inject constructor(private val remindRepository: RemindRepository) {
+ suspend fun getUnreadContents(): PokitResult> {
+ return remindRepository.getUnReadContents()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt
new file mode 100644
index 00000000..adfc5a23
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/CreateLinkUseCase.kt
@@ -0,0 +1,28 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class CreateLinkUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun createLink(
+ data: String,
+ title: String,
+ categoryId: Int,
+ memo: String,
+ alertYn: String,
+ thumbNail: String,
+ ): PokitResult {
+ return repository.createLink(
+ data = data,
+ title = title,
+ categoryId = categoryId,
+ memo = memo,
+ alertYn = alertYn,
+ thumbNail = thumbNail
+ )
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/DeleteLinkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/DeleteLinkUseCase.kt
new file mode 100644
index 00000000..ac7d6a64
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/DeleteLinkUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class DeleteLinkUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun deleteLink(linkId: Int): PokitResult {
+ return repository.deleteLink(linkId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinkCardUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinkCardUseCase.kt
new file mode 100644
index 00000000..10f820ec
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinkCardUseCase.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.LinkCard
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class GetLinkCardUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun getLinkCard(url: String): PokitResult {
+ return repository.getLinkCard(url = url)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinkUseCase.kt
new file mode 100644
index 00000000..a9fdc869
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinkUseCase.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class GetLinkUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun getLink(linkId: Int): PokitResult {
+ return repository.getLink(linkId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt
new file mode 100644
index 00000000..77aec8e5
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/GetLinksUseCase.kt
@@ -0,0 +1,47 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.model.link.LinksSort
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class GetLinksUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun getLinks(
+ categoryId: Int = 0,
+ size: Int = 10,
+ page: Int = 0,
+ sort: LinksSort = LinksSort.RECENT,
+ isRead: Boolean? = null,
+ favorite: Boolean? = null,
+ startDate: String? = null,
+ endDate: String? = null,
+ categoryIds: List? = null,
+ ): PokitResult> {
+ return repository.getLinks(
+ categoryId = categoryId,
+ size = size,
+ page = page,
+ sort = sort,
+ isRead = isRead,
+ favorite = favorite,
+ startDate = startDate,
+ endDate = endDate,
+ categoryIds = categoryIds
+ )
+ }
+
+ suspend fun getUncategorizedLinks(
+ size: Int = 10,
+ page: Int = 0,
+ sort: LinksSort = LinksSort.RECENT,
+ ): PokitResult> {
+ return repository.getUncategorizedLinks(
+ size = size,
+ page = page,
+ sort = sort
+ )
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt
new file mode 100644
index 00000000..a9760256
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/ModifyLinkUseCase.kt
@@ -0,0 +1,30 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class ModifyLinkUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun modifyLink(
+ linkId: Int,
+ data: String,
+ title: String,
+ categoryId: Int,
+ memo: String,
+ alertYn: String,
+ thumbNail: String,
+ ): PokitResult {
+ return repository.modifyLink(
+ linkId = linkId,
+ data = data,
+ title = title,
+ categoryId = categoryId,
+ memo = memo,
+ alertYn = alertYn,
+ thumbNail = thumbNail
+ )
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/SearchLinksUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/SearchLinksUseCase.kt
new file mode 100644
index 00000000..8deaa87a
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/SearchLinksUseCase.kt
@@ -0,0 +1,34 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.link.Link
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class SearchLinksUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun searchLinks(
+ page: Int,
+ size: Int,
+ sort: List,
+ isRead: Boolean?,
+ favorites: Boolean?,
+ startDate: String?,
+ endDate: String?,
+ categoryIds: List?,
+ searchWord: String,
+ ): PokitResult> {
+ return repository.searchLinks(
+ page = page,
+ size = size,
+ sort = sort,
+ isRead = isRead,
+ favorites = favorites,
+ startDate = startDate,
+ endDate = endDate,
+ categoryIds = categoryIds,
+ searchWord = searchWord
+ )
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/link/SetBookmarkUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/SetBookmarkUseCase.kt
new file mode 100644
index 00000000..c7e9cee3
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/link/SetBookmarkUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.link
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.link.LinkRepository
+import javax.inject.Inject
+
+class SetBookmarkUseCase @Inject constructor(
+ private val repository: LinkRepository,
+) {
+ suspend fun setBookMarked(linkId: Int, bookmarked: Boolean): PokitResult {
+ return repository.setBookmark(linkId = linkId, bookmarked = bookmarked)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/CreatePokitUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/CreatePokitUseCase.kt
new file mode 100644
index 00000000..9e1df4c2
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/CreatePokitUseCase.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class CreatePokitUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun createPokit(
+ name: String,
+ imageId: Int,
+ ): PokitResult {
+ return repository.createPokit(name = name, imageId = imageId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/DeletePokitUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/DeletePokitUseCase.kt
new file mode 100644
index 00000000..b17ffc59
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/DeletePokitUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class DeletePokitUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun deletePokit(pokitId: Int): PokitResult {
+ return repository.deletePokit(pokitId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitCountUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitCountUseCase.kt
new file mode 100644
index 00000000..8b0c6802
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitCountUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class GetPokitCountUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun getPokitCount(): PokitResult {
+ return repository.getPokitCount()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitImagesUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitImagesUseCase.kt
new file mode 100644
index 00000000..c0a49abb
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitImagesUseCase.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class GetPokitImagesUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun getImages(): PokitResult> {
+ return repository.getPokitImages()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitUseCase.kt
new file mode 100644
index 00000000..7134283b
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitUseCase.kt
@@ -0,0 +1,14 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class GetPokitUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun getPokit(pokitId: Int): PokitResult {
+ return repository.getPokit(pokitId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitsUseCase.kt
new file mode 100644
index 00000000..64a8aeaa
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetPokitsUseCase.kt
@@ -0,0 +1,25 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+import pokitmons.pokit.domain.model.pokit.PokitsSort
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class GetPokitsUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun getPokits(
+ filterUncategorized: Boolean = true,
+ size: Int = 10,
+ page: Int = 0,
+ sort: PokitsSort = PokitsSort.RECENT,
+ ): PokitResult> {
+ return repository.getPokits(
+ filterUncategorized = filterUncategorized,
+ size = size,
+ page = page,
+ sort = sort
+ )
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetUncategorizedPokitUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetUncategorizedPokitUseCase.kt
new file mode 100644
index 00000000..9addf411
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/GetUncategorizedPokitUseCase.kt
@@ -0,0 +1,30 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitError
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.pokit.Pokit
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class GetUncategorizedPokitUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ companion object {
+ const val UNCATEGORIZED_POKIT_NAME = "미분류"
+ }
+
+ suspend fun getUncategoriezdPokit(): PokitResult {
+ val response = repository.getPokits(filterUncategorized = false, size = 30, page = 0)
+ if (response is PokitResult.Success) {
+ val uncategorizedPokit = response.result.find { it.name == UNCATEGORIZED_POKIT_NAME }
+ return if (uncategorizedPokit != null) {
+ PokitResult.Success(uncategorizedPokit)
+ } else {
+ PokitResult.Error(error = PokitError("미분류 카테고리를 찾을 수 없습니다.", "C_9999"))
+ }
+ } else {
+ response as PokitResult.Error
+ return PokitResult.Error(error = response.error.copy())
+ }
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/ModifyPokitUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/ModifyPokitUseCase.kt
new file mode 100644
index 00000000..13ced61a
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/pokit/ModifyPokitUseCase.kt
@@ -0,0 +1,17 @@
+package pokitmons.pokit.domain.usecase.pokit
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.repository.pokit.PokitRepository
+import javax.inject.Inject
+
+class ModifyPokitUseCase @Inject constructor(
+ private val repository: PokitRepository,
+) {
+ suspend fun modifyPokit(
+ pokitId: Int,
+ name: String,
+ imageId: Int,
+ ): PokitResult {
+ return repository.modifyPokit(pokitId = pokitId, name = name, imageId = imageId)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/search/AddRecentSearchWordUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/AddRecentSearchWordUseCase.kt
new file mode 100644
index 00000000..c63a11a4
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/AddRecentSearchWordUseCase.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.domain.usecase.search
+
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Inject
+
+class AddRecentSearchWordUseCase @Inject constructor(
+ private val repository: SearchRepository,
+) {
+ suspend fun addRecentSearchWord(word: String) {
+ repository.addRecentSearchWord(word)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/search/GetRecentSearchWordsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/GetRecentSearchWordsUseCase.kt
new file mode 100644
index 00000000..b7034c48
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/GetRecentSearchWordsUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.search
+
+import kotlinx.coroutines.flow.Flow
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Inject
+
+class GetRecentSearchWordsUseCase @Inject constructor(
+ private val repository: SearchRepository,
+) {
+ fun getWords(): Flow> {
+ return repository.getRecentSearchWords()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/search/GetUseRecentSearchWordsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/GetUseRecentSearchWordsUseCase.kt
new file mode 100644
index 00000000..da55a3fc
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/GetUseRecentSearchWordsUseCase.kt
@@ -0,0 +1,13 @@
+package pokitmons.pokit.domain.usecase.search
+
+import kotlinx.coroutines.flow.Flow
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Inject
+
+class GetUseRecentSearchWordsUseCase @Inject constructor(
+ private val repository: SearchRepository,
+) {
+ fun getUse(): Flow {
+ return repository.getUseRecentSearchWord()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/search/RemoveRecentSearchWordUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/RemoveRecentSearchWordUseCase.kt
new file mode 100644
index 00000000..1745d408
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/RemoveRecentSearchWordUseCase.kt
@@ -0,0 +1,16 @@
+package pokitmons.pokit.domain.usecase.search
+
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Inject
+
+class RemoveRecentSearchWordUseCase @Inject constructor(
+ private val repository: SearchRepository,
+) {
+ suspend fun removeWord(word: String) {
+ repository.removeSearchWord(word)
+ }
+
+ suspend fun removeAll() {
+ repository.removeAllSearchWords()
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/search/SetUseRecentSearchWordsUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/SetUseRecentSearchWordsUseCase.kt
new file mode 100644
index 00000000..25c7983e
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/search/SetUseRecentSearchWordsUseCase.kt
@@ -0,0 +1,12 @@
+package pokitmons.pokit.domain.usecase.search
+
+import pokitmons.pokit.domain.repository.search.SearchRepository
+import javax.inject.Inject
+
+class SetUseRecentSearchWordsUseCase @Inject constructor(
+ private val repository: SearchRepository,
+) {
+ suspend fun setUse(use: Boolean): Boolean {
+ return repository.setUseRecentSearchWord(use)
+ }
+}
diff --git a/domain/src/main/java/pokitmons/pokit/domain/usecase/setting/EditNicknameUseCase.kt b/domain/src/main/java/pokitmons/pokit/domain/usecase/setting/EditNicknameUseCase.kt
new file mode 100644
index 00000000..b439e285
--- /dev/null
+++ b/domain/src/main/java/pokitmons/pokit/domain/usecase/setting/EditNicknameUseCase.kt
@@ -0,0 +1,15 @@
+package pokitmons.pokit.domain.usecase.setting
+
+import pokitmons.pokit.domain.commom.PokitResult
+import pokitmons.pokit.domain.model.setting.EditNicknameResult
+import pokitmons.pokit.domain.repository.setting.SettingRepository
+import javax.inject.Inject
+
+class EditNicknameUseCase @Inject constructor(private val settingRepository: SettingRepository) {
+ suspend fun editNickname(nickname: String): PokitResult {
+ return when (val editNicknameResult = settingRepository.editNickname(nickname)) {
+ is PokitResult.Success -> PokitResult.Success(editNicknameResult.result)
+ is PokitResult.Error -> PokitResult.Error(editNicknameResult.error)
+ }
+ }
+}
diff --git a/feature/addlink/.gitignore b/feature/addlink/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/feature/addlink/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/addlink/build.gradle.kts b/feature/addlink/build.gradle.kts
new file mode 100644
index 00000000..59c2c114
--- /dev/null
+++ b/feature/addlink/build.gradle.kts
@@ -0,0 +1,75 @@
+plugins {
+ alias(libs.plugins.com.android.library)
+ alias(libs.plugins.org.jetbrains.kotlin.android)
+ alias(libs.plugins.hilt)
+ id("kotlin-kapt")
+}
+
+android {
+ namespace = "com.strayalpaca.addlink"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.1"
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+
+ implementation(libs.orbit.compose)
+ implementation(libs.orbit.core)
+ implementation(libs.orbit.viewmodel)
+
+ // coil
+ implementation(libs.coil.compose)
+
+ // hilt
+ implementation(libs.hilt)
+ kapt(libs.hilt.compiler)
+
+ implementation(project(":core:ui"))
+ implementation(project(":core:feature"))
+ implementation(project(":domain"))
+}
diff --git a/feature/addlink/consumer-rules.pro b/feature/addlink/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/feature/addlink/proguard-rules.pro b/feature/addlink/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/feature/addlink/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/addlink/src/androidTest/java/com/strayalpaca/addlink/ExampleInstrumentedTest.kt b/feature/addlink/src/androidTest/java/com/strayalpaca/addlink/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..8675bf96
--- /dev/null
+++ b/feature/addlink/src/androidTest/java/com/strayalpaca/addlink/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.strayalpaca.addlink
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.strayalpaca.addlink.test", appContext.packageName)
+ }
+}
diff --git a/feature/addlink/src/main/AndroidManifest.xml b/feature/addlink/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/feature/addlink/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+