Feat: Commit list in home tab
This commit is contained in:
parent
c9bf49d395
commit
3bfeb662f0
10 changed files with 245 additions and 6 deletions
|
@ -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")
|
||||
|
|
|
@ -18,4 +18,6 @@ class RestRepository(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun getCommits(repo: String, page: Int = 1) = service.getCommits(repo, page)
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>
|
Loading…
Reference in a new issue