diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 9b08e21acd..50f6cc6af7 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -14,7 +14,8 @@ android:theme="@style/GdxTheme" > diff --git a/android/assets/jsons/translationsByLanguage/German.properties b/android/assets/jsons/translationsByLanguage/German.properties index 04cd39a0ec..f6eef008f7 100644 --- a/android/assets/jsons/translationsByLanguage/German.properties +++ b/android/assets/jsons/translationsByLanguage/German.properties @@ -1734,3 +1734,16 @@ Haka War Dance = Haka-Kriegstanz -10% combat strength for adjacent enemy units = -10% Kampfstärke für angrenzende feindliche Einheiten Rejuvenation = Verjüngung All healing effects doubled = Alle Heilungseffekte verdoppelt +# Multiplayer Turn Checker Service +An error has occured = Ein Fehler ist aufgetreten +Multiplayer turn notifier service terminated = Multiplayer Zug Benachrichtigungsdienst wurde beendet +Your friends are waiting on your turn. = Deine Freunde warten auf deinen Zug. +Unciv - It's your turn! = Unciv - Du bist am Zug! +Unciv will inform you when it's your turn. = Unciv wird dich benachrichtigen wenn du am Zug bist. +Last checked: [lastTimeChecked]. Checks ca. every [checkPeriod] minute(s) when Internet available. = Zuletzt geprüft: [lastTimeChecked]. Prüft etwa alle [checkPeriod] Minute(n) wenn Internet vorhanden. +Configurable in Unciv options menu. = Konfigurierbar im Unciv Optionsmenü. +Unciv multiplayer turn notifier running = Unciv Multiplayer Zug Benachrichtiger läuft. +Multiplayer options = Multiplayer Einstellungen +Enable out-of-game turn notifications = Aktiviere Zug Benachrichtigungen außerhalb des Spiels +Time between turn checks out-of-game (in minutes) = Intervall zwischen Zug Prüfungen (in Minuten) +Show persistent notification for turn notifier service = Zeige persistente Benachrichtung für den Zug Benachrichtungsdienst \ No newline at end of file diff --git a/android/assets/jsons/translationsByLanguage/template.properties b/android/assets/jsons/translationsByLanguage/template.properties index 90ac1fe73e..5a0916b336 100644 --- a/android/assets/jsons/translationsByLanguage/template.properties +++ b/android/assets/jsons/translationsByLanguage/template.properties @@ -1720,3 +1720,16 @@ Haka War Dance = -10% combat strength for adjacent enemy units = Rejuvenation = All healing effects doubled = +# Multiplayer Turn Checker Service +An error has occured = +Multiplayer turn notifier service terminated = +Your friends are waiting on your turn. = +Unciv - It's your turn! = +Unciv will inform you when it's your turn. = +Last checked: [lastTimeChecked]. Checks ca. every [checkPeriod] minute(s) when Internet available. = +Configurable in Unciv options menu. = +Unciv multiplayer turn notifier running = +Multiplayer options = +Enable out-of-game turn notifications = +Time between turn checks out-of-game (in minutes) = +Show persistent notification for turn notifier service = \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 24b625e3ec..1ef8549d6c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -27,6 +27,11 @@ android { archivesBaseName = "Unciv" } + // necessary for Android Work lib + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + // Had to add this crap for Travis to build, it wanted to sign the app // but couldn't create the debug keystore for some reason @@ -45,7 +50,7 @@ android { aaptOptions { ignoreAssetsPattern "!SaveFiles:!fonts:!maps:!music:!mods" } - + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } @@ -116,6 +121,11 @@ task run(type: Exec) { commandLine "$adb", 'shell', 'am', 'start', '-n', 'com.unciv.app/AndroidLauncher' } +dependencies { + implementation 'androidx.core:core:1.2.0' + implementation "androidx.work:work-runtime-ktx:2.3.1" +} + // sets up the Android Eclipse project, using the old Ant based build. eclipse { // need to specify Java source sets explicitly, SpringSource Gradle Eclipse plugin @@ -132,8 +142,8 @@ eclipse { } classpath { - plusConfigurations += [ project.configurations.compile ] - containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES' + plusConfigurations += [ project.configurations.compile ] + containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES' } project { @@ -151,7 +161,7 @@ eclipse { idea { module { sourceDirs += file("src") - scopes = [ COMPILE: [plus:[project.configurations.compile]]] + scopes = [ COMPILE: [plus:[project.configurations.compile]]] iml { withXml { diff --git a/android/src/com/unciv/app/AndroidLauncher.kt b/android/src/com/unciv/app/AndroidLauncher.kt index a5f2292bd7..57367f098e 100644 --- a/android/src/com/unciv/app/AndroidLauncher.kt +++ b/android/src/com/unciv/app/AndroidLauncher.kt @@ -2,6 +2,8 @@ package com.unciv.app import android.os.Build import android.os.Bundle +import androidx.core.app.NotificationManagerCompat +import androidx.work.WorkManager import com.badlogic.gdx.backends.android.AndroidApplication import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration import com.unciv.UncivGame @@ -10,6 +12,7 @@ import java.io.File class AndroidLauncher : AndroidApplication() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + createNotificationChannels() // Only allow mods on KK+, to avoid READ_EXTERNAL_STORAGE permission earlier versions need if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -23,6 +26,14 @@ class AndroidLauncher : AndroidApplication() { initialize(game, config) } + /** + * Necessary for Multiplayer Turner Checker, starting with Android Oreo + */ + private fun createNotificationChannels() { + MultiplayerTurnCheckWorker.createNotificationChannelInfo(context) + MultiplayerTurnCheckWorker.createNotificationChannelService(context) + } + /** * Copies mods from external data directory (where users can access) to the private one (where * libGDX reads from). Note: deletes all files currently in the private mod directory and @@ -43,4 +54,22 @@ class AndroidLauncher : AndroidApplication() { if (!externalModsDir.exists()) externalModsDir.mkdirs() externalModsDir.copyRecursively(internalModsDir) } + + override fun onPause() { + if (UncivGame.Current.settings.multiplayerTurnCheckerEnabled + && UncivGame.Current.isGameInfoInitialized() + && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer) { + MultiplayerTurnCheckWorker.startTurnChecker(applicationContext, UncivGame.Current.gameInfo, UncivGame.Current.settings) + } + super.onPause() + } + + override fun onResume() { + WorkManager.getInstance(applicationContext).cancelAllWorkByTag(MultiplayerTurnCheckWorker.WORK_TAG) + with(NotificationManagerCompat.from(this)) { + cancel(MultiplayerTurnCheckWorker.NOTIFICATION_ID_INFO) + cancel(MultiplayerTurnCheckWorker.NOTIFICATION_ID_SERVICE) + } + super.onResume() + } } \ No newline at end of file diff --git a/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt b/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt new file mode 100644 index 0000000000..6b08782590 --- /dev/null +++ b/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt @@ -0,0 +1,235 @@ +package com.unciv.app + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE +import androidx.core.app.NotificationManagerCompat +import androidx.work.* +import com.badlogic.gdx.backends.android.AndroidApplication +import com.unciv.logic.GameInfo +import com.unciv.models.metadata.GameSettings +import com.unciv.models.translations.tr +import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer +import java.util.* +import java.util.concurrent.TimeUnit + + +class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParameters) + : Worker(appContext, workerParams) { + + companion object { + const val WORK_TAG = "UNCIV_MULTIPLAYER_TURN_CHECKER_WORKER" + const val NOTIFICATION_ID_SERVICE = 1 + const val NOTIFICATION_ID_INFO = 2 + private const val NOTIFICATION_CHANNEL_ID_INFO = "UNCIV_NOTIFICATION_CHANNEL_INFO" + private const val NOTIFICATION_CHANNEL_ID_SERVICE = "UNCIV_NOTIFICATION_CHANNEL_SERVICE" + + @Volatile private var failCount = 0 + @Volatile private var gameId = "" + @Volatile private var userId = "" + @Volatile private var configuredDelay = 5L + @Volatile private var persistentNotificationEnabled = true + + fun enqueue(appContext: Context, delayInMinutes: Long) { + val constraints = Constraints.Builder() + // If no internet is available, worker waits before becoming active. + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val checkTurnWork = OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setInitialDelay(delayInMinutes, TimeUnit.MINUTES) + .addTag(WORK_TAG) + .build() + + WorkManager.getInstance(appContext).enqueue(checkTurnWork) + } + + /** + * Notification Channel for 'It's your turn' and error notifications. + * + * This code is necessary for API level >= 26 + * API level < 26 does not support Notification Channels + * For more infos: https://developer.android.com/training/notify-user/channels.html#CreateChannel + */ + fun createNotificationChannelInfo(appContext: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val name = "Unciv Multiplayer Turn Checker Alert" + val descriptionText = "Informs you when it's your turn in multiplayer." + val importance = NotificationManager.IMPORTANCE_HIGH + val mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID_INFO, name, importance) + mChannel.description = descriptionText + mChannel.setShowBadge(true) + mChannel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC) + + val notificationManager = appContext.getSystemService(AndroidApplication.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(mChannel) + } + } + + /** + * Notification Channel for persistent service notification. + * + * This code is necessary for API level >= 26 + * API level < 26 does not support Notification Channels + * For more infos: https://developer.android.com/training/notify-user/channels.html#CreateChannel + */ + fun createNotificationChannelService(appContext: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val name = "Unciv Multiplayer Turn Checker Persistent Status" + val descriptionText = "Shown constantly to inform you about background checking." + val importance = NotificationManager.IMPORTANCE_MIN + val mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID_SERVICE, name, importance) + mChannel.description = descriptionText + + val notificationManager = appContext.getSystemService(AndroidApplication.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(mChannel) + } + } + + /** + * The persistent notification is purely for informational reasons. + * It is not technically necessary for the Worker, since it is not a Service. + */ + fun showPersistentNotification(appContext: Context, lastTimeChecked: String, checkPeriod: String) { + if (persistentNotificationEnabled) { + val pendingIntent: PendingIntent = + Intent(appContext, AndroidLauncher::class.java).let { notificationIntent -> + PendingIntent.getActivity(appContext, 0, notificationIntent, 0) + } + + val notification: NotificationCompat.Builder = NotificationCompat.Builder(appContext, NOTIFICATION_CHANNEL_ID_SERVICE) + .setPriority(NotificationManagerCompat.IMPORTANCE_MIN) // it's only a status + .setContentTitle("Unciv multiplayer turn notifier running".tr()) + .setStyle(NotificationCompat.BigTextStyle() + .bigText("Unciv will inform you when it's your turn.".tr() + "\n" + + "Last checked: [$lastTimeChecked]. Checks ca. every [$checkPeriod] minute(s) when Internet available.".tr() + + " " + "Configurable in Unciv options menu.".tr())) + .setSmallIcon(R.drawable.uncivicon2) + .setContentIntent(pendingIntent) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setOnlyAlertOnce(true) + .setOngoing(true) + + with(NotificationManagerCompat.from(appContext)) { + notify(NOTIFICATION_ID_INFO, notification.build()) + } + } + } + + fun notifyUserAboutTurn(applicationContext: Context) { + val pendingIntent: PendingIntent = + Intent(applicationContext, AndroidLauncher::class.java).let { notificationIntent -> + PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0) + } + + val contentTitle = "Unciv - It's your turn!".tr() + val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO) + .setPriority(NotificationManagerCompat.IMPORTANCE_HIGH) // people are waiting! + .setContentTitle(contentTitle) + .setContentText("Your friends are waiting on your turn.".tr()) + .setTicker(contentTitle) + // without at least vibrate, some Android versions don't show a heads-up notification + .setDefaults(DEFAULT_VIBRATE) + .setLights(Color.YELLOW, 300, 100) + .setSmallIcon(R.drawable.uncivicon2) + .setContentIntent(pendingIntent) + .setCategory(NotificationCompat.CATEGORY_SOCIAL) + .setOngoing(false) + + with(NotificationManagerCompat.from(applicationContext)) { + notify(NOTIFICATION_ID_INFO, notification.build()) + } + } + + fun startTurnChecker(applicationContext: Context, gameInfo: GameInfo, settings: GameSettings) { + if (gameInfo.currentPlayerCiv.playerId == settings.userId) { + // May be useful to remind a player that he forgot to complete his turn. + notifyUserAboutTurn(applicationContext) + } else { + gameId = gameInfo.gameId + userId = settings.userId + configuredDelay = settings.multiplayerTurnCheckerDelayInMinutes + persistentNotificationEnabled = settings.multiplayerTurnCheckerPermanentNotificationEnabled + + showPersistentNotification(applicationContext, + "—", settings.multiplayerTurnCheckerDelayInMinutes.toString()) + // Initial check always happens after a minute, ignoring delay config. Better user experience this way. + enqueue(applicationContext, 1) + } + } + } + + override fun doWork(): Result { + try { + val latestGame = OnlineMultiplayer().tryDownloadGame(gameId) + if (latestGame.currentPlayerCiv.playerId == userId) { + notifyUserAboutTurn(applicationContext) + with(NotificationManagerCompat.from(applicationContext)) { + cancel(NOTIFICATION_ID_SERVICE) + } + } else { + enqueue(applicationContext, configuredDelay) + updatePersistentNotification() + } + failCount = 0 + } catch (ex: Exception) { + if (failCount++ > 3) { + showErrorNotification() + with(NotificationManagerCompat.from(applicationContext)) { + cancel(NOTIFICATION_ID_SERVICE) + } + return Result.failure() + } else { + // If check fails, retry in one minute. + // Makes sense, since checks only happen if Internet is available in principle. + // Therefore a failure means either a problem with the GameInfo or with Dropbox. + enqueue(applicationContext, 1) + updatePersistentNotification() + } + } + return Result.success() + } + + private fun updatePersistentNotification() { + val cal = GregorianCalendar.getInstance() + val hour = cal.get(GregorianCalendar.HOUR_OF_DAY).toString() + var minute = cal.get(GregorianCalendar.MINUTE).toString() + if (minute.length == 1) { + minute = "0$minute" + } + val displayTime = "$hour:$minute" + + showPersistentNotification(applicationContext, displayTime, + configuredDelay.toString()) + } + + private fun showErrorNotification() { + val pendingIntent: PendingIntent = + Intent(applicationContext, AndroidLauncher::class.java).let { notificationIntent -> + PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0) + } + + val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO) + .setPriority(NotificationManagerCompat.IMPORTANCE_DEFAULT) // No direct user action expected + .setContentTitle("An error has occured".tr()) + .setContentText("Multiplayer turn notifier service terminated".tr()) + .setSmallIcon(R.drawable.uncivicon2) + // without at least vibrate, some Android versions don't show a heads-up notification + .setDefaults(DEFAULT_VIBRATE) + .setLights(Color.YELLOW, 300, 100) + .setContentIntent(pendingIntent) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .setOngoing(false) + + with(NotificationManagerCompat.from(applicationContext)) { + notify(NOTIFICATION_ID_INFO, notification.build()) + } + } +} \ No newline at end of file diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index b5cacbeca1..23370caf61 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -31,6 +31,7 @@ class UncivGame( constructor(version: String) : this(version, null) lateinit var gameInfo: GameInfo + fun isGameInfoInitialized() = ::gameInfo.isInitialized lateinit var settings : GameSettings lateinit var crashController: CrashController /** diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 295c1af8a1..ab63acbc02 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -26,6 +26,9 @@ class GameSettings { var nuclearWeaponEnabled = false var continuousRendering = true var userId = "" + var multiplayerTurnCheckerEnabled = true + var multiplayerTurnCheckerPermanentNotificationEnabled = true + var multiplayerTurnCheckerDelayInMinutes = 5L fun save(){ GameSaver().setGeneralSettings(this) diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt index 39a1b4fc4e..cd5ea64795 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/WorldScreenOptionsPopup.kt @@ -1,5 +1,6 @@ package com.unciv.ui.worldscreen.mainmenu +import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor @@ -39,38 +40,38 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){ innerTable.add("Display options".toLabel(fontSize = 24)).colspan(2).row() innerTable.add("Show worked tiles".toLabel()) - addButton(innerTable, if (settings.showWorkedTiles) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.showWorkedTiles) "Yes" else "No") { settings.showWorkedTiles= !settings.showWorkedTiles update() } innerTable.add("Show resources and improvements".toLabel()) - addButton(innerTable, if (settings.showResourcesAndImprovements) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.showResourcesAndImprovements) "Yes" else "No") { settings.showResourcesAndImprovements = !settings.showResourcesAndImprovements update() } innerTable.add("Show tutorials".toLabel()) - addButton(innerTable, if (settings.showTutorials) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.showTutorials) "Yes" else "No") { settings.showTutorials = !settings.showTutorials update() } innerTable.add("Show minimap".toLabel()) - addButton(innerTable, if (settings.showMinimap) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.showMinimap) "Yes" else "No") { settings.showMinimap = !settings.showMinimap update() } innerTable.add("Show pixel units".toLabel()) - addButton(innerTable, if (settings.showPixelUnits) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.showPixelUnits) "Yes" else "No") { settings.showPixelUnits = !settings.showPixelUnits update() } innerTable.add("Show pixel improvements".toLabel()) - addButton(innerTable, if (settings.showPixelImprovements) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.showPixelImprovements) "Yes" else "No") { settings.showPixelImprovements = !settings.showPixelImprovements update() } @@ -83,56 +84,76 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){ // Do not add to template.properties yet please. innerTable.add("Continuous rendering\n(HIGHLY EXPERIMENTAL)".toLabel()) - addButton(innerTable, if (settings.continuousRendering) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.continuousRendering) "Yes" else "No") { settings.continuousRendering = !settings.continuousRendering Gdx.graphics.isContinuousRendering = settings.continuousRendering update() } - innerTable.add("Gameplay options".toLabel(fontSize = 24)).colspan(2).row() + innerTable.add("Gameplay options".toLabel(fontSize = 24)).colspan(2).padTop(20f).row() innerTable.add("Check for idle units".toLabel()) - addButton(innerTable, if (settings.checkForDueUnits) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.checkForDueUnits) "Yes" else "No") { settings.checkForDueUnits = !settings.checkForDueUnits update() } innerTable.add("Move units with a single tap".toLabel()) - addButton(innerTable, if (settings.singleTapMove) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.singleTapMove) "Yes" else "No") { settings.singleTapMove = !settings.singleTapMove update() } innerTable.add("Auto-assign city production".toLabel()) - addButton(innerTable, if (settings.autoAssignCityProduction) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.autoAssignCityProduction) "Yes" else "No") { settings.autoAssignCityProduction = !settings.autoAssignCityProduction update() } innerTable.add("Auto-build roads".toLabel()) - addButton(innerTable, if (settings.autoBuildingRoads) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.autoBuildingRoads) "Yes" else "No") { settings.autoBuildingRoads = !settings.autoBuildingRoads update() } innerTable.add("Enable nuclear weapons".toLabel()) - addButton(innerTable, if (settings.nuclearWeaponEnabled) "Yes".tr() else "No".tr()) { + addButton(innerTable, if (settings.nuclearWeaponEnabled) "Yes" else "No") { settings.nuclearWeaponEnabled = !settings.nuclearWeaponEnabled update() } addAutosaveTurnsSelectBox(innerTable) - innerTable.add("Other options".toLabel(fontSize = 24)).colspan(2).row() + // at the moment the notification service only exists on Android + if (Gdx.app.type == Application.ApplicationType.Android) { + innerTable.add("Multiplayer options".toLabel(fontSize = 24)).colspan(2).padTop(20f).row() + + innerTable.add("Enable out-of-game turn notifications".toLabel()) + addButton(innerTable, if (settings.multiplayerTurnCheckerEnabled) "Yes" else "No") { + settings.multiplayerTurnCheckerEnabled = !settings.multiplayerTurnCheckerEnabled + update() + } + if (settings.multiplayerTurnCheckerEnabled) { + addMultiplayerTurnCheckerDelayBox(innerTable) + + innerTable.add("Show persistent notification for turn notifier service".toLabel()) + addButton(innerTable, if (settings.multiplayerTurnCheckerPermanentNotificationEnabled) "Yes" else "No") { + settings.multiplayerTurnCheckerPermanentNotificationEnabled = !settings.multiplayerTurnCheckerPermanentNotificationEnabled + update() + } + } + } + + innerTable.add("Other options".toLabel(fontSize = 24)).colspan(2).padTop(20f).row() addSoundEffectsVolumeSlider(innerTable) addMusicVolumeSlider(innerTable) - innerTable.add("Version".toLabel()) - innerTable.add(UncivGame.Current.version.toLabel()).row() + innerTable.add("Version".toLabel()).pad(10f) + innerTable.add(UncivGame.Current.version.toLabel()).pad(10f).row() val scrollPane = ScrollPane(innerTable, skin) @@ -167,7 +188,7 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){ Sounds.play(UncivSound.Click) } }) - innerTable.add(soundEffectsVolumeSlider).row() + innerTable.add(soundEffectsVolumeSlider).pad(10f).row() } private fun addMusicVolumeSlider(innerTable: Table) { @@ -184,7 +205,7 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){ UncivGame.Current.music?.volume = 0.4f * musicVolumeSlider.value } }) - innerTable.add(musicVolumeSlider).row() + innerTable.add(musicVolumeSlider).pad(10f).row() } else{ val downloadMusicButton = TextButton("Download music".tr(),CameraStageBaseScreen.skin) @@ -276,6 +297,26 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){ }) } + private fun addMultiplayerTurnCheckerDelayBox(innerTable: Table) { + innerTable.add("Time between turn checks out-of-game (in minutes)".toLabel()) + + val checkDelaySelectBox = SelectBox(skin) + val possibleDelaysArray = Array() + possibleDelaysArray.addAll(1L, 2L, 5L, 15L) + checkDelaySelectBox.items = possibleDelaysArray + checkDelaySelectBox.selected = UncivGame.Current.settings.multiplayerTurnCheckerDelayInMinutes + + innerTable.add(checkDelaySelectBox).pad(10f).row() + + checkDelaySelectBox.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + UncivGame.Current.settings.multiplayerTurnCheckerDelayInMinutes = checkDelaySelectBox.selected + UncivGame.Current.settings.save() + update() + } + }) + } + private fun addLanguageSelectBox(innerTable: Table) { val languageSelectBox = SelectBox(skin) val languageArray = Array()