Feat: Commit list in home tab

This commit is contained in:
wingio 2023-03-20 14:27:20 -04:00
parent c9bf49d395
commit 3bfeb662f0
10 changed files with 245 additions and 6 deletions

View File

@ -80,10 +80,11 @@ android {
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.0")
implementation("androidx.paging:paging-compose:1.0.0-alpha18")
implementation(platform("androidx.compose:compose-bom:2022.10.00"))
implementation("androidx.activity:activity-compose:1.5.1")
implementation("androidx.activity:activity-compose:1.6.1")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
@ -108,6 +109,11 @@ dependencies {
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion")
val coilVersion = "2.2.2"
implementation("io.coil-kt:coil:$coilVersion")
implementation("io.coil-kt:coil-compose:$coilVersion")
val ktorVersion = "2.1.1"
implementation("io.ktor:ktor-client-core:$ktorVersion")
@ -117,7 +123,6 @@ dependencies {
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.github.diamondminer88:zip-android:2.1.0@aar")
// implementation("com.android.tools.build:apksig:7.4.0-beta04")
implementation(files("libs/lspatch.jar"))
testImplementation("junit:junit:4.13.2")

View File

@ -18,4 +18,6 @@ class RestRepository(
)
}
suspend fun getCommits(repo: String, page: Int = 1) = service.getCommits(repo, page)
}

View File

@ -0,0 +1,19 @@
package dev.beefers.vendetta.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Commit(
val sha: String,
@SerialName("commit") val info: Info,
@SerialName("html_url") val url: String,
val author: User
) {
@Serializable
data class Info(
val message: String
)
}

View File

@ -0,0 +1,10 @@
package dev.beefers.vendetta.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
@SerialName("login") val username: String,
@SerialName("avatar_url") val avatar: String
)

View File

@ -1,7 +1,9 @@
package dev.beefers.vendetta.manager.network.service
import dev.beefers.vendetta.manager.network.dto.Commit
import dev.beefers.vendetta.manager.network.dto.Index
import dev.beefers.vendetta.manager.network.dto.Release
import io.ktor.client.request.parameter
import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -22,4 +24,11 @@ class RestService(
}
}
suspend fun getCommits(repo: String, page: Int = 1) = withContext(Dispatchers.IO) {
httpService.request<List<Commit>> {
url("https://api.github.com/repos/vendetta-mod/$repo/commits")
parameter("page", page)
}
}
}

View File

@ -25,6 +25,17 @@ inline fun <D> ApiResponse<D>.ifSuccessful(block: (D) -> Unit) {
if (this is ApiResponse.Success) block(data)
}
fun <D> ApiResponse<D>.fold(
onSuccess: (D) -> Unit = {},
onError: () -> Unit = {}
) {
when(this) {
is ApiResponse.Success -> onSuccess(data)
is ApiResponse.Error,
is ApiResponse.Failure -> onError()
}
}
@Suppress("UNCHECKED_CAST")
fun <T, R> ApiResponse<T>.transform(block: (T) -> R): ApiResponse<R> {
return when (this) {

View File

@ -4,11 +4,15 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@ -18,6 +22,9 @@ import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -30,6 +37,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import androidx.paging.compose.itemsIndexed
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@ -39,6 +50,7 @@ import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
import dev.beefers.vendetta.manager.ui.components.SegmentedButton
import dev.beefers.vendetta.manager.ui.screen.installer.InstallerScreen
import dev.beefers.vendetta.manager.ui.viewmodel.home.HomeViewModel
import dev.beefers.vendetta.manager.ui.widgets.home.Commit
import dev.beefers.vendetta.manager.utils.DiscordVersion
import dev.beefers.vendetta.manager.utils.ManagerTab
import dev.beefers.vendetta.manager.utils.TabOptions
@ -150,10 +162,72 @@ class HomeScreen : ManagerTab {
}
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
ElevatedCard(
modifier = Modifier
.fillMaxSize()
) {
val commits = viewModel.commits.collectAsLazyPagingItems()
val loading = commits.loadState.append is LoadState.Loading || commits.loadState.refresh is LoadState.Loading
val failed = commits.loadState.append is LoadState.Error || commits.loadState.refresh is LoadState.Error
LazyColumn {
itemsIndexed(
items = commits,
key = { _, commit -> commit.sha }
) { i, commit ->
if (commit != null) {
Column {
Commit(commit = commit)
if(i < commits.itemSnapshotList.lastIndex) {
Divider(
thickness = 0.5.dp,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
}
if(loading) {
item {
Box(
contentAlignment = Alignment.TopCenter,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
CircularProgressIndicator(
strokeWidth = 3.dp,
modifier = Modifier.size(30.dp)
)
}
}
}
if(failed) {
item {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = stringResource(R.string.msg_load_fail),
style = MaterialTheme.typography.labelLarge,
textAlign = TextAlign.Center
)
Button(onClick = { commits.retry() }) {
Text(stringResource(R.string.action_retry))
}
}
}
}
}
}
}
}

View File

@ -7,12 +7,20 @@ import android.provider.Settings
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import dev.beefers.vendetta.manager.domain.manager.InstallManager
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
import dev.beefers.vendetta.manager.domain.repository.RestRepository
import dev.beefers.vendetta.manager.network.dto.Commit
import dev.beefers.vendetta.manager.network.utils.ApiResponse
import dev.beefers.vendetta.manager.network.utils.dataOrNull
import dev.beefers.vendetta.manager.network.utils.fold
import dev.beefers.vendetta.manager.utils.DiscordVersion
import kotlinx.coroutines.launch
@ -26,6 +34,29 @@ class HomeViewModel(
var discordVersions by mutableStateOf<Map<DiscordVersion.Type, DiscordVersion?>?>(null)
private set
val commits = Pager(PagingConfig(pageSize = 30)) {
object : PagingSource<Int, Commit>() {
override fun getRefreshKey(state: PagingState<Int, Commit>): Int? =
state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Commit> {
val page = params.key ?: 0
return when(val response = repo.getCommits("Vendetta", page)) {
is ApiResponse.Success -> LoadResult.Page(
data = response.data,
prevKey = if (page > 0) page - 1 else null,
nextKey = if (response.data.isNotEmpty()) page + 1 else null
)
is ApiResponse.Failure -> LoadResult.Error(response.error)
is ApiResponse.Error -> LoadResult.Error(response.error)
}
}
}
}.flow.cachedIn(coroutineScope)
init {
getDiscordVersions()
}
@ -57,7 +88,6 @@ class HomeViewModel(
context.startActivity(this)
}
}
}
}

View File

@ -0,0 +1,76 @@
package dev.beefers.vendetta.manager.ui.widgets.home
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
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.draw.clip
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import dev.beefers.vendetta.manager.network.dto.Commit
@Composable
fun Commit(
commit: Commit
) {
val uriHandler = LocalUriHandler.current
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.fillMaxWidth()
.clickable { uriHandler.openUri(commit.url) }
.padding(16.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
model = commit.author.avatar,
contentDescription = commit.author.username,
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
)
Text(
text = commit.author.username,
style = MaterialTheme.typography.labelMedium
)
}
Text(
"",
style = MaterialTheme.typography.labelLarge
)
Text(
text = commit.sha.substring(0, 7),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary,
fontFamily = FontFamily.Monospace
)
}
Text(
text = commit.info.message.split("\n").first(),
style = MaterialTheme.typography.labelLarge
)
}
}

View File

@ -42,6 +42,7 @@
<string name="action_launch">Launch</string>
<string name="action_uninstall">Uninstall</string>
<string name="action_info">Info</string>
<string name="action_retry">Retry</string>
<string name="installer_cached">Cached</string>
@ -70,4 +71,6 @@
<string name="channel_stable">Stable</string>
<string name="channel_beta">Beta</string>
<string name="channel_alpha">Alpha</string>
<string name="msg_load_fail">Failed to load commits</string>
</resources>