diff --git a/README.md b/README.md index a6a2b9c..8f0dfeb 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -# android-signup# android-signup +# android-signup + +## ๐Ÿš€1๋‹จ๊ณ„ - ์ปดํฌ์ฆˆ ๊ธฐ์ดˆ +- [x] ์ œ์‹œ๋œ ํ•™์Šต ํ…Œ์ŠคํŠธ ์™„์„ฑ +- [x] Preview ๋…ธ์ถœ +- [x] Preview์˜ interactive ๋ชจ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฒ„ํŠผ ํด๋ฆญํ•ด๋ณด๊ธฐ + +### ๐Ÿš€2๋‹จ๊ณ„ - ๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ +- [x] ํšŒ์›๊ฐ€์ž… ๋ทฐ ๊ตฌํ˜„ + - Material3 Button, TextField๋ฅผ ํ™œ์šฉ \ No newline at end of file diff --git a/app/.editorConfig b/app/.editorConfig new file mode 100644 index 0000000..931bfa6 --- /dev/null +++ b/app/.editorConfig @@ -0,0 +1,2 @@ +[*.{kt,kts}] +ktlint_function_naming_ignore_when_annotated_with=Composable \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1e47716..8dab83d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,7 +25,7 @@ android { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } diff --git a/app/src/androidTest/java/nextstep/signup/LayoutBasicsTest.kt b/app/src/androidTest/java/nextstep/signup/LayoutBasicsTest.kt new file mode 100644 index 0000000..062693d --- /dev/null +++ b/app/src/androidTest/java/nextstep/signup/LayoutBasicsTest.kt @@ -0,0 +1,78 @@ +package nextstep.signup + +import androidx.compose.runtime.Composable +import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onChildren +import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.tooling.preview.Preview +import org.junit.Rule +import org.junit.Test + +class LayoutBasicsTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun text() { + val text = "์•ˆ๋…• ๋‚œ ์ปดํฌ์ฆˆ์•ผ~" + composeTestRule.setContent { + TextComposable() + } + + composeTestRule + .onNodeWithText(text) + .assertExists() + } + + @Test + fun column() { + composeTestRule.setContent { + ColumnComposable() + } + + composeTestRule.onNodeWithTag("์ด๋ฆ„") + .onChildren() + .assertCountEquals(3) + .onFirst() + .assert(hasText("๊นœํฌ์ฆˆ")) + } + + @Test + fun button() { + composeTestRule.setContent { + ButtonComposable() + } + + val button = + composeTestRule + .onNodeWithTag("๋ฒ„ํŠผ") + .performClick() + + button.assertIsNotEnabled() + } +} + +@Preview(showBackground = true) +@Composable +fun TextPreview() { + TextComposable() +} + +@Preview(showBackground = true) +@Composable +fun ColumnPreview() { + ColumnComposable() +} + +@Preview(showBackground = true) +@Composable +fun ButtonPreview() { + ButtonComposable() +} diff --git a/app/src/androidTest/java/nextstep/signup/TestComposable.kt b/app/src/androidTest/java/nextstep/signup/TestComposable.kt new file mode 100644 index 0000000..66eec3e --- /dev/null +++ b/app/src/androidTest/java/nextstep/signup/TestComposable.kt @@ -0,0 +1,54 @@ +package nextstep.signup + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +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 + +@Composable +fun TextComposable() { + Text( + text = "์•ˆ๋…• ๋‚œ ์ปดํฌ์ฆˆ์•ผ~", + color = Color.Blue, + style = + TextStyle( + fontSize = 26.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily.SansSerif, + ), + ) +} + +@Composable +fun ColumnComposable() { + Column( + modifier = Modifier.testTag("์ด๋ฆ„"), + ) { + Text(text = "๊นœํฌ์ฆˆ", color = Color.Blue) + Text(text = "ํ‚ดํฌ์ฆˆ", color = Color.Cyan) + Text(text = "๋”ํฌ์ฆˆ", color = Color.Yellow) + } +} + +@Composable +fun ButtonComposable() { + val enabled = remember { mutableStateOf(true) } + Button( + onClick = { + enabled.value = false + }, + enabled = enabled.value, + modifier = Modifier.testTag("๋ฒ„ํŠผ"), + ) { + Text("ํด๋ฆญํ•ด์ฃผ์„ธ์š”") + } +} diff --git a/app/src/main/java/nextstep/signup/MainActivity.kt b/app/src/main/java/nextstep/signup/MainActivity.kt index a74bc99..5717f34 100644 --- a/app/src/main/java/nextstep/signup/MainActivity.kt +++ b/app/src/main/java/nextstep/signup/MainActivity.kt @@ -3,26 +3,42 @@ package nextstep.signup import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme 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.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import nextstep.signup.ui.ButtonView +import nextstep.signup.ui.TextFieldView +import nextstep.signup.ui.TextView import nextstep.signup.ui.theme.SignupTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContent { SignupTheme { - // A surface container using the 'background' color from the theme Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background + modifier = + Modifier + .fillMaxSize() + .padding(top = 56.dp, start = 32.dp, end = 32.dp), + color = MaterialTheme.colorScheme.background, ) { - Greeting("Android") + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TextView(R.string.main_greeting) + TextFieldScreen() + ButtonView(R.string.main_sign_up, paddingTop = 42.dp) + } } } } @@ -30,17 +46,17 @@ class MainActivity : ComponentActivity() { } @Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) +fun TextFieldScreen() { + TextFieldView(paddingTop = 42.dp, label = R.string.main_user_name) + TextFieldView(paddingTop = 36.dp, label = R.string.main_email, keyboardType = KeyboardType.Email) + TextFieldView(paddingTop = 36.dp, label = R.string.main_password, keyboardType = KeyboardType.Password) + TextFieldView(paddingTop = 36.dp, label = R.string.main_password_confirm, keyboardType = KeyboardType.Password) } @Preview(showBackground = true) @Composable fun GreetingPreview() { SignupTheme { - Greeting("Android") + TextFieldScreen() } } diff --git a/app/src/main/java/nextstep/signup/ui/ViewComposable.kt b/app/src/main/java/nextstep/signup/ui/ViewComposable.kt new file mode 100644 index 0000000..3ac6926 --- /dev/null +++ b/app/src/main/java/nextstep/signup/ui/ViewComposable.kt @@ -0,0 +1,69 @@ +package nextstep.signup.ui + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +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.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import nextstep.signup.ui.theme.Blue50 + +@Composable +fun TextView( + @StringRes description: Int, +) { + Text( + text = stringResource(id = description), + style = MaterialTheme.typography.titleLarge, + ) +} + +@Composable +fun TextFieldView( + paddingTop: Dp = 0.dp, + @StringRes label: Int, + keyboardType: KeyboardType = KeyboardType.Text, +) { + var input by remember { mutableStateOf("") } + Spacer(modifier = Modifier.padding(top = paddingTop)) + TextField( + value = input, + onValueChange = { input = it }, + modifier = Modifier.fillMaxWidth(), + label = { Text(text = stringResource(id = label)) }, + keyboardOptions = KeyboardOptions(keyboardType = keyboardType), + colors = TextFieldDefaults.colors(focusedIndicatorColor = Blue50), + ) +} + +@Composable +fun ButtonView( + @StringRes description: Int, + paddingTop: Dp, +) { + Spacer(modifier = Modifier.padding(top = paddingTop)) + Button( + onClick = {}, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(containerColor = Blue50), + contentPadding = PaddingValues(vertical = 15.dp), + ) { + Text(text = stringResource(id = description)) + } +} diff --git a/app/src/main/java/nextstep/signup/ui/theme/Color.kt b/app/src/main/java/nextstep/signup/ui/theme/Color.kt index 09dbbec..2e46d2b 100644 --- a/app/src/main/java/nextstep/signup/ui/theme/Color.kt +++ b/app/src/main/java/nextstep/signup/ui/theme/Color.kt @@ -8,4 +8,6 @@ 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) + +val Blue50 = Color(0xFF2196F3) diff --git a/app/src/main/java/nextstep/signup/ui/theme/Theme.kt b/app/src/main/java/nextstep/signup/ui/theme/Theme.kt index 5e311bd..f8497e2 100644 --- a/app/src/main/java/nextstep/signup/ui/theme/Theme.kt +++ b/app/src/main/java/nextstep/signup/ui/theme/Theme.kt @@ -1,6 +1,5 @@ package nextstep.signup.ui.theme -import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme @@ -9,23 +8,20 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.core.view.WindowCompat -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 +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), @@ -34,29 +30,30 @@ private val LightColorScheme = lightColorScheme( onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - */ -) + */ + ) @Composable fun SignupTheme( 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 -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } + 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 - } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } MaterialTheme( colorScheme = colorScheme, typography = Typography, - content = content + content = content, ) } diff --git a/app/src/main/java/nextstep/signup/ui/theme/Type.kt b/app/src/main/java/nextstep/signup/ui/theme/Type.kt index bbd4c54..32c522a 100644 --- a/app/src/main/java/nextstep/signup/ui/theme/Type.kt +++ b/app/src/main/java/nextstep/signup/ui/theme/Type.kt @@ -6,29 +6,22 @@ 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 +val Typography = + Typography( + bodyLarge = + TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), + titleLarge = + TextStyle( + fontFamily = FontFamily.Default, + fontSize = 26.sp, + lineHeight = 20.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.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/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea1ed34..c60f96f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,9 @@ Signup + "Welcome to Compose ๐Ÿš€" + UserName + Email + Password + Password Confirm + Sign Up \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index c1e23bc..1f9df90 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,4 +2,9 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false + alias(libs.plugins.ktlint) apply false +} + +allprojects { + apply(plugin = "org.jlleitschuh.gradle.ktlint") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 360ca20..1cae6f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.5.1" +agp = "8.3.1" kotlin = "1.9.24" coreKtx = "1.13.1" junit = "4.13.2" @@ -8,6 +8,7 @@ espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.8.4" activityCompose = "1.9.1" composeBom = "2024.06.00" +ktlint = "12.1.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -28,3 +29,4 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }