Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ data class OnlineModule(
get() = url ?: "https://github.com/Xposed-Modules-Repo/$name"
}


data class Collaborator(
@SerializedName("login") val login: String?,
@SerializedName("name") val name: String?,
Expand Down Expand Up @@ -268,7 +269,15 @@ data class StoreCatalog(
val loaded: Boolean = false,
val fromCache: Boolean = false,
val loadedAtMillis: Long = 0L,
val page: Int = 1,
val pageCount: Int = 1,
val total: Int = 0,
val isSyncing: Boolean = false,
val syncProgress: Float = 0f,
) {
val isEmpty: Boolean
get() = modules.isEmpty()

val isFullySynced: Boolean
get() = page >= pageCount && pageCount > 0
}
99 changes: 73 additions & 26 deletions manager-ui/src/main/kotlin/org/matrix/vector/ui/store/RepoScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.CloudOff
Expand All @@ -41,16 +42,19 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand Down Expand Up @@ -109,7 +113,7 @@ fun RepoScreen(
StoreHeader(
catalog = catalog,
updates = updates,
search = { StoreSearch(query, viewModel, sort, priorities, channel) },
search = { StoreSearch(query, viewModel, sort, priorities, channel, isSyncing = catalog.isSyncing) },
actions = actions,
)

Expand All @@ -124,30 +128,65 @@ fun RepoScreen(
return@Column
}

PullToRefreshBox(isRefreshing = refreshing, onRefresh = viewModel::refresh) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 20.dp),
) {
if (entries.isEmpty()) {
// Inside the list rather than beside it: an empty Box has nothing to
// scroll, and pull-to-refresh is exactly what the reader wants when the
// reason the list is empty is that the network was down.
item {
EmptyState(
modifier = Modifier.fillParentMaxSize(),
catalog = catalog,
filtered = query.isNotBlank(),
)
}
} else {
items(entries, key = { it.module.name }) { entry ->
StoreRow(entry = entry, onClick = { onModuleClick(entry.module.name) })
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant)
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
PullToRefreshBox(isRefreshing = refreshing, onRefresh = viewModel::refresh) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 20.dp),
) {
if (entries.isEmpty()) {
// Inside the list rather than beside it: an empty Box has nothing to
// scroll, and pull-to-refresh is exactly what the reader wants when the
// reason the list is empty is that the network was down.
item {
EmptyState(
modifier = Modifier.fillParentMaxSize(),
catalog = catalog,
filtered = query.isNotBlank(),
)
}
} else {
items(entries, key = { it.module.name }) { entry ->
StoreRow(entry = entry, onClick = { onModuleClick(entry.module.name) })
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant)
}
}
}
}
}

// Sync progress bar positioned above the bottom bar
if (catalog.isSyncing) {
Column(
modifier =
Modifier.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 6.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Syncing modules (${catalog.page}/${catalog.pageCount})...",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
text = "${(catalog.syncProgress * 100).toInt()}%",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
)
}
Spacer(Modifier.height(4.dp))
LinearProgressIndicator(
progress = { catalog.syncProgress },
modifier = Modifier.fillMaxWidth().height(3.dp),
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
}
}
}
}
Expand All @@ -165,11 +204,12 @@ private fun StoreHeader(
actions = actions,
description = {
if (catalog.modules.isNotEmpty()) {
val count = if (catalog.total > 0) catalog.total else catalog.modules.size
val total =
context.resources.getQuantityString(
R.plurals.store_module_count,
catalog.modules.size,
catalog.modules.size,
count,
count,
)
// "Up to date" is stated in words rather than as a zero, because a zero in a row
// of counts reads as a failure to load rather than as good news.
Expand Down Expand Up @@ -200,6 +240,7 @@ private fun StoreSearch(
sort: StoreSort,
priorities: List<StorePriority>,
channel: StoreChannel,
isSyncing: Boolean,
) {
SearchField(
query = query,
Expand All @@ -213,6 +254,7 @@ private fun StoreSearch(
onTogglePriority = viewModel::togglePriority,
channel = channel,
onChannelChange = viewModel::setChannel,
isSyncing = isSyncing,
)
}
}
Expand All @@ -235,21 +277,25 @@ private fun StoreFilterButton(
onTogglePriority: (StorePriority) -> Unit,
channel: StoreChannel,
onChannelChange: (StoreChannel) -> Unit,
isSyncing: Boolean = false,
) {
var sheetOpen by remember { mutableStateOf(false) }
val narrowed =
sort != StoreSort.RecentlyUpdated ||
channel != StoreChannel.Stable ||
priorities != listOf(StorePriority.Updates)

IconButton(onClick = { sheetOpen = true }) {
IconButton(onClick = { if (!isSyncing) sheetOpen = true }, enabled = !isSyncing) {
BadgedBox(badge = { if (narrowed) Badge(modifier = Modifier.size(6.dp)) }) {
Icon(
Icons.Rounded.FilterList,
contentDescription = stringResource(R.string.store_filter),
tint =
if (narrowed) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurfaceVariant,
when {
isSyncing -> MaterialTheme.colorScheme.outlineVariant
narrowed -> MaterialTheme.colorScheme.primary
else -> MaterialTheme.colorScheme.onSurfaceVariant
},
)
}
}
Expand Down Expand Up @@ -427,6 +473,7 @@ private fun StoreRow(entry: StoreEntry, onClick: () -> Unit) {
}
}


@Composable
private fun RowBadge(icon: ImageVector, text: String, tint: Color, modifier: Modifier = Modifier) {
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
Expand Down
Loading
Loading