Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spruced up ModManagementScreen - phase 1 #3983

Merged
merged 2 commits into from
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions android/assets/jsons/translations/template.properties
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ Invalid ID! =

Mods =
Download [modName] =
Update [modName] =
Could not download mod list =
Download mod from URL =
Download =
Expand All @@ -948,6 +949,9 @@ Disable as permanent visual mod =
Installed =
Downloaded! =
Could not download mod =
Online query result is incomplete =
No description provided =
[stargazers]✯ =

# Uniques that are relevant to more than one type of game object

Expand Down
2 changes: 2 additions & 0 deletions core/src/com/unciv/models/ruleset/Ruleset.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class ModOptions {

var lastUpdated = ""
var modUrl = ""
var author = ""
var modSize = 0
}

class Ruleset {
Expand Down
447 changes: 345 additions & 102 deletions core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions core/src/com/unciv/ui/saves/LoadGameScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.unciv.logic.UncivShowableException
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.utils.*
import java.text.SimpleDateFormat
import com.unciv.ui.utils.UncivDateFormat.formatDate
import java.util.*
import java.util.concurrent.CancellationException
import kotlin.concurrent.thread
Expand Down Expand Up @@ -180,8 +180,7 @@ class LoadGameScreen(previousScreen:CameraStageBaseScreen) : PickerScreen(disabl


val savedAt = Date(save.lastModified())
var textToSet = save.name() +
"\n${"Saved at".tr()}: " + SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US).format(savedAt)
var textToSet = save.name() + "\n${"Saved at".tr()}: " + savedAt.formatDate()
thread { // Even loading the game to get its metadata can take a long time on older phones
try {
val game = GameSaver.loadGamePreviewFromFile(save)
Expand Down
30 changes: 28 additions & 2 deletions core/src/com/unciv/ui/utils/ExtensionFunctions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
import com.unciv.models.UncivSound
import com.unciv.models.translations.tr
import java.text.SimpleDateFormat
import java.util.*
import kotlin.concurrent.thread
import kotlin.random.Random

Expand Down Expand Up @@ -102,8 +104,7 @@ fun Table.addSeparator(): Cell<Image> {

fun Table.addSeparatorVertical(): Cell<Image> {
val image = ImageGetter.getWhiteDot()
val cell = add(image).width(2f).fillY()
return cell
return add(image).width(2f).fillY()
}

fun <T : Actor> Table.addCell(actor: T): Table {
Expand Down Expand Up @@ -200,3 +201,28 @@ fun <T> List<T>.randomWeighted(weights: List<Float>, random: Random = Random): T
}
return this.last()
}

/**
* Standardize date formatting so dates are presented in a consistent style and all decisions
* to change date handling are encapsulated here
*/
object UncivDateFormat {
private val standardFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)

/** Format a date to ISO format with minutes */
fun Date.formatDate(): String = standardFormat.format(this)
// Previously also used:
//val updateString = "{Updated}: " +DateFormat.getDateInstance(DateFormat.SHORT).format(date)

// Everything under java.time is from Java 8 onwards, meaning older phones that use Java 7 won't be able to handle it :/
// So we're forced to use ancient Java 6 classes instead of the newer and nicer LocalDateTime.parse :(
// Direct solution from https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date

@Suppress("SpellCheckingInspection")
private val utcFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

/** Parse an UTC date as passed by online API's
* example: `"2021-04-11T14:43:33Z".parseDate()`
*/
fun String.parseDate(): Date = utcFormat.parse(this)
}
129 changes: 5 additions & 124 deletions core/src/com/unciv/ui/worldscreen/mainmenu/DropBox.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package com.unciv.ui.worldscreen.mainmenu

import com.badlogic.gdx.files.FileHandle
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.ui.saves.Gzip
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.Charset
import java.util.zip.ZipEntry
import java.util.zip.ZipFile


object DropBox {
Expand All @@ -19,6 +16,7 @@ object DropBox {
with(URL(url).openConnection() as HttpURLConnection) {
requestMethod = "POST" // default is GET

@Suppress("SpellCheckingInspection")
setRequestProperty("Authorization", "Bearer LTdBbopPUQ0AAAAAAAACxh4_Qd1eVMM7IBK3ULV3BgxzWZDMfhmgFbuUNF_rXQWb")

if (dropboxApiArg != "") setRequestProperty("Dropbox-API-Arg", dropboxApiArg)
Expand Down Expand Up @@ -76,8 +74,7 @@ object DropBox {

fun downloadFileAsString(fileName: String): String {
val inputStream = downloadFile(fileName)
val text = BufferedReader(InputStreamReader(inputStream)).readText()
return text
return BufferedReader(InputStreamReader(inputStream)).readText()
}

fun uploadFile(fileName: String, data: String, overwrite: Boolean = false){
Expand All @@ -98,13 +95,14 @@ object DropBox {
// return BufferedReader(InputStreamReader(result)).readText()
// }


@Suppress("PropertyName")
class FolderList{
var entries = ArrayList<FolderListEntry>()
var cursor = ""
var has_more = false
}

@Suppress("PropertyName")
class FolderListEntry{
var name=""
var path_display=""
Expand All @@ -128,127 +126,10 @@ class OnlineMultiplayer {
/**
* WARNING!
* Does not initialize transitive GameInfo data.
* It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike tryDownloadGame().
* It is therefore stateless and safe to call for Multiplayer Turn Notifier, unlike tryDownloadGame().
*/
fun tryDownloadGameUninitialized(gameId: String): GameInfo {
val zippedGameInfo = DropBox.downloadFileAsString(getGameLocation(gameId))
return GameSaver.gameInfoFromStringWithoutTransients(Gzip.unzip(zippedGameInfo))
}
}

object Github {
// Consider merging this with the Dropbox function
fun download(url: String, action: (HttpURLConnection) -> Unit = {}): InputStream? {
with(URL(url).openConnection() as HttpURLConnection)
{
action(this)

try {
return inputStream
} catch (ex: Exception) {
println(ex.message)
val reader = BufferedReader(InputStreamReader(errorStream))
println(reader.readText())
return null
}
}
}

// This took a long time to get just right, so if you're changing this, TEST IT THOROUGHLY on both Desktop and Phone
fun downloadAndExtract(gitRepoUrl:String, defaultBranch:String, folderFileHandle:FileHandle): FileHandle? {
val zipUrl = "$gitRepoUrl/archive/$defaultBranch.zip"
val inputStream = download(zipUrl)
if (inputStream == null) return null

val tempZipFileHandle = folderFileHandle.child("tempZip.zip")
tempZipFileHandle.write(inputStream, false)
val unzipDestination = tempZipFileHandle.sibling("tempZip") // folder, not file
Zip.extractFolder(tempZipFileHandle, unzipDestination)
val innerFolder = unzipDestination.list().first() // tempZip/<repoName>-master/

val finalDestinationName = innerFolder.name().replace("-$defaultBranch", "").replace('-', ' ')
val finalDestination = folderFileHandle.child(finalDestinationName)
finalDestination.mkdirs() // If we don't create this as a directory, it will think this is a file and nothing will work.
for (innerFileOrFolder in innerFolder.list()) {
innerFileOrFolder.moveTo(finalDestination)
}

tempZipFileHandle.delete()
unzipDestination.deleteDirectory()

return finalDestination
}


fun tryGetGithubReposWithTopic(amountPerPage:Int, page:Int): RepoSearch? {
// Default per-page is 30 - when we get to above 100 mods, we'll need to start search-queries
val inputStream = download("https://api.github.com/search/repositories?q=topic:unciv-mod&per_page=$amountPerPage&page=$page")
if (inputStream == null) return null
return GameSaver.json().fromJson(RepoSearch::class.java, inputStream.bufferedReader().readText())
}

class RepoSearch {
var items = ArrayList<Repo>()
}

class Repo {
var name = ""
var description = ""
var stargazers_count = 0
var default_branch = ""
var html_url = ""
var updated_at = ""
}
}

object Zip {

// I went through a lot of similar answers that didn't work until I got to this gem by NeilMonday
// (with mild changes to fit the FileHandles)
// https://stackoverflow.com/questions/981578/how-to-unzip-files-recursively-in-java
fun extractFolder(zipFile: FileHandle, unzipDestination: FileHandle) {
println(zipFile)
val BUFFER = 2048
val file = zipFile.file()
val zip = ZipFile(file)
unzipDestination.mkdirs()
val zipFileEntries = zip.entries()

// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
val entry = zipFileEntries.nextElement() as ZipEntry
val currentEntry = entry.name
val destFile = unzipDestination.child(currentEntry)
val destinationParent = destFile.parent()

// create the parent directory structure if needed
destinationParent.mkdirs()
if (!entry.isDirectory) {
val inputStream = BufferedInputStream(zip
.getInputStream(entry))
var currentByte: Int
// establish buffer for writing file
val data = ByteArray(BUFFER)

// write the current file to disk
val fos = FileOutputStream(destFile.file())
val dest = BufferedOutputStream(fos,
BUFFER)

// read and write until last byte is encountered
while (inputStream.read(data, 0, BUFFER).also { currentByte = it } != -1) {
dest.write(data, 0, currentByte)
}
dest.flush()
dest.close()
inputStream.close()
}
if (currentEntry.endsWith(".zip")) {
// found a zip file, try to open
extractFolder(destFile, unzipDestination)
}
}
zip.close() // Needed so we can delete the zip file later
}
}
Loading