diff --git a/Near/app/build.gradle.kts b/Near/app/build.gradle.kts index 50357e74..7165d602 100644 --- a/Near/app/build.gradle.kts +++ b/Near/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.kotlin.compose) alias(libs.plugins.hilt.application) alias(libs.plugins.kotlin.kapt) + alias(libs.plugins.kotlin.serialization) } android { @@ -73,4 +74,8 @@ dependencies { implementation(libs.room.ktx) kapt(libs.room.compiler) implementation(libs.room.paging) + // Navigation + implementation(libs.navigation.compose) + // Serialization + implementation(libs.kotlin.serialization.json) } diff --git a/Near/app/src/main/AndroidManifest.xml b/Near/app/src/main/AndroidManifest.xml index 1101a1e5..1a2cc18e 100644 --- a/Near/app/src/main/AndroidManifest.xml +++ b/Near/app/src/main/AndroidManifest.xml @@ -14,7 +14,7 @@ android:theme="@style/Theme.Near" tools:targetApi="31"> @@ -26,4 +26,4 @@ - \ No newline at end of file + diff --git a/Near/app/src/main/java/com/alarmy/near/MainActivity.kt b/Near/app/src/main/java/com/alarmy/near/MainActivity.kt deleted file mode 100644 index 738551df..00000000 --- a/Near/app/src/main/java/com/alarmy/near/MainActivity.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.alarmy.near - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.alarmy.near.ui.theme.NearTheme - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContent { - NearTheme { - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - Greeting( - name = "Android", - modifier = Modifier.padding(innerPadding), - ) - } - } - } - } -} - -@Composable -fun Greeting( - name: String, - modifier: Modifier = Modifier, -) { - Text( - text = "Hello $name!", - modifier = modifier, - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - NearTheme { - Greeting("Android") - } -} diff --git a/Near/app/src/main/java/com/alarmy/near/data/di/RepositoryModule.kt b/Near/app/src/main/java/com/alarmy/near/data/di/RepositoryModule.kt new file mode 100644 index 00000000..f63b55df --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/data/di/RepositoryModule.kt @@ -0,0 +1,17 @@ +package com.alarmy.near.data.di + +import com.alarmy.near.data.repository.ExampleRepository +import com.alarmy.near.data.repository.ExampleRepositoryImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface RepositoryModule { + @Binds + @Singleton + abstract fun bindExampleRepository(exampleRepositoryImpl: ExampleRepositoryImpl): ExampleRepository +} diff --git a/Near/app/src/main/java/com/alarmy/near/data/mapper/ExampleMapper.kt b/Near/app/src/main/java/com/alarmy/near/data/mapper/ExampleMapper.kt new file mode 100644 index 00000000..67093aaf --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/data/mapper/ExampleMapper.kt @@ -0,0 +1,9 @@ +package com.alarmy.near.data.mapper + +import com.alarmy.near.model.Example +import com.alarmy.near.network.response.ExampleEntity + +fun ExampleEntity.toModel(): Example = + Example( + id = id.toString(), + ) diff --git a/Near/app/src/main/java/com/alarmy/near/data/repository/ExampleRepository.kt b/Near/app/src/main/java/com/alarmy/near/data/repository/ExampleRepository.kt new file mode 100644 index 00000000..631d9a57 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/data/repository/ExampleRepository.kt @@ -0,0 +1,10 @@ +package com.alarmy.near.data.repository + +import com.alarmy.near.model.Example +import kotlinx.coroutines.flow.Flow + +interface ExampleRepository { + fun getExampleData(): String + + fun getData(): Flow +} diff --git a/Near/app/src/main/java/com/alarmy/near/data/repository/ExampleRepositoryImpl.kt b/Near/app/src/main/java/com/alarmy/near/data/repository/ExampleRepositoryImpl.kt new file mode 100644 index 00000000..f98375eb --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/data/repository/ExampleRepositoryImpl.kt @@ -0,0 +1,18 @@ +package com.alarmy.near.data.repository + +import com.alarmy.near.model.Example +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class ExampleRepositoryImpl + @Inject + constructor() : ExampleRepository { + override fun getExampleData(): String = "Hello from Repository" + + override fun getData(): Flow = + flow { + // val result = exampleService.fetchExample() + // emit(result.data.toModel()) + } + } diff --git a/Near/app/src/main/java/com/alarmy/near/model/.gitkeep b/Near/app/src/main/java/com/alarmy/near/model/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Near/app/src/main/java/com/alarmy/near/model/Example.kt b/Near/app/src/main/java/com/alarmy/near/model/Example.kt new file mode 100644 index 00000000..c6c1dd7c --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/model/Example.kt @@ -0,0 +1,5 @@ +package com.alarmy.near.model + +data class Example( + val id: String, +) diff --git a/Near/app/src/main/java/com/alarmy/near/di/AppModule.kt b/Near/app/src/main/java/com/alarmy/near/network/di/AppModule.kt similarity index 96% rename from Near/app/src/main/java/com/alarmy/near/di/AppModule.kt rename to Near/app/src/main/java/com/alarmy/near/network/di/AppModule.kt index 464b8cda..369900cc 100644 --- a/Near/app/src/main/java/com/alarmy/near/di/AppModule.kt +++ b/Near/app/src/main/java/com/alarmy/near/network/di/AppModule.kt @@ -1,4 +1,4 @@ -package com.alarmy.near.di +package com.alarmy.near.network.di // @Module // @InstallIn(SingletonComponent::class) diff --git a/Near/app/src/main/java/com/alarmy/near/network/response/ExampleEntity.kt b/Near/app/src/main/java/com/alarmy/near/network/response/ExampleEntity.kt new file mode 100644 index 00000000..b2c04583 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/network/response/ExampleEntity.kt @@ -0,0 +1,5 @@ +package com.alarmy.near.network.response + +data class ExampleEntity( + val id: Long, +) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt new file mode 100644 index 00000000..fd8eebb9 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeScreen.kt @@ -0,0 +1,51 @@ +package com.alarmy.near.presentation.feature.home + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.alarmy.near.presentation.feature.home.model.HomeUiState +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +internal fun HomeRoute( + viewModel: HomeViewModel = hiltViewModel(), + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, +) { + val uiState = viewModel.uiStateFlow.collectAsStateWithLifecycle() + HomeScreen( + uiState = uiState.value, + onContactClick = {}, + onRemoveContact = viewModel::removeContact, + ) +} + +@Composable +internal fun HomeScreen( + modifier: Modifier = Modifier, + uiState: HomeUiState, + onContactClick: (Long) -> Unit = { _ -> }, + onRemoveContact: (Long) -> Unit = { _ -> }, +) { + Column(modifier = Modifier.fillMaxSize().background(Color.White)) { + Text("홈 화면") + } +} + +@Preview +@Composable +internal fun HomeScreenPreview() { + NearTheme { + HomeScreen( + uiState = HomeUiState.Loading, + onContactClick = {}, + onRemoveContact = {}, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt new file mode 100644 index 00000000..6a0b0496 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/HomeViewModel.kt @@ -0,0 +1,54 @@ +package com.alarmy.near.presentation.feature.home + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.alarmy.near.data.repository.ExampleRepository +import com.alarmy.near.presentation.feature.home.model.HomeUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel + @Inject + constructor( + private val exampleRepository: ExampleRepository, + ) : ViewModel() { + // Example: 여러번 초기화되는 StateFlow + private val _uiStateFlow = MutableStateFlow(HomeUiState.Loading) + val uiStateFlow = _uiStateFlow.asStateFlow() + + // Example: 한 번만 초기화되는 StateFlow + private val exampleStateFlow = + exampleRepository + .getData() + .map { + // Mapping to UIState + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = HomeUiState.Loading, + ) + + fun fetchContacts() { + viewModelScope.launch { + exampleRepository + .getData() + .catch { + // handle error + }.collect { + // updateUI + } + } + } + + fun removeContact(id: Long) { + // contactRepository.removeContact(id) + } + } diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/model/HomeUiState.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/model/HomeUiState.kt new file mode 100644 index 00000000..c4510aef --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/model/HomeUiState.kt @@ -0,0 +1,9 @@ +package com.alarmy.near.presentation.feature.home.model + +sealed interface HomeUiState { + data object Loading : HomeUiState + + data class Success( + val data: Any, + ) : HomeUiState +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt new file mode 100644 index 00000000..748b9ab4 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/home/navigation/HomeNavigation.kt @@ -0,0 +1,29 @@ +package com.alarmy.near.presentation.feature.home.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.alarmy.near.presentation.feature.home.HomeRoute +import kotlinx.serialization.Serializable + +@Serializable +object RouteHome + +/* +* 추후 홈으로 화면 이동이 필요할 때 이 함수를 사용합니다. +* */ +fun NavController.navigateToHome(navOptions: NavOptions) { + navigate(RouteHome, navOptions) +} + +fun NavGraphBuilder.homeNavGraph( + onShowErrorSnackBar: (throwable: Throwable?) -> Unit, + onClickContact: (id: Long) -> Unit, +) { + composable { backStackEntry -> + HomeRoute( + onShowErrorSnackBar = onShowErrorSnackBar, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/MainActivity.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/MainActivity.kt new file mode 100644 index 00000000..35f00fdc --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/MainActivity.kt @@ -0,0 +1,21 @@ +package com.alarmy.near.presentation.feature.main + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import com.alarmy.near.presentation.ui.theme.NearTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + NearTheme { + NearApp() + } + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt new file mode 100644 index 00000000..4730b2ac --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearApp.kt @@ -0,0 +1,57 @@ +package com.alarmy.near.presentation.feature.main + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.exclude +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.launch + +@Composable +internal fun NearApp( + modifier: Modifier = Modifier, + navController: NavHostController = rememberNavController(), +) { + val snackBarState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + + Scaffold( + modifier = Modifier.fillMaxSize(), + snackbarHost = { + SnackbarHost( + hostState = snackBarState, + modifier = + Modifier.windowInsetsPadding( + WindowInsets.safeDrawing.exclude( + WindowInsets.ime, + ), + ), + ) + }, + ) { innerPadding -> + NearNavHost( + modifier = Modifier.padding(innerPadding), + navController = navController, + onShowSnackbar = { + scope.launch { + snackBarState.showSnackbar( + message = it?.message ?: return@launch, + duration = SnackbarDuration.Short, + ) + } + }, + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt new file mode 100644 index 00000000..ee75ae08 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/feature/main/NearNavHost.kt @@ -0,0 +1,28 @@ +package com.alarmy.near.presentation.feature.main + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import com.alarmy.near.presentation.feature.home.navigation.RouteHome +import com.alarmy.near.presentation.feature.home.navigation.homeNavGraph + +@Composable +internal fun NearNavHost( + modifier: Modifier = Modifier, + navController: NavHostController, + onShowSnackbar: (Throwable?) -> Unit = { _ -> }, +) { + /* + * 화면 이동 및 구성을 위한 컴포저블 함수입니다. + * */ + NavHost( + modifier = modifier, + navController = navController, + startDestination = RouteHome, + ) { + homeNavGraph(onShowErrorSnackBar = onShowSnackbar, onClickContact = { + // 예시: navController.navigate(RouteContact(it)) + }) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/LineTypeButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/LineTypeButton.kt new file mode 100644 index 00000000..20ed331a --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/LineTypeButton.kt @@ -0,0 +1,67 @@ +package com.alarmy.near.presentation.ui.component.button + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun NearLineTypeButton( + modifier: Modifier = Modifier, + enabled: Boolean = false, + onClick: () -> Unit, + contentPadding: PaddingValues = ButtonDefaults.ContentPadding, + text: String, +) { + NearBasicButton( + modifier = modifier, + onClick = onClick, + colors = + ButtonDefaults.buttonColors( + containerColor = NearTheme.colors.WHITE_FFFFFF, + contentColor = NearTheme.colors.BLACK_1A1A1A, + disabledContainerColor = NearTheme.colors.WHITE_FFFFFF, + disabledContentColor = NearTheme.colors.GRAY02_B7B7B7, + ), + enabled = enabled, + border = BorderStroke(width = 1.dp, color = NearTheme.colors.GRAY02_B7B7B7), + contentPadding = contentPadding, + ) { + Text(text = text, style = NearTheme.typography.B1_16_BOLD) + } +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearLineEnabledButtonPreview() { + Surface { + NearLineTypeButton( + modifier = Modifier.padding(horizontal = 20.dp), + enabled = true, + text = "Button", + onClick = {}, + contentPadding = PaddingValues(vertical = 17.dp), + ) + } +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearLineDisabledButtonPreview() { + Surface { + NearLineTypeButton( + modifier = Modifier.padding(horizontal = 20.dp), + enabled = false, + text = "Button", + onClick = {}, + contentPadding = PaddingValues(vertical = 17.dp), + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/NearBasicButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/NearBasicButton.kt new file mode 100644 index 00000000..68ded9eb --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/NearBasicButton.kt @@ -0,0 +1,60 @@ +package com.alarmy.near.presentation.ui.component.button + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun NearBasicButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + colors: ButtonColors = ButtonDefaults.buttonColors( + containerColor = NearTheme.colors.BLUE01_5AA2E9, + contentColor = NearTheme.colors.WHITE_FFFFFF, + ), + enabled: Boolean = true, + border: BorderStroke? = null, + contentPadding: PaddingValues, + content: @Composable RowScope.() -> Unit, +) { + Button( + modifier = + modifier + .heightIn(min = 56.dp) + .wrapContentHeight(), + colors = colors, + enabled = enabled, + onClick = onClick, + border = border, + shape = RoundedCornerShape(12.dp), + contentPadding = contentPadding, + content = content, + ) +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearBasicButtonPreview() { + Surface { + NearBasicButton( + modifier = Modifier.padding(horizontal = 20.dp), + content = { Text("Button", style = NearTheme.typography.B1_16_BOLD) }, + onClick = {}, + contentPadding = PaddingValues(vertical = 17.dp), + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/NearSolidTypeButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/NearSolidTypeButton.kt new file mode 100644 index 00000000..1d36d99c --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/button/NearSolidTypeButton.kt @@ -0,0 +1,65 @@ +package com.alarmy.near.presentation.ui.component.button + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun NearSolidTypeButton( + modifier: Modifier = Modifier, + enabled: Boolean = false, + onClick: () -> Unit, + contentPadding: PaddingValues = ButtonDefaults.ContentPadding, + text: String, +) { + NearBasicButton( + modifier = modifier, + onClick = onClick, + colors = + ButtonDefaults.buttonColors( + containerColor = NearTheme.colors.BLUE01_5AA2E9, + contentColor = NearTheme.colors.WHITE_FFFFFF, + disabledContainerColor = NearTheme.colors.GRAY02_B7B7B7, + disabledContentColor = NearTheme.colors.WHITE_FFFFFF, + ), + enabled = enabled, + contentPadding = contentPadding, + ) { + Text(text = text, style = NearTheme.typography.B1_16_BOLD) + } +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearSolidEnabledButtonPreview() { + Surface { + NearSolidTypeButton( + modifier = Modifier.padding(horizontal = 20.dp), + enabled = true, + text = "Button", + onClick = {}, + contentPadding = PaddingValues(vertical = 17.dp), + ) + } +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearSolidDisabledButtonPreview() { + Surface { + NearSolidTypeButton( + modifier = Modifier.padding(horizontal = 20.dp), + enabled = false, + text = "Button", + onClick = {}, + contentPadding = PaddingValues(vertical = 17.dp), + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt new file mode 100644 index 00000000..3e71272a --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearBackgroundCheckbox.kt @@ -0,0 +1,63 @@ +package com.alarmy.near.presentation.ui.component.checkbox + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +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.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.R + +@Composable +fun NearBackgroundCheckbox( + modifier: Modifier = Modifier, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + Image( + modifier = + modifier.clickable( + onClick = { onCheckedChange(!checked) }, + ), + painter = + if (checked) { + painterResource(R.drawable.btn_checkbox_h1_on) + } else { + painterResource(R.drawable.btn_checkbox_h1_off) + }, + contentDescription = + if (checked) { + stringResource( + R.string.content_description_checkbox_check, + ) + } else { + stringResource( + R.string.content_description_checkbox_un_check, + ) + }, + ) +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearBackgroundCheckboxPreview() { + Surface { + Row { + NearBackgroundCheckbox( + checked = true, + onCheckedChange = {}, + ) + Spacer(modifier = Modifier.width(50.dp)) + NearBackgroundCheckbox( + checked = false, + onCheckedChange = {}, + ) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearCheckbox.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearCheckbox.kt new file mode 100644 index 00000000..e2ce96aa --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/checkbox/NearCheckbox.kt @@ -0,0 +1,53 @@ +package com.alarmy.near.presentation.ui.component.checkbox + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +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 com.alarmy.near.R + +@Composable +fun NearCheckbox( + modifier: Modifier = Modifier, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + Image( + modifier = + modifier.clickable( + onClick = { onCheckedChange(!checked) }, + ), + painter = + if (checked) { + painterResource(R.drawable.btn_checkbox_h2_on) + } else { + painterResource(R.drawable.btn_checkbox_h2_off) + }, + contentDescription = null, + ) +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearCheckboxPreview() { + Surface { + Row { + NearCheckbox( + checked = true, + onCheckedChange = {}, + ) + Spacer(modifier = Modifier.width(50.dp)) + NearCheckbox( + checked = false, + onCheckedChange = {}, + ) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearLargeRadioButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearLargeRadioButton.kt new file mode 100644 index 00000000..5d24e427 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearLargeRadioButton.kt @@ -0,0 +1,53 @@ +package com.alarmy.near.presentation.ui.component.radiobutton + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +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 com.alarmy.near.R + +@Composable +fun NearLargeRadioButton( + modifier: Modifier = Modifier, + selected: Boolean, + onClick: (Boolean) -> Unit, +) { + Image( + modifier = + modifier.clickable( + onClick = { onClick(!selected) }, + ), + painter = + if (selected) { + painterResource(R.drawable.btn_radio_h1_on) + } else { + painterResource(R.drawable.btn_radio_h1_off) + }, + contentDescription = null, + ) +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearLargeRadioButtonPreview() { + Surface { + Row { + NearLargeRadioButton( + selected = true, + onClick = {}, + ) + Spacer(modifier = Modifier.width(50.dp)) + NearLargeRadioButton( + selected = false, + onClick = {}, + ) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt new file mode 100644 index 00000000..a8e10f30 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/radiobutton/NearSmallRadioButton.kt @@ -0,0 +1,53 @@ +package com.alarmy.near.presentation.ui.component.radiobutton + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +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 com.alarmy.near.R + +@Composable +fun NearSmallRadioButton( + modifier: Modifier = Modifier, + selected: Boolean, + onClick: (Boolean) -> Unit, +) { + Image( + modifier = + modifier.clickable( + onClick = { onClick(!selected) }, + ), + painter = + if (selected) { + painterResource(R.drawable.btn_radio_h2_on) + } else { + painterResource(R.drawable.btn_radio_h2_off) + }, + contentDescription = null, + ) +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearSmallRadioButtonPreview() { + Surface { + Row { + NearSmallRadioButton( + selected = true, + onClick = {}, + ) + Spacer(modifier = Modifier.width(50.dp)) + NearSmallRadioButton( + selected = false, + onClick = {}, + ) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt new file mode 100644 index 00000000..c5fb4381 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearLimitedTextField.kt @@ -0,0 +1,121 @@ +package com.alarmy.near.presentation.ui.component.textfield + +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.component.textfield.internal.NearOutlinedTextFieldDecorationBox +import com.alarmy.near.presentation.ui.component.textfield.internal.NearTextFieldColors +import com.alarmy.near.presentation.ui.component.textfield.internal.NearTextFieldDecorationContainer +import com.alarmy.near.presentation.ui.theme.NearTheme + +private const val MAX_TEXT_COUNT = 200 + +@Composable +fun NearLimitedTextField( + modifier: Modifier = Modifier, + value: String, + maxTextCount: Int = MAX_TEXT_COUNT, + onValueChange: (String) -> Unit, + enabled: Boolean, + placeHolderText: String, + singleLine: Boolean = false, + interactionSource: InteractionSource = remember { MutableInteractionSource() }, +) { + val colors = NearTextFieldColors() + + // TextCount에서 End,Bottom에 Margin을 주고자 했는데, 원인은 모르곘으나 마진차가 있었음 + // 이를 해결하기 위한 임시 해결책으로 1줄일 때 가운데 정렬 설정 + val lineCount = remember { mutableIntStateOf(1) } + + NearTextField( + modifier = modifier, + value = value, + enabled = enabled, + onValueChange = onValueChange, + placeHolderText = placeHolderText, + onTextLayout = { + lineCount.intValue = it.lineCount + }, + singleLine = singleLine, + interactionSource = interactionSource, + decorationBox = { innerTextField -> + NearOutlinedTextFieldDecorationBox( + value = value, + innerTextField = innerTextField, + enabled = enabled, + singleLine = singleLine, + interactionSource = interactionSource, + colors = colors, + placeHolderText = placeHolderText, + contentPadding = + PaddingValues( + top = 16.dp, + bottom = 16.dp, + start = 16.dp, + end = 76.dp, + // Container 구조상 Row를 통해 TextCount의 텍스트와 마진을 줄 수 없는 구조 + // 현재 차선책으로 강제 value 설정 + ), + container = { + NearTextFieldDecorationContainer( + enabled = enabled, + interactionSource = interactionSource, + colors = colors, + ) + if (value.count() > 0 && enabled) { + Row( + modifier = Modifier, + horizontalArrangement = Arrangement.End, + verticalAlignment = + if (lineCount.intValue == 1) { + Alignment.CenterVertically + } else { + Alignment.Bottom + }, + ) { + Text( + text = "${value.count()}/$maxTextCount", + modifier = + Modifier.padding( + end = 16.dp, + bottom = if (lineCount.intValue == 1) 0.dp else 16.dp, + ), + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY02_B7B7B7, + ) + } + } + }, + ) + }, + ) +} + +@Preview(widthDp = 370, heightDp = 52, showBackground = true) +@Composable +fun NearEnabledLimitedTextFieldPreview() { + Surface(modifier = Modifier.padding(horizontal = 20.dp)) { + NearLimitedTextField( + modifier = + Modifier + .wrapContentHeight(), + enabled = true, + value = "테스트", + onValueChange = {}, + placeHolderText = "플레이스 홀더", + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt new file mode 100644 index 00000000..77c47934 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/NearTextField.kt @@ -0,0 +1,106 @@ +package com.alarmy.near.presentation.ui.component.textfield + +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.component.textfield.internal.NearOutlinedTextFieldDecorationBox +import com.alarmy.near.presentation.ui.component.textfield.internal.NearTextFieldColors +import com.alarmy.near.presentation.ui.theme.NearTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NearTextField( + modifier: Modifier = Modifier, + value: String, + enabled: Boolean = true, + onValueChange: (String) -> Unit, + placeHolderText: String = "", + singleLine: Boolean = false, + onTextLayout: (textLayoutResult: TextLayoutResult) -> Unit = {}, + interactionSource: InteractionSource = remember { MutableInteractionSource() }, + decorationBox: (@Composable (innerTextField: @Composable () -> Unit) -> Unit)? = null, +) { + val colors = NearTextFieldColors() + + BasicTextField( + value = value, + modifier = modifier, + enabled = enabled, + textStyle = + NearTheme.typography.B2_14_MEDIUM.copy( + color = if (enabled) colors.focusedTextColor else colors.disabledTextColor, + ), + cursorBrush = SolidColor(NearTheme.colors.BLACK_1A1A1A), + onValueChange = onValueChange, + decorationBox = + decorationBox + ?: { innerTextField -> + NearOutlinedTextFieldDecorationBox( + value = value, + innerTextField = innerTextField, + enabled = enabled, + singleLine = singleLine, + interactionSource = interactionSource, + colors = colors, + placeHolderText = placeHolderText, + ) + }, + onTextLayout = onTextLayout + ) +} + +@Preview(widthDp = 370, heightDp = 80, showBackground = true) +@Composable +fun NearUnFocusedTextFieldPreview() { + Surface(modifier = Modifier.padding(horizontal = 20.dp)) { + NearTextField( + modifier = Modifier.wrapContentHeight(), + value = "", + onValueChange = {}, + placeHolderText = "플레이스 홀더", + ) + } +} + +@Preview(widthDp = 370, heightDp = 80, showBackground = true) +@Composable +fun NearDisabledTextFieldPreview() { + Surface(modifier = Modifier.padding(horizontal = 20.dp)) { + NearTextField( + modifier = + Modifier + .wrapContentHeight(), + enabled = false, + value = "비활성화", + onValueChange = {}, + placeHolderText = "플레이스 홀더", + ) + } +} + +@Preview(widthDp = 370, heightDp = 80, showBackground = true) +@Composable +fun NearEnabledTextFieldPreview() { + Surface(modifier = Modifier.padding(horizontal = 20.dp)) { + NearTextField( + modifier = + Modifier + .wrapContentHeight(), + enabled = true, + value = "입력완료", + onValueChange = {}, + placeHolderText = "플레이스 홀더", + ) + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt new file mode 100644 index 00000000..9e48fad5 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearOutlinedTextFieldDecorationBox.kt @@ -0,0 +1,69 @@ +package com.alarmy.near.presentation.ui.component.textfield.internal + +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.theme.NearTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun NearOutlinedTextFieldDecorationBox( + value: String, + innerTextField: @Composable () -> Unit, + enabled: Boolean, + singleLine: Boolean, + interactionSource: InteractionSource, + colors: TextFieldColors, + contentPadding: PaddingValues = PaddingValues(16.dp), + placeHolderText: String, + container: (@Composable () -> Unit) = { + NearTextFieldDecorationContainer( + enabled = enabled, + interactionSource = interactionSource, + colors = colors + ) + } +) { + OutlinedTextFieldDefaults.DecorationBox( + contentPadding = contentPadding, + value = value, + innerTextField = innerTextField, + enabled = enabled, + singleLine = singleLine, + interactionSource = interactionSource, + visualTransformation = VisualTransformation.None, + placeholder = { + Text( + text = placeHolderText, + style = NearTheme.typography.B2_14_MEDIUM, + color = NearTheme.colors.GRAY02_B7B7B7, + ) + }, + container = container, + ) +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +internal fun NearTextFieldDecorationContainer( + enabled: Boolean, + interactionSource: InteractionSource, + colors: TextFieldColors, +) { + OutlinedTextFieldDefaults.Container( + enabled = enabled, + isError = false, + interactionSource = interactionSource, + colors = colors, + shape = RoundedCornerShape(12.dp), + focusedBorderThickness = (1.5).dp, + unfocusedBorderThickness = (1.5).dp, + ) +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearTextFieldColors.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearTextFieldColors.kt new file mode 100644 index 00000000..47bdb9c2 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/textfield/internal/NearTextFieldColors.kt @@ -0,0 +1,21 @@ +package com.alarmy.near.presentation.ui.component.textfield.internal + +import android.annotation.SuppressLint +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.runtime.Composable +import com.alarmy.near.presentation.ui.theme.NearTheme + +@SuppressLint("ComposableNaming") +@Composable +fun NearTextFieldColors() = + OutlinedTextFieldDefaults.colors( + focusedBorderColor = NearTheme.colors.BLUE02_8ACCFF, + focusedTextColor = NearTheme.colors.BLACK_1A1A1A, + focusedContainerColor = NearTheme.colors.WHITE_FFFFFF, + unfocusedTextColor = NearTheme.colors.BLACK_1A1A1A, + unfocusedBorderColor = NearTheme.colors.GRAY03_EBEBEB, + unfocusedContainerColor = NearTheme.colors.WHITE_FFFFFF, + disabledTextColor = NearTheme.colors.GRAY02_B7B7B7, + disabledContainerColor = NearTheme.colors.GRAY04_F7F7F7, + disabledBorderColor = NearTheme.colors.GRAY03_EBEBEB, + ) diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/toggle/NearToggleButton.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/toggle/NearToggleButton.kt new file mode 100644 index 00000000..e56cc837 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/component/toggle/NearToggleButton.kt @@ -0,0 +1,116 @@ +package com.alarmy.near.presentation.ui.component.toggle + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.alarmy.near.presentation.ui.theme.NearTheme + +@Composable +fun NearToggleButton( + modifier: Modifier = Modifier, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + NearCustomSwitch( + isChecked = checked, + onCheckedChange, + scale = 1f, + width = 48.dp, + height = 26.dp, + strokeWidth = 0.dp, + checkedTrackColor = NearTheme.colors.BLUE02_8ACCFF, + uncheckedTrackColor = Color(0xffDEDEDE), + ) +} + +@Composable +private fun NearCustomSwitch( + isChecked: Boolean, + onCheckedChange: (Boolean) -> Unit, + scale: Float = 2f, + width: Dp = 36.dp, + height: Dp = 20.dp, + strokeWidth: Dp = 2.dp, + checkedTrackColor: Color = Color(0xFF35898F), + uncheckedTrackColor: Color = Color(0xFFe0e0e0), + gapBetweenThumbAndTrackEdge: Dp = 4.dp, +) { + val thumbRadius = (height / 2) - gapBetweenThumbAndTrackEdge + + // To move thumb, we need to calculate the position (along x axis) + val animatePosition = + animateFloatAsState( + targetValue = + if (isChecked) { + with(LocalDensity.current) { (width - thumbRadius - gapBetweenThumbAndTrackEdge).toPx() } + } else { + with(LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge).toPx() } + }, + ) + + Canvas( + modifier = + Modifier + .size(width = width, height = height) + .scale(scale = scale) + .pointerInput(Unit) { + detectTapGestures( + onTap = { + // This is called when the user taps on the canvas + onCheckedChange(!isChecked) + }, + ) + }, + ) { + // Track + drawRoundRect( + color = if (isChecked) checkedTrackColor else uncheckedTrackColor, + cornerRadius = CornerRadius(x = 13.dp.toPx(), y = 13.dp.toPx()), + ) + + // Thumb + drawCircle( + color = Color(0xffffffff), + radius = thumbRadius.toPx(), + center = + Offset( + x = animatePosition.value, + y = size.height / 2, + ), + ) + } +} + +@Preview(widthDp = 360, heightDp = 70, showBackground = true) +@Composable +fun NearToggleButtonPreview() { + Surface { + Row { + NearToggleButton( + checked = true, + onCheckedChange = {}, + ) + Spacer(modifier = Modifier.width(50.dp)) + NearToggleButton( + checked = false, + onCheckedChange = {}, + ) + } + } +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt new file mode 100644 index 00000000..61de2917 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Color.kt @@ -0,0 +1,39 @@ +@file:Suppress("SpellCheckingInspection") + +package com.alarmy.near.presentation.ui.theme + +import androidx.compose.ui.graphics.Color + +object NearColorPallete { + val BLACK_1A1A1A = Color(0xFF1A1A1A) + val WHITE_FFFFFF = Color(0xFFFFFFFF) + val GRAY01_888888 = Color(0xFF888888) + val GRAY02_B7B7B7 = Color(0xFFB7B7B7) + val GRAY03_EBEBEB = Color(0xFFEBEBEB) + val GRAY04_F7F7F7 = Color(0xFFF7F7F7) + val BLUE01_5AA2E9 = Color(0xFF5AA2E9) + val BLUE02_8ACCFF = Color(0xFF8ACCFF) + val BG01_E3F0F9 = Color(0xFFE3F0F9) + val BG02_F4F9FD = Color(0xFFF4F9FD) + val NEGATIVE_F04E4E = Color(0xFFF04E4E) + val DIM_000000 = Color(0x99000000) +} + +@Suppress("PropertyName") +data class NearColor( + val BLACK_1A1A1A: Color = NearColorPallete.BLACK_1A1A1A, + val WHITE_FFFFFF: Color = NearColorPallete.WHITE_FFFFFF, + val GRAY01_888888: Color = NearColorPallete.GRAY01_888888, + val GRAY02_B7B7B7: Color = NearColorPallete.GRAY02_B7B7B7, + val GRAY03_EBEBEB: Color = NearColorPallete.GRAY03_EBEBEB, + val GRAY04_F7F7F7: Color = NearColorPallete.GRAY04_F7F7F7, + val BLUE01_5AA2E9: Color = NearColorPallete.BLUE01_5AA2E9, + val BLUE02_8ACCFF: Color = NearColorPallete.BLUE02_8ACCFF, + val BG01_E3F0F9: Color = NearColorPallete.BG01_E3F0F9, + val BG02_F4F9FD: Color = NearColorPallete.BG02_F4F9FD, + val NEGATIVE_F04E4E: Color = NearColorPallete.NEGATIVE_F04E4E, + val DIM_000000: Color = NearColorPallete.DIM_000000, +) + +val darkColor = NearColor() +val lightColor = NearColor() diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Theme.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Theme.kt new file mode 100644 index 00000000..b6167e82 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Theme.kt @@ -0,0 +1,42 @@ +package com.alarmy.near.presentation.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.staticCompositionLocalOf + +val LocalCustomColors = + staticCompositionLocalOf { + NearColor() + } + +val LocalCustomTypography = + staticCompositionLocalOf { + Typography + } + +@Composable +fun NearTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val colorScheme = + when { + darkTheme -> lightColor // TODO DarkTheme 추가시 수정 + else -> lightColor + } + CompositionLocalProvider( + LocalCustomColors provides colorScheme, + LocalCustomTypography provides Typography, + content = content, + ) +} + +object NearTheme { + val colors: NearColor + @Composable + get() = LocalCustomColors.current + val typography: NearTypography + @Composable + get() = LocalCustomTypography.current +} diff --git a/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt new file mode 100644 index 00000000..99b830e4 --- /dev/null +++ b/Near/app/src/main/java/com/alarmy/near/presentation/ui/theme/Type.kt @@ -0,0 +1,221 @@ +package com.alarmy.near.presentation.ui.theme + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.alarmy.near.R + +@Suppress("PropertyName") +@Immutable +data class NearTypography( + val H1_24_BOLD: TextStyle, + val H1_24_MEDIUM: TextStyle, + val H1_24_REGULAR: TextStyle, + val H2_18_BOLD: TextStyle, + val B1_16_BOLD: TextStyle, + val B1_16_MEDIUM: TextStyle, + val B2_14_BOLD: TextStyle, + val B2_14_MEDIUM: TextStyle, + val FC_12_BOLD: TextStyle, + val FC_12_MEDIUM: TextStyle, +) + +val Pretendard = + FontFamily( + Font( + resId = R.font.pretendard_bold, + weight = FontWeight.Bold, + ), + Font( + resId = R.font.pretendard_regular, + weight = FontWeight.Normal, + ), + Font( + resId = R.font.pretendard_extra_bold, + weight = FontWeight.ExtraBold, + ), + Font( + resId = R.font.pretendard_extra_light, + weight = FontWeight.ExtraLight, + ), + Font( + resId = R.font.pretendard_extra_light, + weight = FontWeight.Light, + ), + Font( + resId = R.font.pretendard_medium, + weight = FontWeight.Medium, + ), + Font( + resId = R.font.pretendard_semi_bold, + weight = FontWeight.SemiBold, + ), + Font( + resId = R.font.pretendard_thin, + weight = FontWeight.Thin, + ), + ) + +internal val Typography = + NearTypography( + H1_24_BOLD = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + lineHeight = 34.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 24.sp, + letterSpacingPercent = -0.25f, + ), + ), + H1_24_MEDIUM = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Medium, + fontSize = 24.sp, + lineHeight = 34.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 24.sp, + letterSpacingPercent = -0.25f, + ), + ), + H1_24_REGULAR = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Normal, + fontSize = 24.sp, + lineHeight = 34.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 24.sp, + letterSpacingPercent = -0.25f, + ), + ), + H2_18_BOLD = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + lineHeight = 24.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 18.sp, + letterSpacingPercent = -0.25f, + ), + ), + B1_16_BOLD = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + lineHeight = 22.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 16.sp, + letterSpacingPercent = -0.25f, + ), + ), + B1_16_MEDIUM = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 22.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 16.sp, + letterSpacingPercent = -0.25f, + ), + ), + B2_14_BOLD = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 14.sp, + letterSpacingPercent = -0.25f, + ), + ), + B2_14_MEDIUM = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 14.sp, + letterSpacingPercent = -0.25f, + ), + ), + FC_12_BOLD = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Bold, + fontSize = 12.sp, + lineHeight = 18.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 12.sp, + letterSpacingPercent = -0.25f, + ), + ), + FC_12_MEDIUM = + TextStyle( + fontFamily = Pretendard, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = 18.sp, + letterSpacing = + letterSpacingToSp( + fontSize = 12.sp, + letterSpacingPercent = -0.25f, + ), + ), + ) + +@Preview(showBackground = true) +@Composable +private fun TypographyPreview() { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 20.dp)) { + Text("소중한 사람들과 더 가까워지는 시간 24", style = Typography.H1_24_BOLD) + Text("소중한 사람들과 더 가까워지는 시간 24", style = Typography.H1_24_MEDIUM) + Text("소중한 사람들과 더 가까워지는 시간 24", style = Typography.H1_24_REGULAR) + Text("소중한 사람들과 더 가까워지는 시간 18", style = Typography.H2_18_BOLD) + Text("소중한 사람들과 더 가까워지는 시간 16", style = Typography.B1_16_BOLD) + Text("소중한 사람들과 더 가까워지는 시간 16", style = Typography.B1_16_MEDIUM) + Text("소중한 사람들과 더 가까워지는 시간 14", style = Typography.B2_14_BOLD) + Text("소중한 사람들과 더 가까워지는 시간 14", style = Typography.B2_14_MEDIUM) + Text("소중한 사람들과 더 가까워지는 시간 12", style = Typography.FC_12_BOLD) + Text("소중한 사람들과 더 가까워지는 시간 12", style = Typography.FC_12_MEDIUM) + } +} + +/** + * Converts Figma letterSpacing (%) to Android Compose TextUnit (sp). + * + * @param fontSize Font size in sp. + * @param letterSpacingPercent Letter spacing in percent (e.g., -0.25 for -0.25%) + * @return Converted letterSpacing in sp + */ +private fun letterSpacingToSp( + fontSize: TextUnit, + letterSpacingPercent: Float, +): TextUnit = (fontSize.value * (letterSpacingPercent / 100f)).sp diff --git a/Near/app/src/main/java/com/alarmy/near/repository/ExampleRepository.kt b/Near/app/src/main/java/com/alarmy/near/repository/ExampleRepository.kt deleted file mode 100644 index d6ea4d99..00000000 --- a/Near/app/src/main/java/com/alarmy/near/repository/ExampleRepository.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.alarmy.near.repository - -interface ExampleRepository { - fun getExampleData(): String -} diff --git a/Near/app/src/main/java/com/alarmy/near/repository/ExampleRepositoryImpl.kt b/Near/app/src/main/java/com/alarmy/near/repository/ExampleRepositoryImpl.kt deleted file mode 100644 index 4709a51f..00000000 --- a/Near/app/src/main/java/com/alarmy/near/repository/ExampleRepositoryImpl.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.alarmy.near.repository - -class ExampleRepositoryImpl : ExampleRepository { - override fun getExampleData(): String = "Hello from Repository" -} diff --git a/Near/app/src/main/java/com/alarmy/near/ui/theme/Color.kt b/Near/app/src/main/java/com/alarmy/near/ui/theme/Color.kt deleted file mode 100644 index 869c5ab5..00000000 --- a/Near/app/src/main/java/com/alarmy/near/ui/theme/Color.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.alarmy.near.ui.theme - -import androidx.compose.ui.graphics.Color - -val Purple80 = Color(0xFFD0BCFF) -val PurpleGrey80 = Color(0xFFCCC2DC) -val Pink80 = Color(0xFFEFB8C8) - -val Purple40 = Color(0xFF6650a4) -val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/Near/app/src/main/java/com/alarmy/near/ui/theme/Theme.kt b/Near/app/src/main/java/com/alarmy/near/ui/theme/Theme.kt deleted file mode 100644 index 7ed827c7..00000000 --- a/Near/app/src/main/java/com/alarmy/near/ui/theme/Theme.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.alarmy.near.ui.theme - -import android.app.Activity -import android.os.Build -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext - -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ -) - -@Composable -fun NearTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, - content: @Composable () -> Unit -) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } - - darkTheme -> DarkColorScheme - else -> LightColorScheme - } - - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, - content = content - ) -} \ No newline at end of file diff --git a/Near/app/src/main/java/com/alarmy/near/ui/theme/Type.kt b/Near/app/src/main/java/com/alarmy/near/ui/theme/Type.kt deleted file mode 100644 index fdc85618..00000000 --- a/Near/app/src/main/java/com/alarmy/near/ui/theme/Type.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.alarmy.near.ui.theme - -import androidx.compose.material3.Typography -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.sp - -// Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ -) \ No newline at end of file diff --git a/Near/app/src/main/res/drawable/btn_checkbox_h1_off.xml b/Near/app/src/main/res/drawable/btn_checkbox_h1_off.xml new file mode 100644 index 00000000..33746460 --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_checkbox_h1_off.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Near/app/src/main/res/drawable/btn_checkbox_h1_on.xml b/Near/app/src/main/res/drawable/btn_checkbox_h1_on.xml new file mode 100644 index 00000000..1c158816 --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_checkbox_h1_on.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Near/app/src/main/res/drawable/btn_checkbox_h2_off.xml b/Near/app/src/main/res/drawable/btn_checkbox_h2_off.xml new file mode 100644 index 00000000..3e4aac37 --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_checkbox_h2_off.xml @@ -0,0 +1,13 @@ + + + diff --git a/Near/app/src/main/res/drawable/btn_checkbox_h2_on.xml b/Near/app/src/main/res/drawable/btn_checkbox_h2_on.xml new file mode 100644 index 00000000..70b78948 --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_checkbox_h2_on.xml @@ -0,0 +1,13 @@ + + + diff --git a/Near/app/src/main/res/drawable/btn_radio_h1_off.xml b/Near/app/src/main/res/drawable/btn_radio_h1_off.xml new file mode 100644 index 00000000..2e05b5cd --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_radio_h1_off.xml @@ -0,0 +1,14 @@ + + + + diff --git a/Near/app/src/main/res/drawable/btn_radio_h1_on.xml b/Near/app/src/main/res/drawable/btn_radio_h1_on.xml new file mode 100644 index 00000000..c7a463ab --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_radio_h1_on.xml @@ -0,0 +1,14 @@ + + + + diff --git a/Near/app/src/main/res/drawable/btn_radio_h2_off.xml b/Near/app/src/main/res/drawable/btn_radio_h2_off.xml new file mode 100644 index 00000000..8b5686cc --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_radio_h2_off.xml @@ -0,0 +1,14 @@ + + + + diff --git a/Near/app/src/main/res/drawable/btn_radio_h2_on.xml b/Near/app/src/main/res/drawable/btn_radio_h2_on.xml new file mode 100644 index 00000000..c2de1784 --- /dev/null +++ b/Near/app/src/main/res/drawable/btn_radio_h2_on.xml @@ -0,0 +1,14 @@ + + + + diff --git a/Near/app/src/main/res/font/pretendard_black.ttf b/Near/app/src/main/res/font/pretendard_black.ttf new file mode 100644 index 00000000..d0c1db81 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_black.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_bold.ttf b/Near/app/src/main/res/font/pretendard_bold.ttf new file mode 100644 index 00000000..fb07fc65 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_bold.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_extra_bold.ttf b/Near/app/src/main/res/font/pretendard_extra_bold.ttf new file mode 100644 index 00000000..9d5fe072 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_extra_bold.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_extra_light.ttf b/Near/app/src/main/res/font/pretendard_extra_light.ttf new file mode 100644 index 00000000..09e65428 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_extra_light.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_light.ttf b/Near/app/src/main/res/font/pretendard_light.ttf new file mode 100644 index 00000000..2e8541d6 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_light.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_medium.ttf b/Near/app/src/main/res/font/pretendard_medium.ttf new file mode 100644 index 00000000..1db67c68 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_medium.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_regular.ttf b/Near/app/src/main/res/font/pretendard_regular.ttf new file mode 100644 index 00000000..01147e99 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_regular.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_semi_bold.ttf b/Near/app/src/main/res/font/pretendard_semi_bold.ttf new file mode 100644 index 00000000..9f2690f0 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_semi_bold.ttf differ diff --git a/Near/app/src/main/res/font/pretendard_thin.ttf b/Near/app/src/main/res/font/pretendard_thin.ttf new file mode 100644 index 00000000..fe9825f1 Binary files /dev/null and b/Near/app/src/main/res/font/pretendard_thin.ttf differ diff --git a/Near/app/src/main/res/values/strings.xml b/Near/app/src/main/res/values/strings.xml index 7d9190ac..deaa8adc 100644 --- a/Near/app/src/main/res/values/strings.xml +++ b/Near/app/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ Near - \ No newline at end of file + 체크 + 체크 해제 + diff --git a/Near/gradle/libs.versions.toml b/Near/gradle/libs.versions.toml index b5aa516e..b0336eb5 100644 --- a/Near/gradle/libs.versions.toml +++ b/Near/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] agp = "8.10.1" -kotlin = "2.0.21" +kotlin = "2.2.0" coreKtx = "1.16.0" junit = "4.13.2" junitVersion = "1.2.1" @@ -9,7 +9,7 @@ lifecycleRuntimeKtx = "2.9.1" activityCompose = "1.10.1" composeBom = "2024.09.00" # Hilt -hiltVersion = "2.51.1" +hiltVersion = "2.57" hiltNavigationVersion = "1.2.0" # Retrofit retrofitVersion = "2.9.0" @@ -19,6 +19,10 @@ glideVersion = "4.16.0" roomVersion = "2.6.1" # Ktlint ktlintVersion = "13.0.0" +# Navigation +navigationVersion = "2.9.2" +# Kotlin Serialization +kotlinSerializationVersion = "1.9.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -46,6 +50,8 @@ room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = " room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomVersion" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomVersion" } room-paging = { group = "androidx.room", name = "room-paging", version.ref = "roomVersion" } +navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationVersion" } +kotlin-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinSerializationVersion" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -54,4 +60,5 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko hilt-application = { id = "com.google.dagger.hilt.android", version.ref = "hiltVersion" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintVersion" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }