Added deep link to multiplayer games (#6273)

This commit is contained in:
Leonard Günther
2022-03-05 19:00:56 +01:00
committed by GitHub
parent 1b9e496db5
commit 0e26ea2cc8
4 changed files with 87 additions and 33 deletions

View File

@ -29,9 +29,18 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true" >
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="https"
android:host="unciv.app"
android:path="/multiplayer"
/>
</intent-filter>
</activity> </activity>
<activity <activity
android:name="com.unciv.app.AndroidTvLauncher" android:name="com.unciv.app.AndroidTvLauncher"
android:label="@string/app_name" android:label="@string/app_name"

View File

@ -2,7 +2,7 @@ package com.unciv.app
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.os.Build import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.work.WorkManager import androidx.work.WorkManager
@ -16,19 +16,16 @@ import java.io.File
open class AndroidLauncher : AndroidApplication() { open class AndroidLauncher : AndroidApplication() {
private var customSaveLocationHelper: CustomSaveLocationHelperAndroid? = null private var customSaveLocationHelper: CustomSaveLocationHelperAndroid? = null
private var game: UncivGame? = null
private var deepLinkedMultiplayerGame: String? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { customSaveLocationHelper = CustomSaveLocationHelperAndroid(this)
customSaveLocationHelper = CustomSaveLocationHelperAndroid(this)
}
MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext) MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext)
// Only allow mods on KK+, to avoid READ_EXTERNAL_STORAGE permission earlier versions need copyMods()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { val externalfilesDir = getExternalFilesDir(null)
copyMods() if (externalfilesDir != null) GameSaver.externalFilesDirForAndroid = externalfilesDir.path
val externalfilesDir = getExternalFilesDir(null)
if (externalfilesDir != null) GameSaver.externalFilesDirForAndroid = externalfilesDir.path
}
// Manage orientation lock // Manage orientation lock
val limitOrientationsHelper = LimitOrientationsHelperAndroid(this) val limitOrientationsHelper = LimitOrientationsHelperAndroid(this)
@ -44,8 +41,18 @@ open class AndroidLauncher : AndroidApplication() {
customSaveLocationHelper = customSaveLocationHelper, customSaveLocationHelper = customSaveLocationHelper,
limitOrientationsHelper = limitOrientationsHelper limitOrientationsHelper = limitOrientationsHelper
) )
val game = UncivGame(androidParameters)
game = UncivGame(androidParameters)
initialize(game, config) initialize(game, config)
// This is also needed in onCreate to open links and notifications
// correctly even if the app was not running
if (intent.action == Intent.ACTION_VIEW) {
val uri: Uri? = intent.data
deepLinkedMultiplayerGame = uri?.getQueryParameter("id")
} else {
deepLinkedMultiplayerGame = null
}
} }
/** /**
@ -84,15 +91,30 @@ open class AndroidLauncher : AndroidApplication() {
} }
} catch (ex: Exception) { } catch (ex: Exception) {
} }
if (deepLinkedMultiplayerGame != null) {
game?.deepLinkedMultiplayerGame = deepLinkedMultiplayerGame;
deepLinkedMultiplayerGame = null
}
super.onResume() super.onResume()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onNewIntent(intent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { super.onNewIntent(intent)
// This should only happen on API 19+ but it's wrapped in the if check to keep the if (intent == null)
// compiler happy return
customSaveLocationHelper?.handleIntentData(requestCode, data?.data)
if (intent.action == Intent.ACTION_VIEW) {
val uri: Uri? = intent.data
deepLinkedMultiplayerGame = uri?.getQueryParameter("id")
} else {
deepLinkedMultiplayerGame = null
} }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
customSaveLocationHelper?.handleIntentData(requestCode, data?.data)
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
} }
} }

View File

@ -6,6 +6,7 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
@ -138,17 +139,18 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
} }
} }
fun notifyUserAboutTurn(applicationContext: Context, gameName: String) { fun notifyUserAboutTurn(applicationContext: Context, game: Pair<String, String>) {
val pendingIntent: PendingIntent = val intent = Intent(applicationContext, AndroidLauncher::class.java).apply {
Intent(applicationContext, AndroidLauncher::class.java).let { notificationIntent -> action = Intent.ACTION_VIEW
PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0) data = Uri.parse("https://unciv.app/multiplayer?id=${game.second}")
} }
val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, 0)
val contentTitle = applicationContext.resources.getString(R.string.Notify_YourTurn_Short) val contentTitle = applicationContext.resources.getString(R.string.Notify_YourTurn_Short)
val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO) val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO)
.setPriority(NotificationManagerCompat.IMPORTANCE_HIGH) // people are waiting! .setPriority(NotificationManagerCompat.IMPORTANCE_HIGH) // people are waiting!
.setContentTitle(contentTitle) .setContentTitle(contentTitle)
.setContentText(applicationContext.resources.getString(R.string.Notify_YourTurn_Long).replace("[gameName]", gameName)) .setContentText(applicationContext.resources.getString(R.string.Notify_YourTurn_Long).replace("[gameName]", game.first))
.setTicker(contentTitle) .setTicker(contentTitle)
// without at least vibrate, some Android versions don't show a heads-up notification // without at least vibrate, some Android versions don't show a heads-up notification
.setDefaults(DEFAULT_VIBRATE) .setDefaults(DEFAULT_VIBRATE)
@ -190,7 +192,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
// Of the turnNotification is OFF, this will be -1 since we never saved this game in the array // Of the turnNotification is OFF, this will be -1 since we never saved this game in the array
// Or possibly reading the preview file returned an exception // Or possibly reading the preview file returned an exception
if (gameIndex!=-1) if (gameIndex!=-1)
notifyUserAboutTurn(applicationContext, gameNames[gameIndex]) notifyUserAboutTurn(applicationContext, Pair(gameNames[gameIndex], gameIds[gameIndex]))
} else { } else {
val inputData = workDataOf(Pair(FAIL_COUNT, 0), Pair(GAME_ID, gameIds), Pair(GAME_NAME, gameNames), val inputData = workDataOf(Pair(FAIL_COUNT, 0), Pair(GAME_ID, gameIds), Pair(GAME_NAME, gameNames),
Pair(USER_ID, settings.userId), Pair(CONFIGURED_DELAY, settings.multiplayerTurnCheckerDelayInMinutes), Pair(USER_ID, settings.userId), Pair(CONFIGURED_DELAY, settings.multiplayerTurnCheckerDelayInMinutes),
@ -238,8 +240,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
val gameNames = inputData.getStringArray(GAME_NAME)!! val gameNames = inputData.getStringArray(GAME_NAME)!!
var arrayIndex = 0 var arrayIndex = 0
// We only want to notify the user or update persisted notification once but still want // We only want to notify the user or update persisted notification once but still want
// to download all games to update the files hence this bool // to download all games to update the files so we save the first one we find
var foundGame = "" var foundGame: Pair<String, String>? = null
for (gameId in gameIds){ for (gameId in gameIds){
//gameId could be an empty string if startTurnChecker fails to load all files //gameId could be an empty string if startTurnChecker fails to load all files
@ -260,10 +262,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
*/ */
GameSaver.saveGame(gamePreview, gameNames[arrayIndex]) GameSaver.saveGame(gamePreview, gameNames[arrayIndex])
if (currentTurnPlayer.playerId == inputData.getString(USER_ID)!! && foundGame.isEmpty()) { if (currentTurnPlayer.playerId == inputData.getString(USER_ID)!! && foundGame == null) {
// We only save the first found game as the player will go into the foundGame = Pair(gameNames[arrayIndex], gameIds[arrayIndex])
// multiplayer screen anyway to join the game and see the other ones
foundGame = gameNames[arrayIndex]
} }
arrayIndex++ arrayIndex++
} catch (ex: FileNotFoundException){ } catch (ex: FileNotFoundException){
@ -276,7 +276,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
} }
} }
if (foundGame.isNotEmpty()){ if (foundGame != null){
notifyUserAboutTurn(applicationContext, foundGame) notifyUserAboutTurn(applicationContext, foundGame)
with(NotificationManagerCompat.from(applicationContext)) { with(NotificationManagerCompat.from(applicationContext)) {
cancel(NOTIFICATION_ID_SERVICE) cancel(NOTIFICATION_ID_SERVICE)

View File

@ -19,9 +19,10 @@ import com.unciv.ui.audio.MusicMood
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.PlayerReadyScreen import com.unciv.ui.worldscreen.PlayerReadyScreen
import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.worldscreen.WorldScreen
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
import java.lang.Exception
import java.util.* import java.util.*
class UncivGame(parameters: UncivGameParameters) : Game() { class UncivGame(parameters: UncivGameParameters) : Game() {
// we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters // we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters
constructor(version: String) : this(UncivGameParameters(version, null)) constructor(version: String) : this(UncivGameParameters(version, null))
@ -34,6 +35,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
val customSaveLocationHelper = parameters.customSaveLocationHelper val customSaveLocationHelper = parameters.customSaveLocationHelper
val limitOrientationsHelper = parameters.limitOrientationsHelper val limitOrientationsHelper = parameters.limitOrientationsHelper
var deepLinkedMultiplayerGame: String? = null
lateinit var gameInfo: GameInfo lateinit var gameInfo: GameInfo
fun isGameInfoInitialized() = this::gameInfo.isInitialized fun isGameInfoInitialized() = this::gameInfo.isInitialized
lateinit var settings: GameSettings lateinit var settings: GameSettings
@ -121,7 +123,17 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
if (settings.isFreshlyCreated) { if (settings.isFreshlyCreated) {
setScreen(LanguagePickerScreen()) setScreen(LanguagePickerScreen())
} else { setScreen(MainMenuScreen()) } } else {
if (deepLinkedMultiplayerGame == null)
setScreen(MainMenuScreen())
else {
try {
loadGame(OnlineMultiplayer().tryDownloadGame(deepLinkedMultiplayerGame!!))
} catch (ex: Exception) {
setScreen(MainMenuScreen())
}
}
}
isInitialized = true isInitialized = true
} }
} }
@ -159,6 +171,17 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
super.resume() super.resume()
musicController.resume() musicController.resume()
if (!isInitialized) return // The stuff from Create() is still happening, so the main screen will load eventually if (!isInitialized) return // The stuff from Create() is still happening, so the main screen will load eventually
// This is also needed in resume to open links and notifications
// correctly when the app was already running. The handling in onCreate
// does not seem to be enough
if (deepLinkedMultiplayerGame != null) {
try {
loadGame(OnlineMultiplayer().tryDownloadGame(deepLinkedMultiplayerGame!!))
} catch (ex: Exception) {
setScreen(MainMenuScreen())
}
}
} }
override fun pause() { override fun pause() {