diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 1f7191dbf8..b97b5bf05e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -29,9 +29,18 @@ + + + + + + - = Build.VERSION_CODES.KITKAT) { - customSaveLocationHelper = CustomSaveLocationHelperAndroid(this) - } + customSaveLocationHelper = CustomSaveLocationHelperAndroid(this) MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext) - // Only allow mods on KK+, to avoid READ_EXTERNAL_STORAGE permission earlier versions need - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - copyMods() - val externalfilesDir = getExternalFilesDir(null) - if (externalfilesDir != null) GameSaver.externalFilesDirForAndroid = externalfilesDir.path - } + copyMods() + val externalfilesDir = getExternalFilesDir(null) + if (externalfilesDir != null) GameSaver.externalFilesDirForAndroid = externalfilesDir.path // Manage orientation lock val limitOrientationsHelper = LimitOrientationsHelperAndroid(this) @@ -44,8 +41,18 @@ open class AndroidLauncher : AndroidApplication() { customSaveLocationHelper = customSaveLocationHelper, limitOrientationsHelper = limitOrientationsHelper ) - val game = UncivGame(androidParameters) + + game = UncivGame(androidParameters) 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) { } + + if (deepLinkedMultiplayerGame != null) { + game?.deepLinkedMultiplayerGame = deepLinkedMultiplayerGame; + deepLinkedMultiplayerGame = null + } + super.onResume() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // This should only happen on API 19+ but it's wrapped in the if check to keep the - // compiler happy - customSaveLocationHelper?.handleIntentData(requestCode, data?.data) + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + if (intent == null) + return + + 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) } } diff --git a/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt b/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt index 0e0b28375a..2e2e29447a 100644 --- a/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt +++ b/android/src/com/unciv/app/MultiplayerTurnCheckWorker.kt @@ -6,6 +6,7 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.Color +import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE @@ -138,17 +139,18 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame } } - fun notifyUserAboutTurn(applicationContext: Context, gameName: String) { - val pendingIntent: PendingIntent = - Intent(applicationContext, AndroidLauncher::class.java).let { notificationIntent -> - PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0) - } + fun notifyUserAboutTurn(applicationContext: Context, game: Pair) { + val intent = Intent(applicationContext, AndroidLauncher::class.java).apply { + action = Intent.ACTION_VIEW + 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 notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO) .setPriority(NotificationManagerCompat.IMPORTANCE_HIGH) // people are waiting! .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) // without at least vibrate, some Android versions don't show a heads-up notification .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 // Or possibly reading the preview file returned an exception if (gameIndex!=-1) - notifyUserAboutTurn(applicationContext, gameNames[gameIndex]) + notifyUserAboutTurn(applicationContext, Pair(gameNames[gameIndex], gameIds[gameIndex])) } else { 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), @@ -238,8 +240,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame val gameNames = inputData.getStringArray(GAME_NAME)!! var arrayIndex = 0 // 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 - var foundGame = "" + // to download all games to update the files so we save the first one we find + var foundGame: Pair? = null for (gameId in gameIds){ //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]) - if (currentTurnPlayer.playerId == inputData.getString(USER_ID)!! && foundGame.isEmpty()) { - // We only save the first found game as the player will go into the - // multiplayer screen anyway to join the game and see the other ones - foundGame = gameNames[arrayIndex] + if (currentTurnPlayer.playerId == inputData.getString(USER_ID)!! && foundGame == null) { + foundGame = Pair(gameNames[arrayIndex], gameIds[arrayIndex]) } arrayIndex++ } catch (ex: FileNotFoundException){ @@ -276,7 +276,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame } } - if (foundGame.isNotEmpty()){ + if (foundGame != null){ notifyUserAboutTurn(applicationContext, foundGame) with(NotificationManagerCompat.from(applicationContext)) { cancel(NOTIFICATION_ID_SERVICE) diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index a1634f772a..9bd6e73f2c 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -19,9 +19,10 @@ import com.unciv.ui.audio.MusicMood import com.unciv.ui.utils.* import com.unciv.ui.worldscreen.PlayerReadyScreen import com.unciv.ui.worldscreen.WorldScreen +import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer +import java.lang.Exception import java.util.* - class UncivGame(parameters: UncivGameParameters) : Game() { // we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters constructor(version: String) : this(UncivGameParameters(version, null)) @@ -34,6 +35,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val customSaveLocationHelper = parameters.customSaveLocationHelper val limitOrientationsHelper = parameters.limitOrientationsHelper + var deepLinkedMultiplayerGame: String? = null lateinit var gameInfo: GameInfo fun isGameInfoInitialized() = this::gameInfo.isInitialized lateinit var settings: GameSettings @@ -121,7 +123,17 @@ class UncivGame(parameters: UncivGameParameters) : Game() { if (settings.isFreshlyCreated) { setScreen(LanguagePickerScreen()) - } else { setScreen(MainMenuScreen()) } + } else { + if (deepLinkedMultiplayerGame == null) + setScreen(MainMenuScreen()) + else { + try { + loadGame(OnlineMultiplayer().tryDownloadGame(deepLinkedMultiplayerGame!!)) + } catch (ex: Exception) { + setScreen(MainMenuScreen()) + } + } + } isInitialized = true } } @@ -159,6 +171,17 @@ class UncivGame(parameters: UncivGameParameters) : Game() { super.resume() musicController.resume() 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() {