-
Notifications
You must be signed in to change notification settings - Fork 25
๐ 4๋จ๊ณ - GitHub(์ธ๊ธฐ ์ ์ฅ์) #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: hyemdooly
Are you sure you want to change the base?
Changes from all commits
2f94e07
5b28010
5568298
7e0e7d6
9aaa7df
ba836d0
28b32e3
649f531
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package nextstep.github.domain.model | ||
|
||
import nextstep.github.data.repository.model.RepositoryEntity | ||
import nextstep.github.util.orZero | ||
|
||
data class Repository( | ||
val id: Long, | ||
val fullName: String, | ||
val description: String, | ||
val stars: Int, | ||
) { | ||
val isHot: Boolean = stars >= HOT_STANDARD | ||
|
||
companion object { | ||
private const val HOT_STANDARD = 50 | ||
} | ||
} | ||
|
||
fun RepositoryEntity.toDomain(): Repository { | ||
return Repository( | ||
id = id, | ||
fullName = fullName.orEmpty(), | ||
description = description.orEmpty(), | ||
stars = stars.orZero() | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package nextstep.github.domain.usecase | ||
|
||
import nextstep.github.data.repository.GithubRepoRepository | ||
import nextstep.github.domain.model.Repository | ||
import nextstep.github.domain.model.toDomain | ||
|
||
fun interface GetRepositoriesUseCase { | ||
suspend operator fun invoke(organization: String): List<Repository> | ||
} | ||
|
||
class DefaultGetRepositoriesUseCase( | ||
private val githubRepoRepository: GithubRepoRepository, | ||
) : GetRepositoriesUseCase { | ||
override suspend operator fun invoke(organization: String): List<Repository> { | ||
return githubRepoRepository.getRepositories(organization).map { it.toDomain() } | ||
} | ||
} | ||
Comment on lines
+7
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UseCase์ ์ธํฐํ์ด์ค๋ผ.... ์๋ ๋ถํฐ ์๊ฐํด๋ดค๋ ๊ฒ์ด๊ธด ํ๋ฐ ์ ๋ง ๋ณด์ผ๋ฌํ๋ ์ดํธ๊ฐ ๋ง์ ์ฝ๋๋ผ ์ ๋ ์ํ๋ ๊ฒ ๋ซ๊ฒ ๋ค ๋ผ๊ณ ๊ฒฐ๋ก ์ง์๋๋ฐ์, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๊ทธ๋ฌ๊ณ ๋ณด๋ .toDomain์ ๋ณด๋, |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,43 +6,59 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP | |
import androidx.lifecycle.viewModelScope | ||
import androidx.lifecycle.viewmodel.initializer | ||
import androidx.lifecycle.viewmodel.viewModelFactory | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.asSharedFlow | ||
import kotlinx.coroutines.flow.catch | ||
import kotlinx.coroutines.flow.flow | ||
import kotlinx.coroutines.flow.stateIn | ||
import nextstep.github.GithubApplication | ||
import nextstep.github.data.repository.GithubRepoRepository | ||
import nextstep.github.R | ||
import nextstep.github.domain.usecase.GetRepositoriesUseCase | ||
import nextstep.github.ui.model.RepositoryListEvent | ||
import nextstep.github.ui.model.RepositoryListUiState | ||
|
||
class RepositoryListViewModel( | ||
private val githubRepoRepository: GithubRepoRepository | ||
private val getRepositoryUseCase: GetRepositoriesUseCase | ||
) : ViewModel() { | ||
val uiState = flow { | ||
emit(RepositoryListUiState.Loading(error = false)) | ||
|
||
val repositories = githubRepoRepository.getRepositories(ORGANIZATION) | ||
if (repositories.isEmpty()) { | ||
emit(RepositoryListUiState.Empty) | ||
} else { | ||
emit(RepositoryListUiState.Success(items = repositories)) | ||
} | ||
}.catch { | ||
emit(RepositoryListUiState.Loading(error = true)) | ||
}.stateIn( | ||
val uiState = getRepositoriesFlow().stateIn( | ||
scope = viewModelScope, | ||
started = SharingStarted.WhileSubscribed(5_000), | ||
initialValue = RepositoryListUiState.Loading(error = false) | ||
initialValue = RepositoryListUiState.Loading | ||
) | ||
|
||
private val _event = MutableSharedFlow<RepositoryListEvent>() | ||
val event = _event.asSharedFlow() | ||
|
||
private fun getRepositoriesFlow(): Flow<RepositoryListUiState> { | ||
return flow { | ||
val repositories = getRepositoryUseCase(ORGANIZATION) | ||
Comment on lines
+35
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๋์ ์ผ๋ก ๋ณ๊ฒฝํด์ผํ ๋ ๋ณ๊ฒฝ์ ์ด ์ปค์ง ๊ฑฐ๋ผ ์์๋ผ์. |
||
if (repositories.isEmpty()) { | ||
emit(RepositoryListUiState.Empty) | ||
} else { | ||
emit(RepositoryListUiState.Success(items = repositories)) | ||
} | ||
}.catch { | ||
emit(RepositoryListUiState.Loading) | ||
_event.tryEmit( | ||
RepositoryListEvent.ShowSnackBar( | ||
msgRes = R.string.repository_list_fetch_error, | ||
actionLabelRes = R.string.retry | ||
) | ||
Comment on lines
+45
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Viewmodel์ ์๋๋ก์ด๋ ํ๋ ์์ํฌ์ ์กฐ๊ธ์ ์์ ๋ก์์ ์ ๋ํ
์คํธ๊ฐ ๊ฐ๋ฅํ๋ฐ์, |
||
) | ||
} | ||
} | ||
|
||
companion object { | ||
private const val ORGANIZATION = "next-step" | ||
|
||
val Factory: ViewModelProvider.Factory = viewModelFactory { | ||
initializer { | ||
val githubRepository = (this[APPLICATION_KEY] as GithubApplication) | ||
val getRepositoriesUseCase = (this[APPLICATION_KEY] as GithubApplication) | ||
.appContainer | ||
.githubRepoRepository | ||
RepositoryListViewModel(githubRepository) | ||
.getRepositoriesUseCase | ||
RepositoryListViewModel(getRepositoriesUseCase) | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,38 +2,83 @@ package nextstep.github.ui.component | |
|
||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.filled.Star | ||
import androidx.compose.material3.HorizontalDivider | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.res.stringResource | ||
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 nextstep.github.data.repository.model.RepositoryEntity | ||
import nextstep.github.R | ||
import nextstep.github.domain.model.Repository | ||
import nextstep.github.ui.theme.GithubTheme | ||
|
||
@Composable | ||
fun RepositoryItem( | ||
repository: RepositoryEntity, | ||
repository: Repository, | ||
modifier: Modifier = Modifier | ||
) { | ||
RepositoryItem( | ||
fullName = repository.fullName, | ||
description = repository.description, | ||
stars = repository.stars, | ||
isHot = repository.isHot, | ||
modifier = modifier | ||
) | ||
} | ||
|
||
@Composable | ||
fun RepositoryItem( | ||
fullName: String, | ||
description: String, | ||
stars: Int, | ||
isHot: Boolean, | ||
Comment on lines
30
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ค~ ์ ๋ ์ปดํฌ์ฆ ์ปดํฌ๋ํธ ๊ตฌ์ฑํ ๋ ์ด๋ฐ ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑํ๋๋ฐ, ํ์ ๋๋ฆฌ๊ธฐ๋์ ์ ์ด๋ ๊ฒ ๊ตฌ์ฑํด์ฃผ์ จ๋ค์ ์ํ ์ปดํฌ๋ํธ๋ ์ต๋ํ ์์๊ฐ๋ค์ ํ์ฉํ๊ณ , ๋๋ฉ์ธ์ด ๋ค์ด๊ฐ๊ฒ ํธ์์ฑ์ ์ ๊ณตํ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๊ตฌ์ฑํ๋ ํํ๋ฅผ ์ ์ ํธํด์. |
||
modifier: Modifier = Modifier | ||
) { | ||
Column( | ||
modifier = modifier | ||
.background(color = MaterialTheme.colorScheme.surface) | ||
) { | ||
Column( | ||
modifier = Modifier.padding(16.dp) | ||
modifier = Modifier | ||
.padding(16.dp) | ||
.fillMaxWidth() | ||
) { | ||
Row( | ||
modifier = Modifier.fillMaxWidth(), | ||
) { | ||
if (isHot) { | ||
Text( | ||
text = stringResource(R.string.repository_item_hot), | ||
style = MaterialTheme.typography.labelLarge, | ||
color = MaterialTheme.colorScheme.primary | ||
) | ||
} | ||
Spacer(modifier = Modifier.weight(1f)) | ||
Comment on lines
+69
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. VerticalDivider, HorizontalDivider ์ด๋ฐ๊ฑด ๋ง๋ค์ด์ฃผ๋ฉด์ VerticalSpacer ์ด๋ฐ๊ฑด ์๋ง๋ค์ด์ฃผ๋๋ผ๊ตฌ์ ex) ~~~
VerticalSpacer(height = 8.dp) ์ ๋ค ์ฐ๊ณ ๋ณด๋ ์ด๊ฑด weight ๊ฑธ๋ ค์๋ค์ |
||
StarCount( | ||
count = stars, | ||
) | ||
} | ||
Text( | ||
text = repository.fullName.orEmpty(), | ||
text = fullName, | ||
style = MaterialTheme.typography.titleLarge, | ||
color = Color.Black | ||
) | ||
Text( | ||
text = repository.description.orEmpty(), | ||
text = description, | ||
style = MaterialTheme.typography.bodyMedium, | ||
color = Color.Black | ||
) | ||
|
@@ -42,16 +87,45 @@ fun RepositoryItem( | |
} | ||
} | ||
|
||
@Composable | ||
fun StarCount( | ||
count: Int, | ||
modifier: Modifier = Modifier | ||
) { | ||
Row( | ||
modifier = modifier, | ||
verticalAlignment = Alignment.CenterVertically, | ||
) { | ||
Icon( | ||
modifier = Modifier.size(18.dp), | ||
imageVector = Icons.Filled.Star, | ||
contentDescription = null | ||
) | ||
Text( | ||
text = count.toString(), | ||
style = MaterialTheme.typography.labelLarge, | ||
) | ||
} | ||
} | ||
|
||
private class RepositoryItemPreviewParameterProvider : PreviewParameterProvider<Boolean> { | ||
override val values = sequenceOf( | ||
true, | ||
false | ||
) | ||
} | ||
|
||
@Preview | ||
@Composable | ||
private fun RepositoryItemPreview() { | ||
private fun RepositoryItemPreview( | ||
@PreviewParameter(RepositoryItemPreviewParameterProvider::class) value: Boolean | ||
) { | ||
GithubTheme { | ||
RepositoryItem( | ||
RepositoryEntity( | ||
id = 0, | ||
fullName = "next-step/nextstep-docs", | ||
description = "nextstep ๋งค๋ด์ผ ๋ฐ ๋ฌธ์๋ฅผ ๊ด๋ฆฌํ๋ ์ ์ฅ์" | ||
), | ||
fullName = "next-step/nextstep-docs", | ||
description = "nextstep ๋งค๋ด์ผ ๋ฐ ๋ฌธ์๋ฅผ ๊ด๋ฆฌํ๋ ์ ์ฅ์", | ||
stars = 100, | ||
isHot = value, | ||
modifier = Modifier.fillMaxWidth() | ||
) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package nextstep.github.ui.model | ||
|
||
import androidx.annotation.StringRes | ||
|
||
sealed interface RepositoryListEvent { | ||
data class ShowSnackBar(@StringRes val msgRes: Int, @StringRes val actionLabelRes: Int): RepositoryListEvent | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
package nextstep.github.ui.model | ||
|
||
import nextstep.github.data.repository.model.RepositoryEntity | ||
import nextstep.github.domain.model.Repository | ||
|
||
sealed interface RepositoryListUiState { | ||
data class Loading(val error: Boolean): RepositoryListUiState | ||
data object Loading : RepositoryListUiState | ||
data object Empty: RepositoryListUiState | ||
data class Success(val items: List<RepositoryEntity>): RepositoryListUiState | ||
data class Success(val items: List<Repository>): RepositoryListUiState | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ ๋ ์ฐธ ๊ณ ๋ฏผ์ด ๋ง์๋๋ฐ์,
์ ๋ ๋๊ฐ์ ์๊ฐ์ ํ๋ฆ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ์ด์.
๋ทฐ์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด๋ผ๋ ๊ฒ์ ๋ง์ผ๋, HOT์ด๋ผ๋ ๋ฑ์ง๋ฅผ ๋ถ์ฌ์ฃผ๋ ๊ธฐํ์ ๊ฒฐ๊ตญ ๋๋ฉ์ธ์ด๋ผ๊ณ ์๊ฐํด์.
๋ทฐ์ ์์๋ HOT์ ์ด๋์ ์ด๋ป๊ฒ ๋ณด์ฌ์ค ๊ฒ์ด๋์ ๋ก์ง์ด๋ ์๊ฐ์ด ๋ค์ด ์ ๋ ๋๋ฉ์ธ์ isHot์ ๋๋๊ฒ ์ ์ ํ๋ค ์๊ฐํ์ด์.
๋ฐฑ์๋์์ HOT์ ๋ทฐ์ ์์๋ก ๋ณผ ๊ฒ์ด๋? ๋ผ๊ณ ํ์๋, ์ ๋ ์๋๋ผ๊ณ ์๊ฐํ์ด์.
์คํ๋ ค HOT์ธ ์กฐ๊ฑด์ ๋ฐฑ์๋์ ๋ฐ์ ๋ฃ๋๊ฒ, ํซํฝ์ค๋ ๋ค๋ฅธ ๋ฐฐํฌ๋ก ํ์ฌ๊ธ ๋ ์ ๋์ ์ผ๋ก ๋์ฒํ ์ ์๋ ๊ฐ์ด๋ผ ์๊ฐ์ด ๋ค์ด์์.
๋ฐฑ์๋๊ฐ ์ฐ๊ฒฐ๋๋ค๋ฉด, isHot๋ ์๋ต์ผ๋ก ๋ณด๋ด์ฃผ์ง ์์๊น ํ๋ ๊ฐ์ธ์ ์ธ ์๊ฐ์ด ์์์ด์ :)