mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Refactor how extensions list is modelled
To better enable changing the UI in the future based on sections.
This commit is contained in:
		@@ -146,79 +146,75 @@ private fun ExtensionContent(
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        items(
 | 
			
		||||
            items = state.items,
 | 
			
		||||
            contentType = {
 | 
			
		||||
                when (it) {
 | 
			
		||||
                    is ExtensionUiModel.Header -> "header"
 | 
			
		||||
                    is ExtensionUiModel.Item -> "item"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            key = {
 | 
			
		||||
                when (it) {
 | 
			
		||||
                    is ExtensionUiModel.Header -> "extensionHeader-${it.hashCode()}"
 | 
			
		||||
                    is ExtensionUiModel.Item -> "extension-${it.hashCode()}"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        ) { item ->
 | 
			
		||||
            when (item) {
 | 
			
		||||
                is ExtensionUiModel.Header.Resource -> {
 | 
			
		||||
                    val action: @Composable RowScope.() -> Unit =
 | 
			
		||||
                        if (item.textRes == R.string.ext_updates_pending) {
 | 
			
		||||
                            {
 | 
			
		||||
                                Button(onClick = { onClickUpdateAll() }) {
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                        text = stringResource(R.string.ext_update_all),
 | 
			
		||||
                                        style = LocalTextStyle.current.copy(
 | 
			
		||||
                                            color = MaterialTheme.colorScheme.onPrimary,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            {}
 | 
			
		||||
                        }
 | 
			
		||||
                    ExtensionHeader(
 | 
			
		||||
                        textRes = item.textRes,
 | 
			
		||||
                        modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                        action = action,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                is ExtensionUiModel.Header.Text -> {
 | 
			
		||||
                    ExtensionHeader(
 | 
			
		||||
                        text = item.text,
 | 
			
		||||
                        modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                is ExtensionUiModel.Item -> {
 | 
			
		||||
                    ExtensionItem(
 | 
			
		||||
                        modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                        item = item,
 | 
			
		||||
                        onClickItem = {
 | 
			
		||||
                            when (it) {
 | 
			
		||||
                                is Extension.Available -> onInstallExtension(it)
 | 
			
		||||
                                is Extension.Installed -> onOpenExtension(it)
 | 
			
		||||
                                is Extension.Untrusted -> { trustState = it }
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        onLongClickItem = onLongClickItem,
 | 
			
		||||
                        onClickItemCancel = onClickItemCancel,
 | 
			
		||||
                        onClickItemAction = {
 | 
			
		||||
                            when (it) {
 | 
			
		||||
                                is Extension.Available -> onInstallExtension(it)
 | 
			
		||||
                                is Extension.Installed -> {
 | 
			
		||||
                                    if (it.hasUpdate) {
 | 
			
		||||
                                        onUpdateExtension(it)
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        onOpenExtension(it)
 | 
			
		||||
        state.items.forEach { (header, items) ->
 | 
			
		||||
            item(
 | 
			
		||||
                contentType = "header",
 | 
			
		||||
                key = "extensionHeader-${header.hashCode()}",
 | 
			
		||||
            ) {
 | 
			
		||||
                when (header) {
 | 
			
		||||
                    is ExtensionUiModel.Header.Resource -> {
 | 
			
		||||
                        val action: @Composable RowScope.() -> Unit =
 | 
			
		||||
                            if (header.textRes == R.string.ext_updates_pending) {
 | 
			
		||||
                                {
 | 
			
		||||
                                    Button(onClick = { onClickUpdateAll() }) {
 | 
			
		||||
                                        Text(
 | 
			
		||||
                                            text = stringResource(R.string.ext_update_all),
 | 
			
		||||
                                            style = LocalTextStyle.current.copy(
 | 
			
		||||
                                                color = MaterialTheme.colorScheme.onPrimary,
 | 
			
		||||
                                            ),
 | 
			
		||||
                                        )
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                is Extension.Untrusted -> { trustState = it }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                {}
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                    )
 | 
			
		||||
                        ExtensionHeader(
 | 
			
		||||
                            textRes = header.textRes,
 | 
			
		||||
                            modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                            action = action,
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                    is ExtensionUiModel.Header.Text -> {
 | 
			
		||||
                        ExtensionHeader(
 | 
			
		||||
                            text = header.text,
 | 
			
		||||
                            modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            items(
 | 
			
		||||
                items = items,
 | 
			
		||||
                contentType = { "item" },
 | 
			
		||||
                key = { "extension-${it.hashCode()}" },
 | 
			
		||||
            ) { item ->
 | 
			
		||||
                ExtensionItem(
 | 
			
		||||
                    modifier = Modifier.animateItemPlacement(),
 | 
			
		||||
                    item = item,
 | 
			
		||||
                    onClickItem = {
 | 
			
		||||
                        when (it) {
 | 
			
		||||
                            is Extension.Available -> onInstallExtension(it)
 | 
			
		||||
                            is Extension.Installed -> onOpenExtension(it)
 | 
			
		||||
                            is Extension.Untrusted -> { trustState = it }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    onLongClickItem = onLongClickItem,
 | 
			
		||||
                    onClickItemCancel = onClickItemCancel,
 | 
			
		||||
                    onClickItemAction = {
 | 
			
		||||
                        when (it) {
 | 
			
		||||
                            is Extension.Available -> onInstallExtension(it)
 | 
			
		||||
                            is Extension.Installed -> {
 | 
			
		||||
                                if (it.hasUpdate) {
 | 
			
		||||
                                    onUpdateExtension(it)
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    onOpenExtension(it)
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            is Extension.Untrusted -> { trustState = it }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (trustState != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ class ExtensionsScreenModel(
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        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)
 | 
			
		||||
            }
 | 
			
		||||
@@ -80,38 +80,31 @@ class ExtensionsScreenModel(
 | 
			
		||||
            ) { query, downloads, (_updates, _installed, _available, _untrusted) ->
 | 
			
		||||
                val searchQuery = query ?: ""
 | 
			
		||||
 | 
			
		||||
                val languagesWithExtensions = _available
 | 
			
		||||
                    .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 itemsGroups: ItemGroups = mutableMapOf()
 | 
			
		||||
 | 
			
		||||
                val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
 | 
			
		||||
                if (updates.isNotEmpty()) {
 | 
			
		||||
                    items.add(ExtensionUiModel.Header.Resource(R.string.ext_updates_pending))
 | 
			
		||||
                    items.addAll(updates)
 | 
			
		||||
                    itemsGroups[ExtensionUiModel.Header.Resource(R.string.ext_updates_pending)] = updates
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                val installed = _installed.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
 | 
			
		||||
                val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
 | 
			
		||||
                if (installed.isNotEmpty() || untrusted.isNotEmpty()) {
 | 
			
		||||
                    items.add(ExtensionUiModel.Header.Resource(R.string.ext_installed))
 | 
			
		||||
                    items.addAll(installed)
 | 
			
		||||
                    items.addAll(untrusted)
 | 
			
		||||
                    itemsGroups[ExtensionUiModel.Header.Resource(R.string.ext_installed)] = installed + 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()) {
 | 
			
		||||
                    items.addAll(languagesWithExtensions)
 | 
			
		||||
                    itemsGroups.putAll(languagesWithExtensions)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                items
 | 
			
		||||
                itemsGroups
 | 
			
		||||
            }
 | 
			
		||||
                .collectLatest {
 | 
			
		||||
                    mutableState.update { state ->
 | 
			
		||||
@@ -140,10 +133,10 @@ class ExtensionsScreenModel(
 | 
			
		||||
        coroutineScope.launchIO {
 | 
			
		||||
            with(state.value) {
 | 
			
		||||
                if (isEmpty) return@launchIO
 | 
			
		||||
                items
 | 
			
		||||
                items.values
 | 
			
		||||
                    .flatten()
 | 
			
		||||
                    .mapNotNull {
 | 
			
		||||
                        when {
 | 
			
		||||
                            it !is ExtensionUiModel.Item -> null
 | 
			
		||||
                            it.extension !is Extension.Installed -> null
 | 
			
		||||
                            !it.extension.hasUpdate -> null
 | 
			
		||||
                            else -> it.extension
 | 
			
		||||
@@ -216,14 +209,16 @@ class ExtensionsScreenModel(
 | 
			
		||||
data class ExtensionsState(
 | 
			
		||||
    val isLoading: Boolean = true,
 | 
			
		||||
    val isRefreshing: Boolean = false,
 | 
			
		||||
    val items: List<ExtensionUiModel> = emptyList(),
 | 
			
		||||
    val items: ItemGroups = mutableMapOf(),
 | 
			
		||||
    val updates: Int = 0,
 | 
			
		||||
) {
 | 
			
		||||
    val isEmpty = items.isEmpty()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sealed interface ExtensionUiModel {
 | 
			
		||||
    sealed interface Header : ExtensionUiModel {
 | 
			
		||||
typealias ItemGroups = MutableMap<ExtensionUiModel.Header, List<ExtensionUiModel.Item>>
 | 
			
		||||
 | 
			
		||||
object ExtensionUiModel {
 | 
			
		||||
    sealed interface Header {
 | 
			
		||||
        data class Resource(@StringRes val textRes: Int) : Header
 | 
			
		||||
        data class Text(val text: String) : Header
 | 
			
		||||
    }
 | 
			
		||||
@@ -231,5 +226,5 @@ sealed interface ExtensionUiModel {
 | 
			
		||||
    data class Item(
 | 
			
		||||
        val extension: Extension,
 | 
			
		||||
        val installStep: InstallStep,
 | 
			
		||||
    ) : ExtensionUiModel
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user