Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cada0da
chore: 하로 PR 마이그레이션
noeyhoj Mar 6, 2026
2d45bab
docs: README.md 작성
CommitTheKermit Mar 11, 2026
1510d40
refactor: 기존 코드 포맷
CommitTheKermit Mar 11, 2026
50810a0
feat: 태스크 생성 다이얼로그 추가
CommitTheKermit Mar 11, 2026
f8ecd0b
feat: 타이틀 로우 구현
CommitTheKermit Mar 11, 2026
65097c5
feat: 공통 텍스트 인풋 칼럼 구현
CommitTheKermit Mar 11, 2026
4d2c70f
feat: 공통 버튼 칼럼 구현
CommitTheKermit Mar 11, 2026
aeff9a2
feat: 취소, 생성 로우 구현
CommitTheKermit Mar 11, 2026
ff98fa0
docs: README.md 기능 체크
CommitTheKermit Mar 11, 2026
f5f216d
feat: 상태 버튼 상호작용 구현
CommitTheKermit Mar 11, 2026
b42662e
feat: 담당자 버튼 상호작용 구현
CommitTheKermit Mar 11, 2026
4b7ed1f
fix: 타이틀 로우 오타 수정
CommitTheKermit Mar 11, 2026
9a763fb
feat: 에러 검증 로직 구현
CommitTheKermit Mar 11, 2026
385df9e
docs: README.md 기능 체크
CommitTheKermit Mar 11, 2026
c806a00
style: 매직 넘버 상수화
CommitTheKermit Mar 11, 2026
cb4391d
refactor: 버튼의 선택 상태별 modifier 추출
CommitTheKermit Mar 11, 2026
48265d1
refactor: modifier 적용
CommitTheKermit Mar 11, 2026
25bc4b4
refactor: CoachButtonColumn, StatusButtonColumn 공통 코드로 리팩토링
CommitTheKermit Mar 11, 2026
ad4a2a2
docs: README.md 테스트 시나리오 작성
CommitTheKermit Mar 11, 2026
395d3df
fix: 태그 검증 수정
CommitTheKermit Mar 11, 2026
eafb365
test: UI 테스트 구현
CommitTheKermit Mar 11, 2026
81f5bba
docs: README.md 테스트 체크
CommitTheKermit Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 61 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
This is a Kotlin Multiplatform project targeting Android, Desktop (JVM).
## UI 목록

* [/composeApp](./composeApp/src) is for code that will be shared across your Compose Multiplatform applications.
It contains several subfolders:
- [commonMain](./composeApp/src/commonMain/kotlin) is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name.
For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app,
the [iosMain](./composeApp/src/iosMain/kotlin) folder would be the right place for such calls.
Similarly, if you want to edit the Desktop (JVM) specific part, the [jvmMain](./composeApp/src/jvmMain/kotlin)
folder is the appropriate location.
- [x] 태스크 생성 다이얼로그
- [x] 타이틀 로우
- 새 테스크 생성
- 닫기 아이콘
- [x] 구분선

### Build and Run Android Application
- [x] 공통 텍스트 인풋 칼럼
- 제목 칼럼
- 헤더
- 제목 텍스트필드
- 설명 칼럼
- 헤더
- 설명 텍스트필드
- 태그 칼럼
- 헤더
- 설명 텍스트필드
- 힌트 텍스트
- [x] 공통 버튼 칼럼
- 상태 칼럼
- 헤더
- 버튼 로우
- To Do
- In Progress
- Done
- 담당자 칼럼
- 헤더
- 버튼 로우
- 버튼 객체(박스, 이미지, 텍스트)
- 다이노
- 페임스
- [x] 구분선
- [x] 취소, 생성 로우
- 버튼 리스트
- 취소
- 생성

To build and run the development version of the Android app, use the run configuration from the run widget
in your IDE’s toolbar or build it directly from the terminal:
- on macOS/Linux
```shell
./gradlew :composeApp:assembleDebug
```
- on Windows
```shell
.\gradlew.bat :composeApp:assembleDebug
```
## 기능 목록

### Build and Run Desktop (JVM) Application
- [x] 제목 검증
- 공백 검사
- 에러 표시(보더, 에러 힌트 텍스트)
- [x] 태그 검증
- 5글자 제한
- 5개 제한
- 에러 표시(보더, 에러 힌트 텍스트)
- [x] 상태 버튼 상호작용
- 기본값 맨 앞 버튼
- 선택 가능하게
- 선택 효과
- [x] 담당자 버튼 상호작용
- 기본값 맨 앞 버튼
- 선택 가능하게
- 선택 효과
- [x] 생성 버튼 상호작용
- 생성
- 눌렀을 때 유효성 검사
- 비활성화 효과

To build and run the development version of the desktop app, use the run configuration from the run widget
in your IDE’s toolbar or run it directly from the terminal:
- on macOS/Linux
```shell
./gradlew :composeApp:run
```
- on Windows
```shell
.\gradlew.bat :composeApp:run
```
## UI 테스트

---
- [x] 상태 버튼을 클릭 했을 때 다른 상태 버튼은 선택되지 않아야 한다.
- [x] 담당자 버튼을 클릭 했을 때 다른 상태 버튼은 선택되지 않아야 한다.
- [x] 제목 검증 혹은 태그 검증에 실패시 생성 버튼 비활성화
- [x] 텍스트 필드에 입력한 내용이 입력한대로 출력되어야 한다.
- [x] 제목 검증 혹은 태그 검증에 실패시 생성 버튼을 누르면 제목과 태그에서 에러 표시가 출력되야 한다.

Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)
1 change: 1 addition & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ kotlin {
implementation(libs.compose.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.material.icons.extended)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 2 additions & 37 deletions composeApp/src/commonMain/kotlin/woowacourse/kanban/board/App.kt
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
package woowacourse.kanban.board

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
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.tooling.preview.Preview
import kanbanboard.composeapp.generated.resources.Res
import kanbanboard.composeapp.generated.resources.compose_multiplatform
import org.jetbrains.compose.resources.painterResource
import woowacourse.kanban.create.component.TaskCreateDialog

@Composable
@Preview
fun App() {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.primaryContainer)
.safeContentPadding()
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Button(onClick = { showContent = !showContent }) {
Text("Click me!")
}
AnimatedVisibility(showContent) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
}
}
}
TaskCreateDialog()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package woowacourse.kanban.board.component

import androidx.compose.foundation.layout.Box
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.text.style.TextOverflow
import androidx.compose.ui.unit.sp
import woowacourse.kanban.board.constant.CONTENT_COLOR

@Composable
fun Content(
content: String,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier,
) {
Text(
content,
overflow = TextOverflow.Ellipsis,
maxLines = 2,
fontSize = 14.sp,
color = Color(CONTENT_COLOR),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package woowacourse.kanban.board.component

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import woowacourse.kanban.board.constant.BORDER_COLOR
import woowacourse.kanban.board.constant.DEFAULT_CONTENT
import woowacourse.kanban.board.constant.DEFAULT_NAME
import woowacourse.kanban.board.constant.DEFAULT_TITLE
import woowacourse.kanban.board.constant.MAX_CONTENT
import woowacourse.kanban.board.constant.MAX_NAME
import woowacourse.kanban.board.constant.MAX_TITLE
import woowacourse.kanban.board.model.BoardData
import woowacourse.kanban.board.model.Nickname
import woowacourse.kanban.board.model.Tags
import woowacourse.kanban.board.model.Title

@Composable
fun KanbanBoardTemplate(board: BoardData) {
Box(
modifier = Modifier
.border(
width = 1.dp,
color = Color(BORDER_COLOR),
shape = RoundedCornerShape(15.dp),
)
.width(270.dp)
.padding(12.dp),
) {
Column {
// 제목
Title(title = board.title, modifier = Modifier.padding(vertical = 8.dp).testTag("제목"))

// 중간 내용
if (board.content.isNotBlank()) {
Content(content = board.content, modifier = Modifier.padding(vertical = 4.dp).testTag("중간내용"))
}

// 태그
if (board.tags.tags.isNotEmpty()) {
TagsComponent(tags = board.tags, modifier = Modifier.padding(vertical = 8.dp).testTag("테그목록"))
}

// 구분선
HorizontalDivider(thickness = 2.dp)

// 작성자
Profile(nickname = board.nickname, modifier = Modifier.padding(vertical = 8.dp).testTag("프로필"))
}
}
}

class BoardPreviewParameterProvider : PreviewParameterProvider<BoardData> {
override val values = sequenceOf(
BoardData(
title = Title(DEFAULT_TITLE),
content = DEFAULT_CONTENT,
tags = Tags(listOf("컴포넌트", "성능")),
nickname = Nickname(DEFAULT_NAME),
),
BoardData(
title = Title(DEFAULT_TITLE),
tags = Tags(listOf("컴포넌트", "성능")),
nickname = Nickname(DEFAULT_NAME),
),
BoardData(
title = Title(DEFAULT_TITLE),
content = DEFAULT_CONTENT,
tags = Tags(),
nickname = Nickname(DEFAULT_NAME),
),
BoardData(
title = Title(DEFAULT_TITLE),
tags = Tags(),
nickname = Nickname(DEFAULT_NAME),
),
BoardData(
title = Title(MAX_TITLE),
content = MAX_CONTENT,
tags = Tags(listOf("너무너무", "긴 태그", "최대로", "5자까지", "5개제한임")),
nickname = Nickname(MAX_NAME),
),
)
}

@Preview(showBackground = true)
@Composable
private fun BoardScreenView(@PreviewParameter(BoardPreviewParameterProvider::class) board: BoardData) {
KanbanBoardTemplate(board)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package woowacourse.kanban.board.component

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Icon
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import woowacourse.kanban.board.constant.PROFILE_BG_COLOR
import woowacourse.kanban.board.constant.PROFILE_COLOR
import woowacourse.kanban.board.model.Nickname

@Composable
fun Profile(
nickname: Nickname,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Default.Person,
contentDescription = null,
tint = Color(PROFILE_COLOR),
modifier = Modifier.size(25.dp)
.clip(CircleShape)
.border(width = 2.dp, color = Color(PROFILE_BG_COLOR))
.background(color = Color(PROFILE_BG_COLOR)),
)
Text(
nickname.nickname,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package woowacourse.kanban.board.component

import androidx.compose.foundation.layout.Box
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.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun Tag(
content: String,
modifier: Modifier = Modifier,
) {
val maxLength = 5

Box(
modifier = modifier,
) {
Text(content.take(maxLength), modifier = Modifier.padding(6.dp), fontSize = 10.sp)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package woowacourse.kanban.board.component

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import woowacourse.kanban.board.constant.TAG_COLOR
import woowacourse.kanban.board.model.Tags

@Composable
fun TagsComponent(
tags: Tags,
modifier: Modifier = Modifier,
) {
FlowRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
for (tag in tags.tags) {
Tag(
tag,
modifier = Modifier
.background(
color = Color(TAG_COLOR),
shape = RoundedCornerShape(45.dp),
),
)
}
}
}
Loading