mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-10 15:59:33 +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.graphics.Color
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
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.Label
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
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.fromJsonFile
|
||||||
import com.unciv.json.json
|
import com.unciv.json.json
|
||||||
import com.unciv.logic.UncivShowableException
|
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.logic.github.GithubAPI
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
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.input.onClick
|
||||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||||
import com.unciv.ui.components.widgets.ExpanderTab
|
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.components.widgets.WrappableLabel
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.popups.ConfirmPopup
|
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.basescreen.RecreateOnResize
|
||||||
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
|
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
|
||||||
import com.unciv.ui.screens.modmanager.ModManagementOptions.SortType
|
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.ui.screens.pickerscreens.PickerScreen
|
||||||
import com.unciv.utils.Concurrency
|
import com.unciv.utils.Concurrency
|
||||||
import com.unciv.utils.Log
|
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)
|
// Left column (in landscape, portrait stacks them within expanders)
|
||||||
private val installedModsTable = Table().apply { defaults().pad(10f) }
|
private val installedModsTable = Table().apply { defaults().pad(10f) }
|
||||||
private val scrollInstalledMods = AutoScrollPane(installedModsTable)
|
private val scrollInstalledMods = AutoScrollPane(installedModsTable)
|
||||||
@ -90,7 +100,7 @@ class ModManagementScreen private constructor(
|
|||||||
// Right column
|
// Right column
|
||||||
private val modActionTable = ModInfoAndActionPane()
|
private val modActionTable = ModInfoAndActionPane()
|
||||||
private val scrollActionTable = AutoScrollPane(modActionTable)
|
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 val optionsManager = ModManagementOptions(this)
|
||||||
|
|
||||||
private var lastSelectedButton: ModDecoratedButton? = null
|
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
|
// cleanup - background processing needs to be stopped on exit and memory freed
|
||||||
private var runningSearchJob: Job? = null
|
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
|
private var stopBackgroundTasks = false
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
@ -151,8 +163,10 @@ class ModManagementScreen private constructor(
|
|||||||
labelWrapper.add(modDescriptionLabel).row()
|
labelWrapper.add(modDescriptionLabel).row()
|
||||||
labelScroll.actor = labelWrapper
|
labelScroll.actor = labelWrapper
|
||||||
|
|
||||||
if (isNarrowerThan4to3()) initPortrait()
|
isPortrait = isNarrowerThan4to3()
|
||||||
|
if (isPortrait) initPortrait()
|
||||||
else initLandscape()
|
else initLandscape()
|
||||||
|
showLoadingImage()
|
||||||
|
|
||||||
if (installedModInfo.isEmpty())
|
if (installedModInfo.isEmpty())
|
||||||
refreshInstalledModInfo()
|
refreshInstalledModInfo()
|
||||||
@ -168,7 +182,8 @@ class ModManagementScreen private constructor(
|
|||||||
private fun initPortrait() {
|
private fun initPortrait() {
|
||||||
topTable.defaults().top().pad(0f)
|
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) {
|
installedExpanderTab = ExpanderTab(optionsManager.getInstalledHeader(), expanderWidth = stage.width) {
|
||||||
it.add(scrollInstalledMods).growX().maxHeight(stage.height / 2)
|
it.add(scrollInstalledMods).growX().maxHeight(stage.height / 2)
|
||||||
@ -213,19 +228,57 @@ class ModManagementScreen private constructor(
|
|||||||
topTable.add(scrollActionTable)
|
topTable.add(scrollActionTable)
|
||||||
topTable.add().expandX().row()
|
topTable.add().expandX().row()
|
||||||
|
|
||||||
stage.addActor(optionsManager.expander)
|
}
|
||||||
optionsManager.expanderChangeEvent = {
|
|
||||||
optionsManager.expander.pack()
|
private fun showLoadingImage() {
|
||||||
optionsManager.expander.setPosition(stage.width - 2f, stage.height - 2f, Align.topRight)
|
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() {
|
private fun reloadOnlineMods() {
|
||||||
onlineModsTable.clear()
|
onlineModsTable.clear()
|
||||||
onlineModInfo.clear()
|
onlineModInfo.clear()
|
||||||
onlineModsTable.add(getDownloadFromUrlButton()).padBottom(15f).row()
|
onlineModsTable.add(getDownloadFromUrlButton()).padBottom(15f).row()
|
||||||
onlineModsTable.add("...".toLabel()).row()
|
|
||||||
tryDownloadPage(1)
|
tryDownloadPage(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,6 +295,7 @@ class ModManagementScreen private constructor(
|
|||||||
Log.error("Could not download mod list", ex)
|
Log.error("Could not download mod list", ex)
|
||||||
launchOnGLThread {
|
launchOnGLThread {
|
||||||
ToastPopup("Could not download mod list", this@ModManagementScreen)
|
ToastPopup("Could not download mod list", this@ModManagementScreen)
|
||||||
|
replaceLoadingWithOptions()
|
||||||
}
|
}
|
||||||
runningSearchJob = null
|
runningSearchJob = null
|
||||||
return@run
|
return@run
|
||||||
@ -257,13 +311,6 @@ class ModManagementScreen private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addModInfoFromRepoSearch(repoSearch: GithubAPI.RepoSearch, pageNum: Int) {
|
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) {
|
for (repo in repoSearch.items) {
|
||||||
if (stopBackgroundTasks) return
|
if (stopBackgroundTasks) return
|
||||||
repo.name = repo.name.repoNameToFolderName()
|
repo.name = repo.name.repoNameToFolderName()
|
||||||
@ -315,15 +362,8 @@ class ModManagementScreen private constructor(
|
|||||||
if (repoSearch.items.size < amountPerPage) {
|
if (repoSearch.items.size < amountPerPage) {
|
||||||
// Check: It is also not impossible we missed a mod - just inform user
|
// Check: It is also not impossible we missed a mod - just inform user
|
||||||
if (repoSearch.incomplete_results) {
|
if (repoSearch.incomplete_results) {
|
||||||
val retryLabel = "Online query result is incomplete".toLabel(Color.RED)
|
markOnlineQueryIncomplete()
|
||||||
retryLabel.touchable = Touchable.enabled
|
|
||||||
retryLabel.onClick { reloadOnlineMods() }
|
|
||||||
onlineModsTable.add(retryLabel)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// the page was full so there may be more pages.
|
|
||||||
// indicate that search will be continued
|
|
||||||
onlineModsTable.add("...".toLabel()).row()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onlineModsTable.pack()
|
onlineModsTable.pack()
|
||||||
@ -334,6 +374,18 @@ class ModManagementScreen private constructor(
|
|||||||
// continue search unless last page was reached
|
// continue search unless last page was reached
|
||||||
if (repoSearch.items.size >= amountPerPage && !stopBackgroundTasks)
|
if (repoSearch.items.size >= amountPerPage && !stopBackgroundTasks)
|
||||||
tryDownloadPage(pageNum + 1)
|
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) {
|
private fun syncOnlineSelected(modName: String, button: ModDecoratedButton) {
|
||||||
|
Reference in New Issue
Block a user