Refactor how extensions list is modelled
To better enable changing the UI in the future based on sections.
This commit is contained in:
parent
6bb3070c57
commit
75b23c99ec
@ -146,25 +146,15 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(
|
state.items.forEach { (header, items) ->
|
||||||
items = state.items,
|
item(
|
||||||
contentType = {
|
contentType = "header",
|
||||||
when (it) {
|
key = "extensionHeader-${header.hashCode()}",
|
||||||
is ExtensionUiModel.Header -> "header"
|
) {
|
||||||
is ExtensionUiModel.Item -> "item"
|
when (header) {
|
||||||
}
|
|
||||||
},
|
|
||||||
key = {
|
|
||||||
when (it) {
|
|
||||||
is ExtensionUiModel.Header -> "extensionHeader-${it.hashCode()}"
|
|
||||||
is ExtensionUiModel.Item -> "extension-${it.hashCode()}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) { item ->
|
|
||||||
when (item) {
|
|
||||||
is ExtensionUiModel.Header.Resource -> {
|
is ExtensionUiModel.Header.Resource -> {
|
||||||
val action: @Composable RowScope.() -> Unit =
|
val action: @Composable RowScope.() -> Unit =
|
||||||
if (item.textRes == R.string.ext_updates_pending) {
|
if (header.textRes == R.string.ext_updates_pending) {
|
||||||
{
|
{
|
||||||
Button(onClick = { onClickUpdateAll() }) {
|
Button(onClick = { onClickUpdateAll() }) {
|
||||||
Text(
|
Text(
|
||||||
@ -179,18 +169,25 @@ private fun ExtensionContent(
|
|||||||
{}
|
{}
|
||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = item.textRes,
|
textRes = header.textRes,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionUiModel.Header.Text -> {
|
is ExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = item.text,
|
text = header.text,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionUiModel.Item -> {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(
|
||||||
|
items = items,
|
||||||
|
contentType = { "item" },
|
||||||
|
key = { "extension-${it.hashCode()}" },
|
||||||
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
item = item,
|
item = item,
|
||||||
@ -220,7 +217,6 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (trustState != null) {
|
if (trustState != null) {
|
||||||
ExtensionTrustDialog(
|
ExtensionTrustDialog(
|
||||||
onClickConfirm = {
|
onClickConfirm = {
|
||||||
|
@ -40,7 +40,7 @@ class ExtensionsScreenModel(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val context = Injekt.get<Application>()
|
val context = Injekt.get<Application>()
|
||||||
val extensionMapper: (Map<String, InstallStep>) -> ((Extension) -> ExtensionUiModel) = { map ->
|
val extensionMapper: (Map<String, InstallStep>) -> ((Extension) -> ExtensionUiModel.Item) = { map ->
|
||||||
{
|
{
|
||||||
ExtensionUiModel.Item(it, map[it.pkgName] ?: InstallStep.Idle)
|
ExtensionUiModel.Item(it, map[it.pkgName] ?: InstallStep.Idle)
|
||||||
}
|
}
|
||||||
@ -80,38 +80,31 @@ class ExtensionsScreenModel(
|
|||||||
) { query, downloads, (_updates, _installed, _available, _untrusted) ->
|
) { query, downloads, (_updates, _installed, _available, _untrusted) ->
|
||||||
val searchQuery = query ?: ""
|
val searchQuery = query ?: ""
|
||||||
|
|
||||||
val languagesWithExtensions = _available
|
val itemsGroups: ItemGroups = mutableMapOf()
|
||||||
.filter(queryFilter(searchQuery))
|
|
||||||
.groupBy { it.lang }
|
|
||||||
.toSortedMap(LocaleHelper.comparator)
|
|
||||||
.flatMap { (lang, exts) ->
|
|
||||||
listOf(
|
|
||||||
ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)),
|
|
||||||
*exts.map(extensionMapper(downloads)).toTypedArray(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val items = mutableListOf<ExtensionUiModel>()
|
|
||||||
|
|
||||||
val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
|
val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
|
||||||
if (updates.isNotEmpty()) {
|
if (updates.isNotEmpty()) {
|
||||||
items.add(ExtensionUiModel.Header.Resource(R.string.ext_updates_pending))
|
itemsGroups[ExtensionUiModel.Header.Resource(R.string.ext_updates_pending)] = updates
|
||||||
items.addAll(updates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val installed = _installed.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
|
val installed = _installed.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
|
||||||
val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
|
val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
|
||||||
if (installed.isNotEmpty() || untrusted.isNotEmpty()) {
|
if (installed.isNotEmpty() || untrusted.isNotEmpty()) {
|
||||||
items.add(ExtensionUiModel.Header.Resource(R.string.ext_installed))
|
itemsGroups[ExtensionUiModel.Header.Resource(R.string.ext_installed)] = installed + untrusted
|
||||||
items.addAll(installed)
|
|
||||||
items.addAll(untrusted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val languagesWithExtensions = _available
|
||||||
|
.filter(queryFilter(searchQuery))
|
||||||
|
.groupBy { it.lang }
|
||||||
|
.toSortedMap(LocaleHelper.comparator)
|
||||||
|
.map { (lang, exts) ->
|
||||||
|
ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to exts.map(extensionMapper(downloads))
|
||||||
|
}
|
||||||
if (languagesWithExtensions.isNotEmpty()) {
|
if (languagesWithExtensions.isNotEmpty()) {
|
||||||
items.addAll(languagesWithExtensions)
|
itemsGroups.putAll(languagesWithExtensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
items
|
itemsGroups
|
||||||
}
|
}
|
||||||
.collectLatest {
|
.collectLatest {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
@ -140,10 +133,10 @@ class ExtensionsScreenModel(
|
|||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
with(state.value) {
|
with(state.value) {
|
||||||
if (isEmpty) return@launchIO
|
if (isEmpty) return@launchIO
|
||||||
items
|
items.values
|
||||||
|
.flatten()
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
when {
|
when {
|
||||||
it !is ExtensionUiModel.Item -> null
|
|
||||||
it.extension !is Extension.Installed -> null
|
it.extension !is Extension.Installed -> null
|
||||||
!it.extension.hasUpdate -> null
|
!it.extension.hasUpdate -> null
|
||||||
else -> it.extension
|
else -> it.extension
|
||||||
@ -216,14 +209,16 @@ class ExtensionsScreenModel(
|
|||||||
data class ExtensionsState(
|
data class ExtensionsState(
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val isRefreshing: Boolean = false,
|
val isRefreshing: Boolean = false,
|
||||||
val items: List<ExtensionUiModel> = emptyList(),
|
val items: ItemGroups = mutableMapOf(),
|
||||||
val updates: Int = 0,
|
val updates: Int = 0,
|
||||||
) {
|
) {
|
||||||
val isEmpty = items.isEmpty()
|
val isEmpty = items.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ExtensionUiModel {
|
typealias ItemGroups = MutableMap<ExtensionUiModel.Header, List<ExtensionUiModel.Item>>
|
||||||
sealed interface Header : ExtensionUiModel {
|
|
||||||
|
object ExtensionUiModel {
|
||||||
|
sealed interface Header {
|
||||||
data class Resource(@StringRes val textRes: Int) : Header
|
data class Resource(@StringRes val textRes: Int) : Header
|
||||||
data class Text(val text: String) : Header
|
data class Text(val text: String) : Header
|
||||||
}
|
}
|
||||||
@ -231,5 +226,5 @@ sealed interface ExtensionUiModel {
|
|||||||
data class Item(
|
data class Item(
|
||||||
val extension: Extension,
|
val extension: Extension,
|
||||||
val installStep: InstallStep,
|
val installStep: InstallStep,
|
||||||
) : ExtensionUiModel
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user