Skip to content

πŸš€ 4단계 - GitHub(인기 μ €μž₯μ†Œ) #73

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

Open
wants to merge 7 commits into
base: fbghgus123
Choose a base branch
from
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@
- [x] μ—λŸ¬κ°€ λ°œμƒν–ˆμ„ λ•Œ μŠ€λ‚΅λ°” λ…ΈμΆœ κ΅¬ν˜„
- [x] μŠ€λ‚΅λ°” μž¬μ‹œλ„ κΈ°λŠ₯ κ΅¬ν˜„

### 4단계
### 4단계
- [x] 데이터 λ°›μ•„μ˜¬ λ•Œ Star μˆ˜λ„ λ°›μ•„ μ˜€λ„λ‘ μˆ˜μ •
- [x] domain νŒ¨ν‚€μ§€ 생성 ν›„ entity 이동 및 50개 νŒλ‹¨ 둜직 μΆ”κ°€
- [x] UiModel 생성 및 적용
- [x] Start 수 λ…ΈμΆœ 및 Hot ν‘œμ‹œ UI κ΅¬ν˜„
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import nextstep.github.data.entity.Repository
import nextstep.github.domain.entity.Repository
import nextstep.github.ui.RepositoryListScreen
import nextstep.github.ui.model.RepositoryListScreenUiState
import org.junit.Test
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/nextstep/github/data/di/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import nextstep.github.data.GithubNetworkModule.provideJsonConverterFactory
import nextstep.github.data.datasource.api.GithubDataSource
import nextstep.github.data.repository.api.GithubRepository
import nextstep.github.data.service.GithubService
import nextstep.github.domain.usecase.GetRepositoryListUseCase
import retrofit2.Converter
import retrofit2.Retrofit

Expand All @@ -17,6 +18,7 @@ interface AppContainer {
val githubService: GithubService
val githubDataSource: GithubDataSource
val githubRepository: GithubRepository
val getRepositoryList: GetRepositoryListUseCase
}

class AppContainerImpl : AppContainer {
Expand All @@ -26,4 +28,5 @@ class AppContainerImpl : AppContainer {
override val githubService = provideGithubService(retrofit)
override val githubDataSource = provideGithubDataSource(githubService)
override val githubRepository = provideGithubRepository(githubDataSource)
override val getRepositoryList: GetRepositoryListUseCase = GetRepositoryListUseCase(githubRepository)
}
6 changes: 0 additions & 6 deletions app/src/main/java/nextstep/github/data/entity/Repository.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ package nextstep.github.data.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import nextstep.github.data.entity.Repository

@Serializable
data class RepositoryResponseModel(
@SerialName("full_name") val fullName: String?,
@SerialName("description") val description: String?,
@SerialName("stargazers_count") val stars: Int?,
)

fun RepositoryResponseModel.toEntity() = Repository(
fullName = this.fullName.orEmpty(),
description = this.description.orEmpty(),
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package nextstep.github.data.repository.api

import nextstep.github.data.entity.Repository
import nextstep.github.data.model.RepositoryResponseModel

interface GithubRepository {

suspend fun getRepos(): List<Repository>
suspend fun getRepos(): List<RepositoryResponseModel>
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package nextstep.github.data.repository.impl

import nextstep.github.data.datasource.api.GithubDataSource
import nextstep.github.data.entity.Repository
import nextstep.github.data.model.toEntity
import nextstep.github.data.model.RepositoryResponseModel
import nextstep.github.data.repository.api.GithubRepository

class GithubRepositoryImpl(
private val dataSource: GithubDataSource,
): GithubRepository {
override suspend fun getRepos(): List<Repository> {
return dataSource.getRepos().map { it.toEntity() }
override suspend fun getRepos(): List<RepositoryResponseModel> {
return dataSource.getRepos()
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/nextstep/github/domain/entity/Repository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.github.domain.entity

data class Repository(
val fullName: String,
val description: String,
val stars: Int,
val isHot: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package nextstep.github.domain.usecase

import nextstep.github.data.repository.api.GithubRepository
import nextstep.github.domain.entity.Repository

class GetRepositoryListUseCase(
private val githubRepository: GithubRepository,
) {

suspend operator fun invoke(): List<Repository> {
return githubRepository.getRepos().map {
Repository(
fullName = it.fullName.orEmpty(),
description = it.description.orEmpty(),
stars = it.stars ?: 0,
isHot = (it.stars ?: 0) > 50,
)
}
}
}
20 changes: 17 additions & 3 deletions app/src/main/java/nextstep/github/ui/RepositoryListScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
Expand All @@ -18,7 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.collections.immutable.toPersistentList
import nextstep.github.data.entity.Repository
import nextstep.github.domain.entity.Repository
import nextstep.github.ui.component.RepositoryListContent
import nextstep.github.ui.component.RepositoryListEmptyContent
import nextstep.github.ui.component.RepositoryListErrorContent
Expand Down Expand Up @@ -131,7 +132,9 @@ private fun RepositoryListScreenPreview() {
repositoryList = List(10) {
Repository(
fullName = "nextstep/github",
description = "Github Repository for NextStep"
description = "Github Repository for NextStep",
stars = 50,
isHot = true,
)
}.toPersistentList()
)
Expand Down Expand Up @@ -163,8 +166,19 @@ private fun RepositoryListEmptyScreenPreview() {
@Composable
private fun RepositoryListErrorScreenPreview() {
GithubTheme {
val snackBarHostState = SnackbarHostState()

LaunchedEffect(Unit) {
snackBarHostState.showSnackbar(
message = "μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜κ°€ λ°œμƒν•˜μ˜€μŠ΅λ‹ˆλ‹€.",
actionLabel = "μž¬μ‹œλ„",
duration = SnackbarDuration.Indefinite,
)
}

RepositoryListScreen(
uiState = RepositoryListScreenUiState.Error
uiState = RepositoryListScreenUiState.Error,
snackBarHostState = snackBarHostState
)
}
}
20 changes: 14 additions & 6 deletions app/src/main/java/nextstep/github/ui/RepositoryListViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import nextstep.github.NextGitHubApplication
import nextstep.github.data.repository.api.GithubRepository
import nextstep.github.domain.usecase.GetRepositoryListUseCase
import nextstep.github.ui.model.RepositoryListScreenSideEffect
import nextstep.github.ui.model.RepositoryListScreenUiState

class RepositoryListViewModel(
private val repository: GithubRepository,
private val getRepositoryListUseCase: GetRepositoryListUseCase,
) : ViewModel() {

private val _uiState = MutableStateFlow<RepositoryListScreenUiState>(RepositoryListScreenUiState.Loading)
private val _uiState =
MutableStateFlow<RepositoryListScreenUiState>(RepositoryListScreenUiState.Loading)
val uiState: StateFlow<RepositoryListScreenUiState> = _uiState.asStateFlow()

private val _sideEffect: Channel<RepositoryListScreenSideEffect> = Channel()
Expand All @@ -36,7 +39,7 @@ class RepositoryListViewModel(

fun loadRepositoryList() {
viewModelScope.launch(ceh) {
val repositoryList = repository.getRepos().toPersistentList()
val repositoryList = getRepositoryListUseCase().toPersistentList()
_uiState.value = if (repositoryList.isEmpty()) {
RepositoryListScreenUiState.Empty
} else {
Expand All @@ -48,10 +51,15 @@ class RepositoryListViewModel(
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val githubRepository = (this[APPLICATION_KEY] as NextGitHubApplication)
.appContainer
.githubRepository
RepositoryListViewModel(githubRepository)
val appContainer = (this[APPLICATION_KEY] as NextGitHubApplication).appContainer

val githubRepository = appContainer.githubRepository
val getRepositoryListUseCase = appContainer.getRepositoryList

RepositoryListViewModel(
repository = githubRepository,
getRepositoryListUseCase = getRepositoryListUseCase,
)
}
}
}
Expand Down
41 changes: 39 additions & 2 deletions app/src/main/java/nextstep/github/ui/component/RepositoryItem.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package nextstep.github.ui.component

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import nextstep.github.data.entity.Repository
import nextstep.github.domain.entity.Repository
import nextstep.github.ui.theme.GithubTheme

@Composable
Expand All @@ -22,6 +25,23 @@ fun RepositoryItem(
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp)
) {
Box(
modifier = Modifier.fillMaxWidth()
) {
if (repository.isHot) {
Text(
text = "HOT",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.TopStart)
)
}
Text(
text = "β˜… ${repository.stars}",
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.align(Alignment.TopEnd)
)
}
Text(
text = repository.fullName,
style = MaterialTheme.typography.titleLarge,
Expand All @@ -35,14 +55,31 @@ fun RepositoryItem(
}
}

@Preview
@Composable
private fun RepositoryHotItemPreview() {
GithubTheme {
RepositoryItem(
repository = Repository(
fullName = "nextstep/github",
description = "Github Repository for NextStep",
stars = 50,
isHot = true,
)
)
}
}

@Preview
@Composable
private fun RepositoryItemPreview() {
GithubTheme {
RepositoryItem(
repository = Repository(
fullName = "nextstep/github",
description = "Github Repository for NextStep"
description = "Github Repository for NextStep",
stars = 0,
isHot = false,
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import nextstep.github.data.entity.Repository
import nextstep.github.domain.entity.Repository

@Composable
fun RepositoryListContent(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nextstep.github.ui.model

import kotlinx.collections.immutable.PersistentList
import nextstep.github.data.entity.Repository
import nextstep.github.domain.entity.Repository

sealed interface RepositoryListScreenUiState{

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/nextstep/github/ui/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ val PurpleGrey40 = Color(0xFF625B71)
val Pink40 = Color(0xFF7D5260)

object GithubLightColorToken {
val Primary = Color(0xFF6750A4)
val Surface = Color(0xFF2C292B)
val OutlineVariant = Color(0xFFCAC4D0)
}

object GithubDarkColorToken {
val Primary = Color(0xFF6750A4)
val Surface = Color(0xFFAfAAB4)
val OutlineVariant = Color(0xFFAFAAB4)
}
Expand Down
16 changes: 8 additions & 8 deletions app/src/main/java/nextstep/github/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ 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 = darkColorScheme(
primary = GithubLightColorToken.Primary,
secondary = PurpleGrey40,
tertiary = Pink40,
surface = GithubLightColorToken.Surface,
outlineVariant = GithubLightColorToken.OutlineVariant,
)

private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40,
private val DarkColorScheme = lightColorScheme(
primary = GithubDarkColorToken.Primary,
secondary = PurpleGrey80,
tertiary = Pink80,
surface = GithubDarkColorToken.Surface,
outlineVariant = GithubDarkColorToken.OutlineVariant,
)
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/nextstep/github/ui/theme/Type.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ val Typography = Typography(
fontSize = 24.sp,
lineHeight = 32.sp,
letterSpacing = 0.sp
)
),
labelLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),

/*
labelSmall = TextStyle(
Expand Down