Skip to content

Commit f2aeb7b

Browse files
authored
Step3 : Github(UI) (#99)
1 parent 47f50aa commit f2aeb7b

File tree

20 files changed

+326
-32
lines changed

20 files changed

+326
-32
lines changed

app/build.gradle.kts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ plugins {
44
id("com.android.application")
55
kotlin("android")
66
kotlin("kapt")
7+
id("com.google.dagger.hilt.android")
78
id("org.jlleitschuh.gradle.ktlint") version "11.5.0"
89
}
910

1011
android {
1112
compileSdk = Version.compileSdk
1213

1314
defaultConfig {
14-
applicationId = "edu.nextstep.camp.calculator"
15+
applicationId = "edu.nextstep.camp.github"
1516
minSdk = Version.minSdk
1617
targetSdk = Version.targetSdk
1718
versionCode = 1
@@ -39,18 +40,51 @@ android {
3940
buildFeatures {
4041
viewBinding = true
4142
dataBinding = true
43+
compose = true
44+
}
45+
46+
composeOptions {
47+
kotlinCompilerExtensionVersion = Version.composeCompiler
4248
}
4349
}
4450

4551
dependencies {
52+
val composeBom = platform("androidx.compose:compose-bom:2023.01.00")
53+
implementation(composeBom)
54+
androidTestImplementation(composeBom)
55+
56+
// Choose one of the following:
57+
// Material Design 3
58+
implementation("androidx.compose.material3:material3")
59+
// such as input and measurement/layout
60+
implementation("androidx.compose.ui:ui")
61+
62+
// Optional - Integration with activities
63+
implementation("androidx.activity:activity-compose:1.7.2")
64+
// Optional - Integration with ViewModels
65+
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
66+
// Optional - Integration with LiveData
67+
implementation("androidx.compose.runtime:runtime-livedata")
68+
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
69+
4670
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Version.kotlin}")
47-
implementation("androidx.core:core-ktx:1.9.0")
48-
implementation("androidx.appcompat:appcompat:1.6.0")
49-
implementation("com.google.android.material:material:1.7.0")
71+
implementation("androidx.core:core-ktx:1.10.1")
72+
implementation("androidx.appcompat:appcompat:1.6.1")
73+
implementation("com.google.android.material:material:1.9.0")
5074
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
51-
implementation("androidx.fragment:fragment-ktx:1.5.5")
75+
implementation("androidx.fragment:fragment-ktx:1.6.1")
76+
77+
implementation("com.google.dagger:hilt-android:2.47")
78+
kapt("com.google.dagger:hilt-android-compiler:2.47")
79+
80+
implementation("org.orbit-mvi:orbit-core:${Version.orbit}")
81+
implementation("org.orbit-mvi:orbit-viewmodel:${Version.orbit}")
82+
implementation("org.orbit-mvi:orbit-compose:${Version.orbit}")
83+
5284
implementation(project(":core:data"))
85+
implementation(project(":core:domain"))
5386

87+
testImplementation("org.orbit-mvi:orbit-test:${Version.orbit}")
5488
testImplementation("junit:junit:4.13.2")
5589
testImplementation("com.google.truth:truth:1.1.4")
5690
androidTestImplementation("androidx.test.ext:junit:1.1.5")

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="camp.nextstep.edu.github">
44

5+
<uses-permission android:name="android.permission.INTERNET" />
6+
57
<application
68
android:allowBackup="true"
79
android:icon="@mipmap/ic_launcher"
810
android:label="@string/app_name"
11+
android:name=".GithubApplication"
912
android:roundIcon="@mipmap/ic_launcher_round"
1013
android:supportsRtl="true"
1114
android:theme="@style/Theme.Androidgithub">
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package camp.nextstep.edu.github
2+
3+
import android.app.Application
4+
import dagger.hilt.android.HiltAndroidApp
5+
6+
@HiltAndroidApp
7+
class GithubApplication : Application()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package camp.nextstep.edu.github
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import camp.nextstep.edu.github.domain.network.GithubDataSource
6+
import camp.nextstep.edu.github.ui.UiStatus
7+
import camp.nextstep.edu.github.ui.main.GithubMainSideEffect
8+
import camp.nextstep.edu.github.ui.main.GithubMainState
9+
import dagger.hilt.android.lifecycle.HiltViewModel
10+
import kotlinx.coroutines.launch
11+
import org.orbitmvi.orbit.ContainerHost
12+
import org.orbitmvi.orbit.syntax.simple.intent
13+
import org.orbitmvi.orbit.syntax.simple.reduce
14+
import org.orbitmvi.orbit.viewmodel.container
15+
import javax.inject.Inject
16+
17+
@HiltViewModel
18+
class GithubRepositoriesViewModel @Inject constructor(
19+
private val dataSource: GithubDataSource
20+
) : ContainerHost<GithubMainState, GithubMainSideEffect>, ViewModel() {
21+
override val container = container<GithubMainState, GithubMainSideEffect>(GithubMainState())
22+
23+
init {
24+
fetchRepositories()
25+
}
26+
27+
private fun fetchRepositories() {
28+
intent {
29+
reduce { state.copy(status = UiStatus.Loading) }
30+
viewModelScope.launch {
31+
runCatching {
32+
val repositories = dataSource.fetchRepositories()
33+
reduce {
34+
state.copy(
35+
status = UiStatus.Success,
36+
repositories = repositories
37+
)
38+
}
39+
}.getOrElse {
40+
reduce { state.copy(status = UiStatus.Failed("Error : ${it.message}")) }
41+
}
42+
}
43+
}
44+
}
45+
}
Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
11
package camp.nextstep.edu.github
22

33
import android.os.Bundle
4+
import androidx.activity.compose.setContent
5+
import androidx.activity.viewModels
46
import androidx.appcompat.app.AppCompatActivity
7+
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.ui.Modifier
11+
import androidx.navigation.NavGraphBuilder
12+
import androidx.navigation.NavHostController
13+
import androidx.navigation.compose.NavHost
14+
import androidx.navigation.compose.composable
15+
import androidx.navigation.compose.rememberNavController
16+
import camp.nextstep.edu.github.theme.GitHubMainTheme
17+
import camp.nextstep.edu.github.ui.main.component.GithubRepositoryPage
18+
import dagger.hilt.android.AndroidEntryPoint
19+
import org.orbitmvi.orbit.compose.collectAsState
20+
import org.orbitmvi.orbit.compose.collectSideEffect
521

22+
@AndroidEntryPoint
623
class MainActivity : AppCompatActivity() {
24+
private val viewModel: GithubRepositoriesViewModel by viewModels()
725
override fun onCreate(savedInstanceState: Bundle?) {
826
super.onCreate(savedInstanceState)
9-
setContentView(R.layout.activity_main)
27+
28+
setContent {
29+
GitHubMainTheme {
30+
Box(modifier = Modifier.fillMaxSize()) {
31+
val navController = rememberNavController()
32+
NavHost(navController = navController, startDestination = "Main") {
33+
addMain(navController)
34+
}
35+
}
36+
}
37+
}
38+
}
39+
40+
private fun NavGraphBuilder.addMain(navController: NavHostController) {
41+
composable(route = "Main") {
42+
val state by viewModel.collectAsState()
43+
viewModel.collectSideEffect {
44+
navController.navigate(route = "Main")
45+
}
46+
GithubRepositoryPage(state)
47+
}
1048
}
1149
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package camp.nextstep.edu.github.theme
2+
3+
import androidx.compose.material3.MaterialTheme
4+
import androidx.compose.runtime.Composable
5+
6+
@Composable
7+
fun GitHubMainTheme(content: @Composable () -> Unit) {
8+
MaterialTheme(
9+
content = content
10+
)
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package camp.nextstep.edu.github.ui
2+
3+
sealed class UiStatus {
4+
5+
object Loading : UiStatus()
6+
object Success : UiStatus()
7+
data class Failed(val message: String) : UiStatus()
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package camp.nextstep.edu.github.ui.main
2+
3+
sealed class GithubMainSideEffect {
4+
object Completed : GithubMainSideEffect()
5+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package camp.nextstep.edu.github.ui.main
2+
3+
import camp.nextstep.edu.github.domain.GithubRepository
4+
import camp.nextstep.edu.github.ui.UiStatus
5+
6+
data class GithubMainState(
7+
val status: UiStatus? = null,
8+
val repositories: List<GithubRepository> = emptyList()
9+
)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package camp.nextstep.edu.github.ui.main.component
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.PaddingValues
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.lazy.LazyColumn
10+
import androidx.compose.foundation.lazy.items
11+
import androidx.compose.material3.CircularProgressIndicator
12+
import androidx.compose.material3.Divider
13+
import androidx.compose.material3.ExperimentalMaterial3Api
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.Scaffold
16+
import androidx.compose.material3.Text
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.unit.dp
22+
import camp.nextstep.edu.github.domain.GithubRepository
23+
import camp.nextstep.edu.github.ui.UiStatus
24+
import camp.nextstep.edu.github.ui.main.GithubMainState
25+
26+
@OptIn(ExperimentalMaterial3Api::class)
27+
@Composable
28+
fun GithubRepositoryPage(
29+
status: GithubMainState
30+
) {
31+
if (status.status == UiStatus.Loading) {
32+
ProgressLoadingView()
33+
}
34+
if (status.status == UiStatus.Success) {
35+
Scaffold {
36+
Box(
37+
modifier = Modifier
38+
.fillMaxSize()
39+
.padding(it)
40+
) {
41+
RepositoriesView(status)
42+
}
43+
}
44+
}
45+
}
46+
47+
@Composable
48+
private fun ProgressLoadingView() {
49+
Box(
50+
modifier = Modifier.fillMaxSize(),
51+
contentAlignment = Alignment.Center
52+
) {
53+
CircularProgressIndicator()
54+
}
55+
}
56+
57+
@Composable
58+
fun RepositoriesView(
59+
status: GithubMainState
60+
) {
61+
LazyColumn(
62+
verticalArrangement = Arrangement.spacedBy(10.dp),
63+
contentPadding = PaddingValues(10.dp)
64+
) {
65+
items(status.repositories) {
66+
RepositoryItemView(it)
67+
}
68+
}
69+
}
70+
71+
@Composable
72+
fun RepositoryItemView(repository: GithubRepository) {
73+
Column {
74+
Text(text = repository.fullName, style = MaterialTheme.typography.displayMedium)
75+
Text(text = repository.description ?: "", style = MaterialTheme.typography.bodyMedium)
76+
Divider(color = Color.Black)
77+
}
78+
}

0 commit comments

Comments
 (0)