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 {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
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(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")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
|
@ -108,6 +109,11 @@ dependencies {
|
||||||
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
||||||
implementation("cafe.adriel.voyager:voyager-koin:$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"
|
val ktorVersion = "2.1.1"
|
||||||
|
|
||||||
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
implementation("io.ktor:ktor-client-core:$ktorVersion")
|
||||||
|
@ -117,7 +123,6 @@ dependencies {
|
||||||
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
||||||
|
|
||||||
implementation("io.github.diamondminer88:zip-android:2.1.0@aar")
|
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"))
|
implementation(files("libs/lspatch.jar"))
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
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
|
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.Index
|
||||||
import dev.beefers.vendetta.manager.network.dto.Release
|
import dev.beefers.vendetta.manager.network.dto.Release
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
import io.ktor.client.request.url
|
import io.ktor.client.request.url
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
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)
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T, R> ApiResponse<T>.transform(block: (T) -> R): ApiResponse<R> {
|
fun <T, R> ApiResponse<T>.transform(block: (T) -> R): ApiResponse<R> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
|
|
@ -4,11 +4,15 @@ import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
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.filled.OpenInNew
|
||||||
import androidx.compose.material.icons.outlined.Home
|
import androidx.compose.material.icons.outlined.Home
|
||||||
import androidx.compose.material3.Button
|
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.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
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.koin.getScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
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.components.SegmentedButton
|
||||||
import dev.beefers.vendetta.manager.ui.screen.installer.InstallerScreen
|
import dev.beefers.vendetta.manager.ui.screen.installer.InstallerScreen
|
||||||
import dev.beefers.vendetta.manager.ui.viewmodel.home.HomeViewModel
|
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.DiscordVersion
|
||||||
import dev.beefers.vendetta.manager.utils.ManagerTab
|
import dev.beefers.vendetta.manager.utils.ManagerTab
|
||||||
import dev.beefers.vendetta.manager.utils.TabOptions
|
import dev.beefers.vendetta.manager.utils.TabOptions
|
||||||
|
@ -150,10 +162,72 @@ class HomeScreen : ManagerTab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
ElevatedCard(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
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.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
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.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import dev.beefers.vendetta.manager.domain.manager.InstallManager
|
import dev.beefers.vendetta.manager.domain.manager.InstallManager
|
||||||
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
|
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager
|
||||||
import dev.beefers.vendetta.manager.domain.repository.RestRepository
|
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.dataOrNull
|
||||||
|
import dev.beefers.vendetta.manager.network.utils.fold
|
||||||
import dev.beefers.vendetta.manager.utils.DiscordVersion
|
import dev.beefers.vendetta.manager.utils.DiscordVersion
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -26,6 +34,29 @@ class HomeViewModel(
|
||||||
var discordVersions by mutableStateOf<Map<DiscordVersion.Type, DiscordVersion?>?>(null)
|
var discordVersions by mutableStateOf<Map<DiscordVersion.Type, DiscordVersion?>?>(null)
|
||||||
private set
|
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 {
|
init {
|
||||||
getDiscordVersions()
|
getDiscordVersions()
|
||||||
}
|
}
|
||||||
|
@ -57,7 +88,6 @@ class HomeViewModel(
|
||||||
context.startActivity(this)
|
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_launch">Launch</string>
|
||||||
<string name="action_uninstall">Uninstall</string>
|
<string name="action_uninstall">Uninstall</string>
|
||||||
<string name="action_info">Info</string>
|
<string name="action_info">Info</string>
|
||||||
|
<string name="action_retry">Retry</string>
|
||||||
|
|
||||||
<string name="installer_cached">Cached</string>
|
<string name="installer_cached">Cached</string>
|
||||||
|
|
||||||
|
@ -70,4 +71,6 @@
|
||||||
<string name="channel_stable">Stable</string>
|
<string name="channel_stable">Stable</string>
|
||||||
<string name="channel_beta">Beta</string>
|
<string name="channel_beta">Beta</string>
|
||||||
<string name="channel_alpha">Alpha</string>
|
<string name="channel_alpha">Alpha</string>
|
||||||
|
|
||||||
|
<string name="msg_load_fail">Failed to load commits</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue