Skip to content

Conversation

@Phr3d13
Copy link
Contributor

@Phr3d13 Phr3d13 commented Nov 5, 2025

As the title says.

Summary by CodeRabbit

  • New Features
    • Import container configurations from file or share code via new dialogs
    • Export current configuration to a JSON file for backup
    • Share configurations via generated deep links, web links, or share codes
    • Handle configuration imports from custom-scheme links and HTTPS URLs
    • New top-bar actions: Import (From File / From Share Code), Export, Share, Save

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds deep-link support and import/export/share of container configurations: manifest intent-filters, a new ContainerConfigIO utility for serialization/links/share-codes, MainActivity early deep-link import emitting a new AndroidEvent.ConfigImported, UI changes for file/share import/export, and a versionCode bump.

Changes

Cohort / File(s) Summary
Manifest Deep-Link Declarations
app/src/main/AndroidManifest.xml
Added two intent-filters to MainActivity: custom-scheme deep link (gamenative://config) and HTTPS deep link (https://gamenative.app/config) (autoVerify placeholder present).
MainActivity deep-link handling
app/src/main/java/app/gamenative/MainActivity.kt
Early deep-link processing in handleLaunchIntent() using ContainerConfigIO.importFromDeepLink(uri); on success emits AndroidEvent.ConfigImported and returns before existing launch-intent handling.
New AndroidEvent type
app/src/main/java/app/gamenative/events/AndroidEvent.kt
Added data class ConfigImported(val containerData: ContainerData) and import for ContainerData.
Import / Export / Share UI
app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt
Added file pickers (GetContent/CreateDocument), activity-result launchers, UI state for import/export/share/share-code, export/share flows using temp container serialization, top-bar action buttons and related toasts/dialogs.
Container config IO utility
app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt
New object ContainerConfigIO with public APIs for export/import (file & JSON), share-code creation/parsing (base64+gzip), deep-link/web-link generation & import, share intent creation, launch-intent creation, and filename generation. Implements versioning, compression helpers, and safe parsing.
Strings and build metadata
app/src/main/res/values/strings.xml, app/build.gradle.kts
Added string resources failed_to_import_config and share_config. Bumped android.defaultConfig.versionCode from 4 to 5.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant System as Android System / Browser
    participant Manifest as AndroidManifest
    participant MA as MainActivity
    participant ConfigIO as ContainerConfigIO
    participant Event as AndroidEvent
    participant UI as App UI

    User->>System: Tap deep-link (gamenative://... or https://gamenative.app/...)
    System->>Manifest: Resolve intent to MainActivity
    Manifest->>MA: Start with intent(uri)
    MA->>MA: handleLaunchIntent()
    MA->>ConfigIO: importFromDeepLink(uri)
    alt valid config
        ConfigIO-->>MA: ContainerData
        MA->>Event: Emit ConfigImported(ContainerData)
        Event-->>UI: Apply/configure UI state
    else no config / invalid
        ConfigIO-->>MA: null
        MA->>MA: Continue existing launch-intent flow
    end
Loading
sequenceDiagram
    actor User
    participant UI as ContainerConfigDialog
    participant ConfigIO as ContainerConfigIO
    participant System as Android System

    User->>UI: Click "Import" (From File)
    UI->>System: Launch file picker
    System-->>UI: Return file URI
    UI->>ConfigIO: importFromFile(uri)
    ConfigIO-->>UI: ContainerData or null
    UI->>UI: Show success toast / error toast

    User->>UI: Click "Import" (From Share Code)
    UI->>UI: Show input dialog
    User->>UI: Submit share code
    UI->>ConfigIO: importFromShareCode(code)
    ConfigIO-->>UI: ContainerData or null
    UI->>UI: Show success toast / error toast

    User->>UI: Click "Export"
    UI->>System: Launch save dialog
    System-->>UI: Return target URI
    UI->>ConfigIO: exportToFile(targetUri)
    ConfigIO-->>UI: success/failure
    UI->>UI: Show toast

    User->>UI: Click "Share"
    UI->>ConfigIO: createShareMessageIntent(container)
    ConfigIO-->>UI: Intent with share text
    UI->>System: Launch chooser
    System->>User: Share via chosen app
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review focus:
    • ContainerConfigIO.kt: verify encoding/decoding symmetry, version checks, compression, and URI/deep-link parsing.
    • MainActivity.kt: ensure early return on deep-link doesn’t break other intent flows.
    • ContainerConfigDialog.kt: activity-result launcher lifecycle, proper error-toasting, and temp-file handling.
    • Manifest intent-filters: host/scheme/pathPrefix correctness for intended deep-link URIs.

Possibly related PRs

Poem

🐰 I dug a burrow full of code and cheer,
Tiny links of config hopping near.
Gzip dreams and Base64 song,
Share the settings, short or long—
Thump! The rabbit shipped the file with a cheer.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main feature additions: import, export, and share functionality for container configurations across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt (2)

25-25: Remove unused constant.

The MIME_TYPE_JSON constant is defined but never used in this file. Consider removing it or using it in the exportToFile or file picker operations if appropriate.


366-405: Consider extracting common compression logic.

The compression and encoding logic is duplicated across createWebLink, createDeepLink, and createShareCode. Consider extracting this into a private helper function to improve maintainability.

Example refactor:

private fun compressAndEncode(jsonString: String, flags: Int = Base64.URL_SAFE or Base64.NO_WRAP): String {
    val compressed = ByteArrayOutputStream().use { byteStream ->
        GZIPOutputStream(byteStream).use { gzipStream ->
            gzipStream.write(jsonString.toByteArray())
        }
        byteStream.toByteArray()
    }
    return Base64.encodeToString(compressed, flags)
}

fun createWebLink(container: Container): String {
    val encoded = compressAndEncode(exportToJson(container))
    return "https://gamenative.app/config?data=$encoded"
}

fun createDeepLink(container: Container): String {
    val encoded = compressAndEncode(exportToJson(container))
    return "gamenative://config?data=$encoded"
}
app/src/main/java/app/gamenative/MainActivity.kt (1)

191-210: Consider user feedback for import failures.

The deep link import logic is well-structured, but when import fails (line 206), only a log message is generated. Consider showing a user-facing error message (Toast or dialog) to inform users that the config link was invalid or could not be processed.

Example:

                     val containerData = ContainerConfigIO.importFromDeepLink(uri)
                     if (containerData != null) {
                         Timber.i("[ConfigImport]: Successfully imported config from link: ${containerData.name}")
-                        // Store imported config for user to apply
-                        // You can emit an event here to show a dialog or navigate to container creation
                         PluviaApp.events.emit(AndroidEvent.ConfigImported(containerData))
                     } else {
                         Timber.e("[ConfigImport]: Failed to parse config from link")
+                        lifecycleScope.launch {
+                            Toast.makeText(this@MainActivity, "Failed to import configuration from link", Toast.LENGTH_LONG).show()
+                        }
                     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b74c195 and f2a346a.

📒 Files selected for processing (5)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/app/gamenative/MainActivity.kt (2 hunks)
  • app/src/main/java/app/gamenative/events/AndroidEvent.kt (2 hunks)
  • app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt (7 hunks)
  • app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt (1)
app/src/main/java/com/winlator/container/Container.java (1)
  • Container (22-937)
🔇 Additional comments (7)
app/src/main/java/app/gamenative/events/AndroidEvent.kt (1)

4-4: LGTM!

The new ConfigImported event follows the established pattern in this file and correctly carries the imported ContainerData payload.

Also applies to: 22-22

app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt (3)

247-288: LGTM with minor observation.

The share intent functions are well-structured. Note that line 276 contains an emoji that may not render correctly in all contexts, but this is acceptable for user-facing messaging.


297-356: LGTM!

The share code generation and parsing logic is well-implemented with proper compression, versioning, and flexible input handling.


413-432: LGTM!

The deep link parsing correctly handles the URL query parameter and decompression.

app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt (3)

752-808: LGTM!

The share code import dialog is well-designed with clear instructions, validation, and user feedback.


837-941: LGTM!

The top bar actions provide a clear and intuitive interface for import, export, and sharing operations. The dropdown for import options and the automatic filename generation are good UX touches.


173-217: Missing language field in export Container.

The temporary Container created for export (lines 173-217) is missing the language field, which is present in the share button's Container (line 926). This will cause exported configs to lose language settings.

Apply this diff:

                     isEmulateKeyboardMouse = config.emulateKeyboardMouse
                     isForceDlc = config.forceDlc
+                    language = config.language
                     // Set controller emulation bindings if present

Likely an incorrect or invalid review comment.

@Phr3d13 Phr3d13 changed the title Import / Export / Share Import / Export / Share Container Configs Nov 5, 2025
val imported = ContainerConfigIO.importFromFile(context, it)
if (imported != null) {
config = imported
Toast.makeText(context, "Configuration imported successfully", Toast.LENGTH_SHORT).show()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would recommend putting this into the Strings.xml

config = imported
Toast.makeText(context, "Configuration imported successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to import configuration", Toast.LENGTH_LONG).show()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

*/
fun exportToJson(container: Container): String {
val configData = JSONObject().apply {
put("version", CONTAINER_CONFIG_VERSION)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be worth us putting in the app version too.

* @param jsonString JSON string containing the container config
* @return ContainerData object if import succeeded, null otherwise
*/
fun importFromJson(jsonString: String): ContainerData? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general question, is it worth putting in some input sanitation here? Might not be required, but worth asking.

@phobos665
Copy link
Contributor

Idea is great, execution looks solid.
A few suggestions and questions to address and should be good pending testing

@Phr3d13
Copy link
Contributor Author

Phr3d13 commented Nov 13, 2025

I think this commit addresses all suggestions / issues.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
app/src/main/res/values/strings.xml (1)

126-129: Good additions, but consider adding more string resources.

The new string resources are well-named and clear. However, ContainerConfigDialog.kt contains several hardcoded strings that should be moved here for consistency and localization:

  • Line 161: "Configuration imported successfully"
  • Line 178: "Configuration exported successfully"
  • Line 180: "Failed to export configuration"
  • Line 744: "Configuration imported successfully" (duplicate)
  • Line 748: "Invalid share code or link"

Consider adding these to strings.xml:

     <string name="failed_to_import_config">Failed to import configuration</string>
     <string name="share_config">Share Config</string>
+    <string name="config_imported_success">Configuration imported successfully</string>
+    <string name="config_exported_success">Configuration exported successfully</string>
+    <string name="config_export_failed">Failed to export configuration</string>
+    <string name="invalid_share_code">Invalid share code or link</string>
app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt (2)

154-183: Move hardcoded strings to string resources.

The launchers correctly use toTempContainer() and ContainerConfigIO functions, but several toast messages are hardcoded instead of using string resources:

  • Line 161: "Configuration imported successfully"
  • Line 178: "Configuration exported successfully"
  • Line 180: "Failed to export configuration"

Line 163 correctly uses R.string.failed_to_import_config, but the success messages should follow the same pattern.

Based on learnings (phobos665's suggestion to use strings.xml).

Apply this diff after adding the suggested strings to strings.xml:

                 if (imported != null) {
                     config = imported
-                    Toast.makeText(context, "Configuration imported successfully", Toast.LENGTH_SHORT).show()
+                    Toast.makeText(context, context.getString(R.string.config_imported_success), Toast.LENGTH_SHORT).show()
                 } else {
                     Toast.makeText(context, context.getString(R.string.failed_to_import_config), Toast.LENGTH_LONG).show()
                 }
                 val success = ContainerConfigIO.exportToFile(context, tempContainer, it)
                 if (success) {
-                    Toast.makeText(context, "Configuration exported successfully", Toast.LENGTH_SHORT).show()
+                    Toast.makeText(context, context.getString(R.string.config_exported_success), Toast.LENGTH_SHORT).show()
                 } else {
-                    Toast.makeText(context, "Failed to export configuration", Toast.LENGTH_LONG).show()
+                    Toast.makeText(context, context.getString(R.string.config_export_failed), Toast.LENGTH_LONG).show()
                 }

709-765: Consider externalizing dialog strings for localization.

The share code import dialog functions correctly but contains multiple hardcoded strings. While the core functionality works, consider moving these to strings.xml for consistency and localization support:

  • "Import from Share Code"
  • "Paste the share code or link you received:"
  • "Accepts: GN1:..., gamenative://..., or https://gamenative.app/..."
  • "Share Code or Link"
  • "Configuration imported successfully" (line 744)
  • "Invalid share code or link" (line 748)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2a346a and dc5ae6a.

📒 Files selected for processing (5)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt (7 hunks)
  • app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt (1 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/AndroidManifest.xml
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt (1)
app/src/main/java/com/winlator/container/Container.java (1)
  • Container (22-937)
🔇 Additional comments (11)
app/build.gradle.kts (1)

54-54: LGTM! Version code increment aligns with new features.

The version code bump from 4 to 5 is appropriate for the import/export/share functionality being added in this PR.

app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt (8)

18-31: LGTM! Well-defined security constants.

The security limits (MAX_JSON_SIZE_BYTES, MAX_STRING_LENGTH, etc.) are appropriate safeguards against resource exhaustion and malicious inputs. The version constant enables future backward compatibility.


37-99: Excellent refactoring! Addresses past review feedback.

The toTempContainer extension function successfully eliminates the code duplication that was flagged in previous reviews between export and share flows. The mapping is comprehensive and includes all necessary fields (FEXCore settings, Wine registry, controller bindings).


105-251: Strong security measures implemented.

The sanitization functions provide comprehensive protection against common attacks:

  • Path traversal prevention (removes .. patterns)
  • Command injection mitigation (removes shell metacharacters)
  • Environment variable validation (enforces key=value format)
  • Length limits on all inputs

This directly addresses phobos665's question about input sanitation from previous reviews.

Based on learnings (phobos665's feedback about input sanitation).


284-380: Comprehensive export with version tracking.

The export function includes all necessary metadata (version, appVersion, timestamp) and comprehensively maps all container fields including FEXCore settings, Wine registry entries, and optional configurations. This addresses previous feedback about including the app version and ensuring all fields are exported.

Based on learnings (phobos665's suggestion to include app version).


389-528: Robust import with security measures.

The import functions implement multiple layers of protection:

  • Size limits to prevent resource exhaustion (MAX_JSON_SIZE_BYTES)
  • Version validation with forward compatibility warnings
  • Comprehensive input sanitization via sanitizeContainerData
  • Sensible defaults from Container constants

All fields are properly mapped including those mentioned in past reviews.


587-758: LGTM! Share and deep link functionality is well-implemented.

The share code and deep link functions provide multiple user-friendly import methods with proper security measures:

  • GZIP compression reduces share code size
  • Decompression bomb protection validates both compressed and decompressed sizes
  • Support for multiple formats (GN1:, gamenative://, https://) enhances usability
  • Base64 URL_SAFE encoding for web links

769-841: Complete field mapping in launch intent.

The createLaunchIntent function now comprehensively maps all ContainerData fields to JSON, including FEXCore settings, Wine registry entries, paths, and controller bindings. This addresses the previous review feedback about incomplete field mapping.


849-854: LGTM! Filename generation is well-implemented.

The filename sanitization and timestamp formatting create readable, safe filenames for exported configs.

app/src/main/java/app/gamenative/ui/component/dialog/ContainerConfigDialog.kt (2)

3-88: LGTM! Imports are appropriate for the new functionality.

The imports support file picker integration, sharing, and the new ContainerConfigIO utility. The toTempContainer import confirms usage of the refactored helper function.


794-860: Excellent integration! Duplicate code eliminated.

The top bar actions correctly use toTempContainer() at lines 174 and 846, successfully eliminating the code duplication flagged in previous reviews. The integration with ContainerConfigIO is clean and the share button correctly uses the string resource.

Minor: Consider externalizing "Import", "Export", "From File", and "From Share Code" strings for consistency, though button labels are often acceptable as inline text.

Comment on lines +556 to +578
fun createShareMessageIntent(container: Container, gameName: String = ""): Intent {
val webLink = createWebLink(container)
val configCode = createShareCode(container)
val displayName = gameName.ifEmpty { container.name }

val message = buildString {
appendLine("🎮 GameNative Config: $displayName")
appendLine()
appendLine("Settings: ${container.graphicsDriver}, ${container.dxWrapper}, Wine ${container.wineVersion}")
appendLine()
appendLine("� Click to import:")
appendLine(webLink)
appendLine()
appendLine("📋 Or paste this code in GameNative → Import → From Share Code:")
appendLine(configCode)
}

return Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, message)
putExtra(Intent.EXTRA_SUBJECT, "GameNative Config: $displayName")
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix corrupted emoji character.

Line 566 contains a corrupted character () that appears to be an encoding issue. This should be a clickable link emoji to match the style of lines 562 (🎮) and 569 (📋).

Apply this diff to use a proper link emoji:

-            appendLine("� Click to import:")
+            appendLine("🔗 Click to import:")
🤖 Prompt for AI Agents
In app/src/main/java/app/gamenative/utils/ContainerConfigIO.kt around lines 556
to 578, the message string contains a corrupted character on line 566; replace
the corrupted character (`�`) with a proper link emoji (e.g. "🔗") so the line
reads like "🔗 Click to import:", and ensure the file is saved with UTF-8
encoding so the emoji persists correctly.

Comment on lines +197 to +209
if (isConfigLink) {
Timber.d("[ConfigImport]: Received config import link: ${uri.scheme}://${uri.host}${uri.path}")
val containerData = ContainerConfigIO.importFromDeepLink(uri)
if (containerData != null) {
Timber.i("[ConfigImport]: Successfully imported config from link: ${containerData.name}")
// Store imported config for user to apply
// You can emit an event here to show a dialog or navigate to container creation
PluviaApp.events.emit(AndroidEvent.ConfigImported(containerData))
} else {
Timber.e("[ConfigImport]: Failed to parse config from link")
}
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do this the other way around to do an early return pattern.

If(!thing) { TImber.e(...) return };

... rest of logic

@phobos665
Copy link
Contributor

Code looks solid, happy to give approval pending some testing I can do tonight.

@utkarshdalal
Copy link
Owner

I will prune the changes here and merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants