Skip to content
This repository was archived by the owner on Jul 16, 2024. It is now read-only.

Commit 2e08d99

Browse files
authored
Compose RepoDetailActivity (#57)
* Add DetailApi * Add moshi support * Add DetailViewModel * Use DetailViewModel * Add elements * Add click events * Observe vm.repoDetailModel * Increase fork count when clicking * Request in viewModelScope * Remove abiFilters limit * Expose immutable livedata * Rearrange * Use fullName as path param * Fix 404 in 6bb19a5 * Pass fullName to RepoDetailActivity * Fix nullable * Add accompanist deps * Add DetailPageWithSwipeRefresh * Add refresh state logic * Fill with max width * Move Composables to a new file * Fill with max size * Allow column scroll * Auto refresh * Move theme block into DetailPageWithSwipeRefresh * Revert "Auto refresh" This reverts commit 012720a.
1 parent 1da2dce commit 2e08d99

File tree

17 files changed

+261
-25
lines changed

17 files changed

+261
-25
lines changed

app/src/main/kotlin/io/goooler/demoapp/RouterManagerImpl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ object RouterManagerImpl : RouterManager {
3232
.let(context::startActivity)
3333
}
3434

35-
override fun goRepoDetail(context: Context) {
35+
override fun goRepoDetail(context: Context, fullName: String) {
3636
Intent(context, RepoDetailActivity::class.java)
37+
.putExtra(RepoDetailActivity.FULL_NAME, fullName)
3738
.let(context::startActivity)
3839
}
3940

buildSrc/src/main/kotlin/ProjectExtensions.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ inline fun <reified T : BaseExtension> Project.setupBase(
4848
defaultConfig {
4949
minSdk = 21
5050
vectorDrawables.useSupportLibrary = true
51-
ndk.abiFilters += setOf("arm64-v8a")
5251
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
5352
}
5453
sourceSets.configureEach {

common/src/main/kotlin/io/goooler/demoapp/common/router/RouterManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface RouterManager {
1010

1111
fun goMain(context: Context)
1212

13-
fun goRepoDetail(context: Context)
13+
fun goRepoDetail(context: Context, fullName: String)
1414

1515
fun goAudioPlay(context: Context)
1616

@@ -35,8 +35,8 @@ interface RouterManager {
3535
impl.goMain(context)
3636
}
3737

38-
override fun goRepoDetail(context: Context) {
39-
impl.goRepoDetail(context)
38+
override fun goRepoDetail(context: Context, fullName: String) {
39+
impl.goRepoDetail(context, fullName)
4040
}
4141

4242
override fun goAudioPlay(context: Context) {

detail/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id(libs.plugins.android.library.get().pluginId)
33
id(libs.plugins.kotlin.android.get().pluginId)
4+
alias(libs.plugins.moshiX)
45
}
56

67
setupLib(LibModule.Detail) {
@@ -11,4 +12,7 @@ setupLib(LibModule.Detail) {
1112
dependencies {
1213
implementation(libs.androidX.activity.compose)
1314
implementation(libs.bundles.androidX.compose)
15+
implementation(libs.bundles.accompanist)
16+
17+
implementation(libs.square.moshi)
1418
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.goooler.demoapp.detail.api
2+
3+
import io.goooler.demoapp.detail.bean.RepoDetailBean
4+
import retrofit2.http.GET
5+
import retrofit2.http.Path
6+
7+
interface DetailApi {
8+
9+
@GET("https://api.github.com/repos/{fullName}")
10+
suspend fun getRepoDetail(
11+
@Path(value = "fullName", encoded = true) fullName: String
12+
): RepoDetailBean
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.goooler.demoapp.detail.bean
2+
3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
6+
@JsonClass(generateAdapter = true)
7+
class RepoDetailBean(
8+
@Json(name = "full_name") val fullName: String,
9+
val description: String?,
10+
@Json(name = "stargazers_count") val starsCount: Int,
11+
@Json(name = "forks_count") val forksCount: Int,
12+
@Json(name = "open_issues_count") val openIssuesCount: Int,
13+
val license: License?
14+
) {
15+
@JsonClass(generateAdapter = true)
16+
class License(val name: String)
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.goooler.demoapp.detail.model
2+
3+
data class RepoDetailModel(
4+
val fullName: String = "",
5+
val description: String = "",
6+
val license: String = "",
7+
val starsCount: Int = 0,
8+
var forksCount: Int = 0,
9+
val openIssuesCount: Int = 0
10+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.goooler.demoapp.detail.repository
2+
3+
import io.goooler.demoapp.detail.api.DetailApi
4+
import io.goooler.demoapp.detail.bean.RepoDetailBean
5+
6+
class DetailRepository(private val api: DetailApi) {
7+
8+
suspend fun getRepoDetail(fullName: String): RepoDetailBean =
9+
api.getRepoDetail(fullName)
10+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package io.goooler.demoapp.detail.ui
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.layout.width
12+
import androidx.compose.foundation.rememberScrollState
13+
import androidx.compose.foundation.verticalScroll
14+
import androidx.compose.material.Button
15+
import androidx.compose.material.ButtonDefaults
16+
import androidx.compose.material.Icon
17+
import androidx.compose.material.MaterialTheme
18+
import androidx.compose.material.Text
19+
import androidx.compose.material.icons.Icons
20+
import androidx.compose.material.icons.filled.Share
21+
import androidx.compose.material.icons.filled.Star
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.getValue
24+
import androidx.compose.runtime.mutableStateOf
25+
import androidx.compose.runtime.remember
26+
import androidx.compose.runtime.setValue
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import androidx.compose.ui.unit.dp
30+
import com.google.accompanist.swiperefresh.SwipeRefresh
31+
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
32+
import io.goooler.demoapp.common.util.showToast
33+
import io.goooler.demoapp.detail.model.RepoDetailModel
34+
35+
@Composable
36+
fun DetailPageWithSwipeRefresh(
37+
isRefreshing: Boolean,
38+
onRefresh: () -> Unit,
39+
model: RepoDetailModel,
40+
onForkClick: () -> Unit
41+
) {
42+
MaterialTheme {
43+
SwipeRefresh(state = rememberSwipeRefreshState(isRefreshing), onRefresh = onRefresh) {
44+
DetailPage(model, onForkClick)
45+
}
46+
}
47+
}
48+
49+
@Composable
50+
fun DetailPage(model: RepoDetailModel, onForkClick: () -> Unit) {
51+
var isDescExpanded by remember { mutableStateOf(false) }
52+
53+
Column(
54+
modifier = Modifier
55+
.padding(8.dp)
56+
.fillMaxSize()
57+
.verticalScroll(rememberScrollState())
58+
) {
59+
Text(
60+
text = model.fullName,
61+
style = MaterialTheme.typography.h5,
62+
maxLines = 1
63+
)
64+
Spacer(modifier = Modifier.height(5.dp))
65+
Text(
66+
text = model.description,
67+
style = MaterialTheme.typography.body1,
68+
maxLines = if (isDescExpanded) Int.MAX_VALUE else 2,
69+
modifier = Modifier.clickable {
70+
isDescExpanded = !isDescExpanded
71+
}
72+
)
73+
Spacer(modifier = Modifier.height(5.dp))
74+
Row {
75+
Button(onClick = {
76+
"All ${model.starsCount} stars".showToast()
77+
}) {
78+
Icon(
79+
Icons.Filled.Star,
80+
contentDescription = "Star",
81+
modifier = Modifier.size(ButtonDefaults.IconSize)
82+
)
83+
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
84+
Text(model.starsCount.toString())
85+
}
86+
Spacer(modifier = Modifier.width(20.dp))
87+
Button(onClick = onForkClick) {
88+
Icon(
89+
Icons.Filled.Share,
90+
contentDescription = "Fork",
91+
modifier = Modifier.size(ButtonDefaults.IconSize)
92+
)
93+
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
94+
Text(model.forksCount.toString())
95+
}
96+
}
97+
Spacer(modifier = Modifier.height(5.dp))
98+
}
99+
}
100+
101+
@Preview
102+
@Composable
103+
fun DetailPagePreview() {
104+
val model = RepoDetailModel(
105+
"Compose/Demo",
106+
"Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.",
107+
"Apache",
108+
99,
109+
1,
110+
2
111+
)
112+
DetailPage(model) {}
113+
}

detail/src/main/kotlin/io/goooler/demoapp/detail/ui/RepoDetailActivity.kt

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,33 @@ package io.goooler.demoapp.detail.ui
22

33
import android.os.Bundle
44
import androidx.activity.compose.setContent
5-
import androidx.compose.material.Text
6-
import androidx.compose.runtime.Composable
7-
import androidx.compose.ui.tooling.preview.Preview
5+
import androidx.activity.viewModels
6+
import androidx.compose.runtime.getValue
7+
import androidx.compose.runtime.livedata.observeAsState
88
import io.goooler.demoapp.base.core.BaseActivity
9+
import io.goooler.demoapp.detail.vm.DetailViewModel
910

1011
class RepoDetailActivity : BaseActivity() {
1112

13+
private val vm: DetailViewModel by viewModels()
14+
1215
override fun onCreate(savedInstanceState: Bundle?) {
1316
super.onCreate(savedInstanceState)
17+
18+
intent.getStringExtra(FULL_NAME)?.let {
19+
vm.fullName = it
20+
vm.refresh()
21+
}
22+
1423
setContent {
15-
HelloScreen()
24+
val model = vm.repoDetailModel.observeAsState().value
25+
?: throw IllegalArgumentException("RepoDetailModel has not been initialized")
26+
val isRefreshing by vm.isRefreshing.observeAsState(false)
27+
DetailPageWithSwipeRefresh(isRefreshing, vm::refresh, model, vm::fork)
1628
}
1729
}
18-
}
1930

20-
@Preview
21-
@Composable
22-
fun HelloScreen() {
23-
Text(text = "Hello Compose")
31+
companion object {
32+
const val FULL_NAME = "fullName"
33+
}
2434
}

0 commit comments

Comments
 (0)