ModManagementScreen gets a loading indicator (#10909)

This commit is contained in:
SomeTroglodyte
2024-01-15 17:55:36 +01:00
committed by GitHub
parent 0b5c2062b6
commit c829caf51a

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table
@ -14,6 +15,8 @@ import com.unciv.UncivGame
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.UncivShowableException
import com.unciv.logic.github.Github
import com.unciv.logic.github.Github.repoNameToFolderName
import com.unciv.logic.github.GithubAPI
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
@ -34,6 +37,7 @@ import com.unciv.ui.components.input.onActivation
import com.unciv.ui.components.input.onClick
import com.unciv.ui.components.widgets.AutoScrollPane
import com.unciv.ui.components.widgets.ExpanderTab
import com.unciv.ui.components.widgets.LoadingImage
import com.unciv.ui.components.widgets.WrappableLabel
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ConfirmPopup
@ -43,8 +47,6 @@ import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
import com.unciv.ui.screens.modmanager.ModManagementOptions.SortType
import com.unciv.logic.github.Github
import com.unciv.logic.github.Github.repoNameToFolderName
import com.unciv.ui.screens.pickerscreens.PickerScreen
import com.unciv.utils.Concurrency
import com.unciv.utils.Log
@ -81,6 +83,14 @@ class ModManagementScreen private constructor(
}
}
// Since we're `RecreateOnResize`, preserve the portrait/landscape mode for our lifetime
private val isPortrait: Boolean
// Will hold a LoadingImage until the online query is done, then it is freed/nulled
private var loading: LoadingImage? = null
// Holds the Cell in portrait mode which initially gets the loading image and later the options widget
private var optionsCell: Cell<Actor?>? = null
// Left column (in landscape, portrait stacks them within expanders)
private val installedModsTable = Table().apply { defaults().pad(10f) }
private val scrollInstalledMods = AutoScrollPane(installedModsTable)
@ -90,7 +100,7 @@ class ModManagementScreen private constructor(
// Right column
private val modActionTable = ModInfoAndActionPane()
private val scrollActionTable = AutoScrollPane(modActionTable)
// Factory for the Widget floating top right
// Manager providing the Widget floating top right in landscape mode, stacked expander in portrait
private val optionsManager = ModManagementOptions(this)
private var lastSelectedButton: ModDecoratedButton? = null
@ -112,6 +122,8 @@ class ModManagementScreen private constructor(
// cleanup - background processing needs to be stopped on exit and memory freed
private var runningSearchJob: Job? = null
// This is only set for cleanup, not when the user stops the query (by clicking the loading icon)
// Therefore, finding `runningSearchJob?.isActive == false && !stopBackgroundTasks` means stopped by user
private var stopBackgroundTasks = false
override fun dispose() {
@ -151,8 +163,10 @@ class ModManagementScreen private constructor(
labelWrapper.add(modDescriptionLabel).row()
labelScroll.actor = labelWrapper
if (isNarrowerThan4to3()) initPortrait()
isPortrait = isNarrowerThan4to3()
if (isPortrait) initPortrait()
else initLandscape()
showLoadingImage()
if (installedModInfo.isEmpty())
refreshInstalledModInfo()
@ -168,7 +182,8 @@ class ModManagementScreen private constructor(
private fun initPortrait() {
topTable.defaults().top().pad(0f)
topTable.add(optionsManager.expander).top().growX().row()
optionsCell = topTable.add().top().growX()
topTable.row()
installedExpanderTab = ExpanderTab(optionsManager.getInstalledHeader(), expanderWidth = stage.width) {
it.add(scrollInstalledMods).growX().maxHeight(stage.height / 2)
@ -213,19 +228,57 @@ class ModManagementScreen private constructor(
topTable.add(scrollActionTable)
topTable.add().expandX().row()
stage.addActor(optionsManager.expander)
optionsManager.expanderChangeEvent = {
optionsManager.expander.pack()
optionsManager.expander.setPosition(stage.width - 2f, stage.height - 2f, Align.topRight)
}
private fun showLoadingImage() {
val loadingStyle = LoadingImage.Style(circleColor = Color.DARK_GRAY, loadingColor = Color.FIREBRICK)
// Size should fit where the Search and Filter expander will go, which is 48f high and set topRight with 2f margin.
// When changing this, please also change the setPosition in landscape mode
val loading = LoadingImage(40f, loadingStyle)
this.loading = loading
if (isPortrait) {
optionsCell!!.pad(4f)
optionsCell!!.setActor(loading)
} else {// mute complaints - we _know_ it's not null
optionsManager.expander.remove()
loading.setPosition(stage.width - 6f, stage.height - 6f, Align.topRight)
stage.addActor(loading)
}
loading.show() // Now that it's on stage, start animation
// Allow clicking the loading icon to stop the query
loading.onClick {
if (runningSearchJob?.isActive != true) return@onClick
runningSearchJob?.cancel()
markOnlineQueryIncomplete()
replaceLoadingWithOptions()
}
}
private fun replaceLoadingWithOptions() {
val actorToRemove = loading ?: return
loading = null
actorToRemove.remove() // This is able to remove from a Cell or (the floating version) from the parent's children
actorToRemove.dispose()
if (isPortrait) {
optionsCell!!.pad(0f)
optionsCell!!.setActor(optionsManager.expander)
} else {
stage.addActor(optionsManager.expander)
optionsManager.expanderChangeEvent = {
optionsManager.expander.pack()
optionsManager.expander.setPosition(stage.width - 2f, stage.height - 2f, Align.topRight)
}
optionsManager.expanderChangeEvent?.invoke()
}
optionsManager.expanderChangeEvent?.invoke()
}
private fun reloadOnlineMods() {
onlineModsTable.clear()
onlineModInfo.clear()
onlineModsTable.add(getDownloadFromUrlButton()).padBottom(15f).row()
onlineModsTable.add("...".toLabel()).row()
tryDownloadPage(1)
}
@ -242,6 +295,7 @@ class ModManagementScreen private constructor(
Log.error("Could not download mod list", ex)
launchOnGLThread {
ToastPopup("Could not download mod list", this@ModManagementScreen)
replaceLoadingWithOptions()
}
runningSearchJob = null
return@run
@ -257,13 +311,6 @@ class ModManagementScreen private constructor(
}
private fun addModInfoFromRepoSearch(repoSearch: GithubAPI.RepoSearch, pageNum: Int) {
// clear and remove last cell if it is the "..." indicator
val lastCell = onlineModsTable.cells.lastOrNull()
if (lastCell != null && lastCell.actor is Label && (lastCell.actor as Label).text.toString() == "...") {
lastCell.setActor<Actor>(null)
onlineModsTable.cells.removeValue(lastCell, true)
}
for (repo in repoSearch.items) {
if (stopBackgroundTasks) return
repo.name = repo.name.repoNameToFolderName()
@ -315,15 +362,8 @@ class ModManagementScreen private constructor(
if (repoSearch.items.size < amountPerPage) {
// Check: It is also not impossible we missed a mod - just inform user
if (repoSearch.incomplete_results) {
val retryLabel = "Online query result is incomplete".toLabel(Color.RED)
retryLabel.touchable = Touchable.enabled
retryLabel.onClick { reloadOnlineMods() }
onlineModsTable.add(retryLabel)
markOnlineQueryIncomplete()
}
} else {
// the page was full so there may be more pages.
// indicate that search will be continued
onlineModsTable.add("...".toLabel()).row()
}
onlineModsTable.pack()
@ -334,6 +374,18 @@ class ModManagementScreen private constructor(
// continue search unless last page was reached
if (repoSearch.items.size >= amountPerPage && !stopBackgroundTasks)
tryDownloadPage(pageNum + 1)
else
replaceLoadingWithOptions()
}
private fun markOnlineQueryIncomplete() {
val retryLabel = "Online query result is incomplete".toLabel(Color.RED)
retryLabel.touchable = Touchable.enabled
retryLabel.onClick {
showLoadingImage()
reloadOnlineMods()
}
onlineModsTable.add(retryLabel)
}
private fun syncOnlineSelected(modName: String, button: ModDecoratedButton) {