Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@
</intent-filter>
<intent-filter>
<action android:name="app.gamenative.LAUNCH_GAME" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="gamenative" android:host="run" />
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
</intent-filter>

<!--Allows app window to be resized on Oculus Quest VR headsets-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum class AppOptionMenuType(@StringRes val title: Int) {
StorePage(R.string.option_open_store_page),
CreateShortcut(R.string.create_shortcut),
ExportFrontend(R.string.option_export_for_frontend),
CopyURI(R.string.option_copy_web_uri),
RunContainer(R.string.option_open_container),
EditContainer(R.string.option_edit_container),
ResetToDefaults(R.string.option_reset_to_defaults),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.gamenative.ui.screen.library.appscreen

import android.content.ClipData
import android.content.Context
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
Expand All @@ -20,6 +21,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.content.FileProvider
Expand Down Expand Up @@ -526,6 +529,29 @@ abstract class BaseAppScreen {
)
}

@Composable
protected open fun getCopyWebUriOption(
context: Context,
libraryItem: LibraryItem,
): AppMenuOption? {
val gameId = getGameId(libraryItem)
val gameSource = getGameSource(libraryItem)
val uri = "gamenative://run?appid=$gameId&gamesource=$gameSource"
val clipboardManager = LocalClipboard.current
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) { }
return AppMenuOption(
optionType = AppOptionMenuType.CopyURI,
onClick = {
scope.launch {
clipboardManager.setClipEntry(
ClipEntry(ClipData.newPlainText("GameNative URI", uri))
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
)
}
},
)
}

/**
* Get "Use known config" menu option. Subclasses can override to customize behavior
* or disable it entirely by returning null.
Expand Down Expand Up @@ -957,6 +983,7 @@ abstract class BaseAppScreen {
getResetContainerOption(context, libraryItem)?.let { menuOptions.add(it) }
getCreateShortcutOption(context, libraryItem)?.let { menuOptions.add(it) }
getExportContainerOption(context, libraryItem, exportFrontendLauncher)?.let { menuOptions.add(it) }
getCopyWebUriOption(context, libraryItem)?.let { menuOptions.add(it) }
}

// Always available options
Expand Down Expand Up @@ -1103,6 +1130,11 @@ abstract class BaseAppScreen {
},
)

// Export for web URI
val copyWebUri = {
val content = getGameId(libraryItem).toString()
}

var exportConfigRequested by remember(appId) {
mutableStateOf(shouldExportConfig(appId))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ private fun getIconForOption(type: AppOptionMenuType): ImageVector {
AppOptionMenuType.StorePage -> Icons.AutoMirrored.Filled.OpenInNew
AppOptionMenuType.CreateShortcut -> Icons.AutoMirrored.Filled.AddToHomeScreen
AppOptionMenuType.ExportFrontend -> Icons.Default.Share
AppOptionMenuType.CopyURI -> Icons.Default.Share
AppOptionMenuType.RunContainer -> Icons.Default.PlayArrow
AppOptionMenuType.EditContainer -> Icons.Default.Settings
AppOptionMenuType.ResetToDefaults -> Icons.Default.RestartAlt
Expand Down Expand Up @@ -368,6 +369,7 @@ private fun groupOptions(options: List<AppMenuOption>): Map<OptionCategory, List
AppOptionMenuType.RunContainer,
AppOptionMenuType.CreateShortcut,
AppOptionMenuType.ExportFrontend,
AppOptionMenuType.CopyURI,
-> quickActions.add(option)

// Game Management
Expand Down
32 changes: 25 additions & 7 deletions app/src/main/java/app/gamenative/utils/IntentLaunchManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ object IntentLaunchManager {
private const val EXTRA_GAME_SOURCE = "game_source"
private const val EXTRA_CONTAINER_CONFIG = "container_config"
private const val ACTION_LAUNCH_GAME = "app.gamenative.LAUNCH_GAME"
private const val ACTION_VIEW = "android.intent.action.VIEW"
private const val URI_SCHEME = "gamenative"
private const val URI_HOST = "run"
private const val MAX_CONFIG_JSON_SIZE = 50000 // 50KB limit to prevent memory exhaustion

data class LaunchRequest(
Expand All @@ -30,21 +33,36 @@ object IntentLaunchManager {
fun parseLaunchIntent(intent: Intent): LaunchRequest? {
Timber.d("[IntentLaunchManager]: Parsing intent: action=${intent.action}")

if (intent.action != ACTION_LAUNCH_GAME) {
Timber.d("[IntentLaunchManager]: Intent action '${intent.action}' doesn't match expected action '$ACTION_LAUNCH_GAME'")
if (intent.action != ACTION_LAUNCH_GAME && intent.action != ACTION_VIEW) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Timber.d("[IntentLaunchManager]: Intent action '${intent.action}' doesn't match expected action '$ACTION_LAUNCH_GAME' or '$ACTION_VIEW'")
return null
}
var gameId: Int? = 0
var gameSource: String? = ""

val gameId = intent.getIntExtra(EXTRA_APP_ID, -1)
Timber.d("[IntentLaunchManager]: Extracted app_id: $gameId from intent extras")
if (intent.action == ACTION_VIEW) {
if (intent.data?.scheme != URI_SCHEME || intent.data?.host != URI_HOST) {
Timber.d("[IntentLaunchManager]: URI '${intent.dataString}' doesn't match expected URI '$URI_SCHEME://$URI_HOST'")
return null
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
val data = intent.data
Timber.d(data?.getQueryParameterNames().toString())
gameId = data?.getQueryParameter("appid")?.toIntOrNull() ?:0
gameSource = data?.getQueryParameter("gamesource")
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
}

if (intent.action == ACTION_LAUNCH_GAME) {
gameId = intent.getIntExtra(EXTRA_APP_ID, -1)
Timber.d("[IntentLaunchManager]: Extracted app_id: $gameId from intent extras")
// Get Game Source for launch intent
gameSource = intent.getStringExtra(EXTRA_GAME_SOURCE)?.uppercase(java.util.Locale.ROOT)
}

if (gameId <= 0) {
if (gameId == null || gameId <= 0) {
Timber.w("[IntentLaunchManager]: Invalid or missing app_id in launch intent: $gameId")
return null
}

// Get Game Source for launch intent
var gameSource = intent.getStringExtra(EXTRA_GAME_SOURCE)?.uppercase(java.util.Locale.ROOT)
val isValidGameSource = GameSource.entries.any { it.name == gameSource }
if (!isValidGameSource) {
gameSource = GameSource.STEAM.name
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,7 @@
<!-- App option menu labels -->
<string name="option_open_store_page">Open store page</string>
<string name="option_export_for_frontend">Export for frontend</string>
<string name="option_copy_web_uri">Copy web URI</string>
<string name="option_open_container">Open container</string>
<string name="option_edit_container">Edit container</string>
<string name="option_reset_to_defaults">Reset container</string>
Expand Down