Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow staring and unstarring repos within the repo details page #33

Merged
merged 9 commits into from
Nov 14, 2023
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fragment RepoDetails on Repository {
id
description
readme {
contentHTML
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ inline fun <T> GraphQLResponse<T>.ifSuccessful(block: (T) -> Unit) {
}
}

inline fun <T> GraphQLResponse<T>.ifUnsuccessful(block: (String) -> Unit) {
fold(
onSuccess = {},
onError = block
)
}

fun <T> GraphQLResponse<T>.getOrNull(): T? = when (this) {
is GraphQLResponse.Success -> data
is GraphQLResponse.Error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,72 @@ package com.materiiapps.gloom.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
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.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RowScope.LargeSegmentedButton(
icon: Any,
text: String,
onClick: () -> Unit,
iconDescription: String? = null,
text: String
enabled: Boolean = true
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
modifier = Modifier
.clickable(enabled = enabled, role = Role.Button) { onClick() }
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
.weight(1f)
.padding(16.dp)
) {
when (icon) {
is ImageVector -> {
Icon(
imageVector = icon,
contentDescription = iconDescription,
tint = MaterialTheme.colorScheme.primary
)
}
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.primary.copy(if (enabled) 1f else 0.5f)
) {
when (icon) {
is ImageVector -> {
Icon(
imageVector = icon,
contentDescription = iconDescription
)
}

is Painter -> {
Icon(
painter = icon,
contentDescription = iconDescription,
tint = MaterialTheme.colorScheme.primary
)
is Painter -> {
Icon(
painter = icon,
contentDescription = iconDescription
)
}
}
}

Text(
text = text,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
maxLines = 1,
modifier = Modifier.basicMarquee(Int.MAX_VALUE)
)
Text(
text = text,
style = MaterialTheme.typography.labelLarge,
maxLines = 1,
modifier = Modifier.basicMarquee(Int.MAX_VALUE)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,15 @@ class DetailsTab(
res = Res.plurals.stars,
count = repoDetails.stargazerCount,
repoDetails.stargazerCount
)
),
onClick = viewModel::toggleStar,
enabled = !viewModel.isStarLoading
)
repoDetails.licenseInfo?.let {
LargeSegmentedButton(
icon = Icons.Custom.Balance,
text = it.nickname ?: it.key.uppercase()
text = it.nickname ?: it.key.uppercase(),
onClick = { /* TODO */ }
)
}
LargeSegmentedButton(
Expand All @@ -176,7 +179,8 @@ class DetailsTab(
res = Res.plurals.forks,
count = repoDetails.forkCount,
repoDetails.forkCount
)
),
onClick = { /* TODO */ }
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import com.materiiapps.gloom.api.repository.GraphQLRepository
import com.materiiapps.gloom.api.utils.fold
import com.materiiapps.gloom.api.utils.ifUnsuccessful
import com.materiiapps.gloom.gql.fragment.RepoDetails
import kotlinx.coroutines.launch

Expand All @@ -22,6 +23,8 @@ class RepoDetailsViewModel(
var detailsLoading by mutableStateOf(false)
var hasError by mutableStateOf(false)

var isStarLoading by mutableStateOf(false)

init {
loadDetails()
}
Expand All @@ -42,4 +45,33 @@ class RepoDetailsViewModel(
}
}

private fun updateStarDetails(starred: Boolean) {
details = details?.copy(
viewerHasStarred = starred,
stargazerCount = details!!.stargazerCount + if (starred) 1 else -1
)
}

fun toggleStar() {
val details = details!!
val hasStarred = details.viewerHasStarred

// Optimistic update
updateStarDetails(starred = !hasStarred)

coroutineScope.launch {
isStarLoading = true

if (hasStarred) {
gql.unstarRepo(details.id)
} else {
gql.starRepo(details.id)
}.ifUnsuccessful {
// Revert optimistic update
updateStarDetails(starred = hasStarred)
}
isStarLoading = false
}
}

}
Loading