mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 07:18:57 +07:00
ModManagementScreen gets a loading indicator (#10909)
This commit is contained in:
@ -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) {
|
||||
|
Reference in New Issue
Block a user