mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-15 02:09:21 +07:00
Added Multiplayer Turn Notification Service (#1947)
* Added Multiplayer Turn Notification Service https://github.com/yairm210/Unciv/issues/1680 * Minor update on Credits.md (#1949) # Nations - Fix typo on word "crescent" - Fix link that flew off on word "sword" * 3.5.14-patch1 * Edit startgame screen. (#1950) Co-authored-by: u-ndefine <41176671+u-ndefine@users.noreply.github.com> * Small improvements suggested in pull request review https://github.com/yairm210/Unciv/pull/1947 * Removed potential concurrency hazzards caused by access to non-final variables. Added option to turn off persistent notification (may be necessary on pre-Oreo phones.) Added suggested comments. https://github.com/yairm210/Unciv/pull/1947 * Fixed miscommited debug code. https://github.com/yairm210/Unciv/pull/1947 Co-authored-by: u-ndefine <41176671+u-ndefine@users.noreply.github.com> Co-authored-by: Yair Morgenstern <yairm210@hotmail.com> Co-authored-by: lishaoxia1985 <49801619+lishaoxia1985@users.noreply.github.com>
This commit is contained in:
@ -14,7 +14,8 @@
|
|||||||
android:theme="@style/GdxTheme" >
|
android:theme="@style/GdxTheme" >
|
||||||
<activity
|
<activity
|
||||||
android:name="com.unciv.app.AndroidLauncher"
|
android:name="com.unciv.app.AndroidLauncher"
|
||||||
android:label="@string/app_name"
|
android:launchMode="singleTask"
|
||||||
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="userLandscape"
|
android:screenOrientation="userLandscape"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -1734,3 +1734,16 @@ Haka War Dance = Haka-Kriegstanz
|
|||||||
-10% combat strength for adjacent enemy units = -10% Kampfstärke für angrenzende feindliche Einheiten
|
-10% combat strength for adjacent enemy units = -10% Kampfstärke für angrenzende feindliche Einheiten
|
||||||
Rejuvenation = Verjüngung
|
Rejuvenation = Verjüngung
|
||||||
All healing effects doubled = Alle Heilungseffekte verdoppelt
|
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
|
@ -1720,3 +1720,16 @@ Haka War Dance =
|
|||||||
-10% combat strength for adjacent enemy units =
|
-10% combat strength for adjacent enemy units =
|
||||||
Rejuvenation =
|
Rejuvenation =
|
||||||
All healing effects doubled =
|
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 =
|
@ -27,6 +27,11 @@ android {
|
|||||||
archivesBaseName = "Unciv"
|
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
|
// 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
|
// but couldn't create the debug keystore for some reason
|
||||||
|
|
||||||
@ -45,7 +50,7 @@ android {
|
|||||||
aaptOptions {
|
aaptOptions {
|
||||||
ignoreAssetsPattern "!SaveFiles:!fonts:!maps:!music:!mods"
|
ignoreAssetsPattern "!SaveFiles:!fonts:!maps:!music:!mods"
|
||||||
}
|
}
|
||||||
|
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
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'
|
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.
|
// sets up the Android Eclipse project, using the old Ant based build.
|
||||||
eclipse {
|
eclipse {
|
||||||
// need to specify Java source sets explicitly, SpringSource Gradle Eclipse plugin
|
// need to specify Java source sets explicitly, SpringSource Gradle Eclipse plugin
|
||||||
@ -132,8 +142,8 @@ eclipse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
classpath {
|
classpath {
|
||||||
plusConfigurations += [ project.configurations.compile ]
|
plusConfigurations += [ project.configurations.compile ]
|
||||||
containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES'
|
containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES'
|
||||||
}
|
}
|
||||||
|
|
||||||
project {
|
project {
|
||||||
@ -151,7 +161,7 @@ eclipse {
|
|||||||
idea {
|
idea {
|
||||||
module {
|
module {
|
||||||
sourceDirs += file("src")
|
sourceDirs += file("src")
|
||||||
scopes = [ COMPILE: [plus:[project.configurations.compile]]]
|
scopes = [ COMPILE: [plus:[project.configurations.compile]]]
|
||||||
|
|
||||||
iml {
|
iml {
|
||||||
withXml {
|
withXml {
|
||||||
|
@ -2,6 +2,8 @@ package com.unciv.app
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
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.AndroidApplication
|
||||||
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
@ -10,6 +12,7 @@ import java.io.File
|
|||||||
class AndroidLauncher : AndroidApplication() {
|
class AndroidLauncher : AndroidApplication() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
createNotificationChannels()
|
||||||
|
|
||||||
// Only allow mods on KK+, to avoid READ_EXTERNAL_STORAGE permission earlier versions need
|
// Only allow mods on KK+, to avoid READ_EXTERNAL_STORAGE permission earlier versions need
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
@ -23,6 +26,14 @@ class AndroidLauncher : AndroidApplication() {
|
|||||||
initialize(game, config)
|
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
|
* 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
|
* 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()
|
if (!externalModsDir.exists()) externalModsDir.mkdirs()
|
||||||
externalModsDir.copyRecursively(internalModsDir)
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
235
android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt
Normal file
235
android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt
Normal file
@ -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<MultiplayerTurnCheckWorker>()
|
||||||
|
.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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ class UncivGame(
|
|||||||
constructor(version: String) : this(version, null)
|
constructor(version: String) : this(version, null)
|
||||||
|
|
||||||
lateinit var gameInfo: GameInfo
|
lateinit var gameInfo: GameInfo
|
||||||
|
fun isGameInfoInitialized() = ::gameInfo.isInitialized
|
||||||
lateinit var settings : GameSettings
|
lateinit var settings : GameSettings
|
||||||
lateinit var crashController: CrashController
|
lateinit var crashController: CrashController
|
||||||
/**
|
/**
|
||||||
|
@ -26,6 +26,9 @@ class GameSettings {
|
|||||||
var nuclearWeaponEnabled = false
|
var nuclearWeaponEnabled = false
|
||||||
var continuousRendering = true
|
var continuousRendering = true
|
||||||
var userId = ""
|
var userId = ""
|
||||||
|
var multiplayerTurnCheckerEnabled = true
|
||||||
|
var multiplayerTurnCheckerPermanentNotificationEnabled = true
|
||||||
|
var multiplayerTurnCheckerDelayInMinutes = 5L
|
||||||
|
|
||||||
fun save(){
|
fun save(){
|
||||||
GameSaver().setGeneralSettings(this)
|
GameSaver().setGeneralSettings(this)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.unciv.ui.worldscreen.mainmenu
|
package com.unciv.ui.worldscreen.mainmenu
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Application
|
||||||
import com.badlogic.gdx.Gdx
|
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
|
||||||
@ -39,38 +40,38 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){
|
|||||||
innerTable.add("Display options".toLabel(fontSize = 24)).colspan(2).row()
|
innerTable.add("Display options".toLabel(fontSize = 24)).colspan(2).row()
|
||||||
|
|
||||||
innerTable.add("Show worked tiles".toLabel())
|
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
|
settings.showWorkedTiles= !settings.showWorkedTiles
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Show resources and improvements".toLabel())
|
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
|
settings.showResourcesAndImprovements = !settings.showResourcesAndImprovements
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
innerTable.add("Show tutorials".toLabel())
|
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
|
settings.showTutorials = !settings.showTutorials
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Show minimap".toLabel())
|
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
|
settings.showMinimap = !settings.showMinimap
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Show pixel units".toLabel())
|
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
|
settings.showPixelUnits = !settings.showPixelUnits
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Show pixel improvements".toLabel())
|
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
|
settings.showPixelImprovements = !settings.showPixelImprovements
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
@ -83,56 +84,76 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){
|
|||||||
|
|
||||||
// Do not add to template.properties yet please.
|
// Do not add to template.properties yet please.
|
||||||
innerTable.add("Continuous rendering\n(HIGHLY EXPERIMENTAL)".toLabel())
|
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
|
settings.continuousRendering = !settings.continuousRendering
|
||||||
Gdx.graphics.isContinuousRendering = settings.continuousRendering
|
Gdx.graphics.isContinuousRendering = settings.continuousRendering
|
||||||
update()
|
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())
|
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
|
settings.checkForDueUnits = !settings.checkForDueUnits
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Move units with a single tap".toLabel())
|
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
|
settings.singleTapMove = !settings.singleTapMove
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Auto-assign city production".toLabel())
|
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
|
settings.autoAssignCityProduction = !settings.autoAssignCityProduction
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
innerTable.add("Auto-build roads".toLabel())
|
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
|
settings.autoBuildingRoads = !settings.autoBuildingRoads
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
innerTable.add("Enable nuclear weapons".toLabel())
|
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
|
settings.nuclearWeaponEnabled = !settings.nuclearWeaponEnabled
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
addAutosaveTurnsSelectBox(innerTable)
|
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)
|
addSoundEffectsVolumeSlider(innerTable)
|
||||||
addMusicVolumeSlider(innerTable)
|
addMusicVolumeSlider(innerTable)
|
||||||
|
|
||||||
innerTable.add("Version".toLabel())
|
innerTable.add("Version".toLabel()).pad(10f)
|
||||||
innerTable.add(UncivGame.Current.version.toLabel()).row()
|
innerTable.add(UncivGame.Current.version.toLabel()).pad(10f).row()
|
||||||
|
|
||||||
|
|
||||||
val scrollPane = ScrollPane(innerTable, skin)
|
val scrollPane = ScrollPane(innerTable, skin)
|
||||||
@ -167,7 +188,7 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){
|
|||||||
Sounds.play(UncivSound.Click)
|
Sounds.play(UncivSound.Click)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
innerTable.add(soundEffectsVolumeSlider).row()
|
innerTable.add(soundEffectsVolumeSlider).pad(10f).row()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addMusicVolumeSlider(innerTable: Table) {
|
private fun addMusicVolumeSlider(innerTable: Table) {
|
||||||
@ -184,7 +205,7 @@ class WorldScreenOptionsPopup(val worldScreen:WorldScreen) : Popup(worldScreen){
|
|||||||
UncivGame.Current.music?.volume = 0.4f * musicVolumeSlider.value
|
UncivGame.Current.music?.volume = 0.4f * musicVolumeSlider.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
innerTable.add(musicVolumeSlider).row()
|
innerTable.add(musicVolumeSlider).pad(10f).row()
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
val downloadMusicButton = TextButton("Download music".tr(),CameraStageBaseScreen.skin)
|
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<Long>(skin)
|
||||||
|
val possibleDelaysArray = Array<Long>()
|
||||||
|
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) {
|
private fun addLanguageSelectBox(innerTable: Table) {
|
||||||
val languageSelectBox = SelectBox<Language>(skin)
|
val languageSelectBox = SelectBox<Language>(skin)
|
||||||
val languageArray = Array<Language>()
|
val languageArray = Array<Language>()
|
||||||
|
Reference in New Issue
Block a user