Skip to content

Comments

Deeplinking improvement#256

Open
rainxchzed wants to merge 2 commits intomainfrom
deeplinking-improvement
Open

Deeplinking improvement#256
rainxchzed wants to merge 2 commits intomainfrom
deeplinking-improvement

Conversation

@rainxchzed
Copy link
Owner

@rainxchzed rainxchzed commented Feb 20, 2026

Summary by CodeRabbit

  • New Features
    • Added deep linking support to open app directly from URLs containing GitHub links.
    • Introduced share functionality allowing users to share content via intent actions.
    • Enhanced application security with updated manifest configuration for cleartext traffic handling.

This commit adds `android:hasFragileUserData="true"` to the `AndroidManifest.xml`. This ensures that when a user uninstalls the app, the system prompts them to confirm if they want to also remove the app's data.

The change also includes `tools:targetApi="29"` to support this attribute.
This commit enables the application to receive and process shared URLs (for `github.com` and `github-store.org`) from other apps through the Android Share Sheet.

When a user shares text containing a supported URL with the app, the app will now extract the URL and navigate to the corresponding repository details screen.

- **feat(android)**: Added an `intent-filter` for `ACTION_SEND` in `AndroidManifest.xml` to accept `text/plain` and `text/html` content.
- **feat(deeplink)**: Implemented `DeepLinkParser.extractSupportedUrl()` to find the first `github.com` or `github-store.org` URL within a given text string.
- **refactor(android)**: Updated `MainActivity` to handle `ACTION_SEND` intents, extract the URL from the shared text, and trigger the deep link flow.
- **fix(android)**: Corrected the `pathPattern` in the `AndroidManifest.xml` for `github.com` deep links to correctly match repository paths.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

Walkthrough

The changes add deep link handling for receiving shared content via intent, implement centralized intent processing in MainActivity, extract supported URLs from shared text, and expand Android manifest declarations with security attributes and intent filters for both ACTION_VIEW and ACTION_SEND.

Changes

Cohort / File(s) Summary
Android Manifest Configuration
composeApp/src/androidMain/AndroidManifest.xml
Added security and targeting attributes (hasFragileUserData, targetApi="29", usesCleartextTraffic="false") to Application. Updated MainActivity with exported="true" and launchMode="singleTask". Restructured VIEW intent-filter data tags for granular scheme/host/pathPattern matching. Added SEND intent-filters for text/plain and text/html mime types.
Intent Handling Refactoring
composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt
Introduced centralized handleIncomingIntent(intent: Intent?) method processing both ACTION_VIEW and ACTION_SEND intents. ACTION_SEND extracts text and parses supported URLs via DeepLinkParser. Replaced inline intent handling with method calls in onCreate and DisposableEffect. Added onDispose cleanup. Introduced AppAndroidPreview composable for preview support.
Deep Link URL Extraction
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/deeplink/DeepLinkParser.kt
Added new public function extractSupportedUrl(text: String): String? using case-insensitive regex to extract github.com or github-store.org URLs from shared text.
Code Formatting
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/Main.kt
Reformatted DeepLinkDestination.None branch from single-line to multi-line comment block without behavioral change.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Android as Android System
    participant MA as MainActivity
    participant DLP as DeepLinkParser
    participant App as App/Main

    User->>Android: Share text via ACTION_SEND
    Android->>MA: handleIncomingIntent(intent)
    MA->>MA: Extract EXTRA_TEXT from intent
    MA->>DLP: extractSupportedUrl(text)
    DLP->>DLP: Regex match for github.com/<br/>github-store.org
    DLP-->>MA: Return matched URL or null
    alt URL found
        MA->>MA: Parse URL via DeepLinkParser
        MA->>MA: Assign result to deepLinkUri
        MA->>App: Trigger deep link navigation
    else URL not found
        MA->>MA: deepLinkUri remains unchanged
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Intent handlers bounce and send,
Deep links parse from shared text's end,
Manifest guards with security's might,
SEND and VIEW dance in the light,
GitHub URLs hop just right! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Deeplinking improvement' accurately reflects the main changes in the changeset, which focus on enhancing deep linking functionality across multiple files including intent handling, manifest configuration, and a new URL extraction utility.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch deeplinking-improvement

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
Contributor

@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: 2

🧹 Nitpick comments (2)
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/deeplink/DeepLinkParser.kt (1)

178-181: Regex matches spoofed domains and captures trailing punctuation.

Two issues with the URL extraction pattern:

  1. Domain boundary missing: The pattern matches https://github.com.evil.com/path because there's no boundary after the domain literal. While parse() downstream mitigates this (it checks for https://github.com/ with the trailing slash), it's better to be precise at extraction time.

  2. Trailing punctuation captured: If the shared text is "Check out https://github.com/owner/repo.", the trailing . is included in the match. Same for ), ,, ;, etc. — common in natural-language messages. This can cause parse() to fail on otherwise valid URLs.

Proposed fix
-    fun extractSupportedUrl(text: String): String? {
-        val regex = """https?://(?:www\.)?(?:github\.com|github-store\.org)[^\s<>"']+""".toRegex(RegexOption.IGNORE_CASE)
-        return regex.find(text)?.value
-    }
+    private val supportedUrlRegex =
+        """https?://(?:www\.)?(?:github\.com|github-store\.org)(?:/[^\s<>"')\],;.!?]*)?""".toRegex(RegexOption.IGNORE_CASE)
+
+    fun extractSupportedUrl(text: String): String? {
+        return supportedUrlRegex.find(text)?.value?.trimEnd('/', '.')
+    }

The revised pattern:

  • Requires / (or end) after the domain, preventing subdomain spoofing.
  • Excludes common sentence-ending punctuation from the character class.
  • Compiles the Regex once as a property instead of on every call.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/deeplink/DeepLinkParser.kt`
around lines 178 - 181, The extractSupportedUrl function uses a regex that
allows spoofed domains (e.g., github.com.evil.com) and captures trailing
punctuation; update it to a precompiled Regex property (not recreated on each
call) that requires either a slash or end-of-string after the domain portion (so
the domain literal is a boundary) and limits the subsequent character class to
exclude common sentence punctuation like . , ; : ) ] > ' " so trailing
punctuation isn't captured; replace the inline toRegex usage in
extractSupportedUrl to use this new precompiled pattern and return its first
match value.
composeApp/src/androidMain/AndroidManifest.xml (1)

74-76: Verify pathPattern granularity with split <data> elements.

When scheme, host, and pathPattern are declared in separate <data> tags inside the same <intent-filter>, Android combines them multiplicatively. With a single value each this works correctly, but it's a subtle rule that can cause surprising matches if more <data> entries are added later. Keeping them in a single <data> tag (if the manifest schema permits) or adding a comment documenting the multiplicative combination would prevent accidental regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/androidMain/AndroidManifest.xml` around lines 74 - 76, The
three separate <data> elements inside the same <intent-filter> (the ones
declaring android:scheme="https", android:host="github.com", and
android:pathPattern="/.*/.*") will be combined multiplicatively by Android and
can lead to surprising matches if more <data> entries are added later; fix this
by consolidating these attributes into a single <data> entry with
android:scheme, android:host and android:pathPattern together (or, if you must
keep separate tags, add a clear comment above the intent-filter explaining the
multiplicative combination rule) so the intent-filter semantics are explicit and
safe from accidental regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composeApp/src/androidMain/AndroidManifest.xml`:
- Around line 61-66: The manifest's SEND intent-filter with text/plain and
text/html makes the app appear for all text shares; update handleIncomingIntent
to detect when an ACTION_SEND intent yields no supported URL (i.e.,
extractSupportedUrl(...) returns null) and show a brief user-visible message
(Toast or Snackbar) explaining the share was ignored; locate the code that
handles Intent.ACTION_SEND in handleIncomingIntent and add a concise
Toast/Snackbar path for the null result case so users get feedback instead of
silent no-op.

In `@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt`:
- Around line 45-60: The incoming-intent handler (handleIncomingIntent) sets
deepLinkUri but because App/Main.kt never clears it, a repeated identical share
won't retrigger LaunchedEffect(deepLinkUri); change the flow so each consumed
deepLink signals back to clear or produce a unique wrapper: after navigation in
Main.kt's LaunchedEffect that reacts to deepLinkUri, reset deepLinkUri to null
(or call a provided clearDeepLink callback), or alternatively change deepLinkUri
to a wrapper (e.g., a data class/Pair with an incrementing nonce) and update
handleIncomingIntent to set a new unique wrapper on each intent; also update the
LaunchedEffect key to use the wrapper (or pair) and ensure
handleIncomingIntent/DeepLinkParser/App agree on the clear/reset contract.

---

Nitpick comments:
In `@composeApp/src/androidMain/AndroidManifest.xml`:
- Around line 74-76: The three separate <data> elements inside the same
<intent-filter> (the ones declaring android:scheme="https",
android:host="github.com", and android:pathPattern="/.*/.*") will be combined
multiplicatively by Android and can lead to surprising matches if more <data>
entries are added later; fix this by consolidating these attributes into a
single <data> entry with android:scheme, android:host and android:pathPattern
together (or, if you must keep separate tags, add a clear comment above the
intent-filter explaining the multiplicative combination rule) so the
intent-filter semantics are explicit and safe from accidental regressions.

In
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/deeplink/DeepLinkParser.kt`:
- Around line 178-181: The extractSupportedUrl function uses a regex that allows
spoofed domains (e.g., github.com.evil.com) and captures trailing punctuation;
update it to a precompiled Regex property (not recreated on each call) that
requires either a slash or end-of-string after the domain portion (so the domain
literal is a boundary) and limits the subsequent character class to exclude
common sentence punctuation like . , ; : ) ] > ' " so trailing punctuation isn't
captured; replace the inline toRegex usage in extractSupportedUrl to use this
new precompiled pattern and return its first match value.

Comment on lines +61 to +66
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<data android:mimeType="text/html" />
</intent-filter>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

App will appear in the share sheet for all plain/HTML text shares.

The SEND intent-filter with text/plain and text/html cannot be scoped to specific domains at the manifest level, so the app shows up whenever a user shares any text — articles, notes, non-GitHub links, etc. When the shared text doesn't contain a supported URL, extractSupportedUrl returns null and nothing happens, but the user gets no feedback explaining why.

Consider adding a brief Toast or Snackbar in handleIncomingIntent when ACTION_SEND yields no supported URL, so the user understands the share was intentionally ignored.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/androidMain/AndroidManifest.xml` around lines 61 - 66, The
manifest's SEND intent-filter with text/plain and text/html makes the app appear
for all text shares; update handleIncomingIntent to detect when an ACTION_SEND
intent yields no supported URL (i.e., extractSupportedUrl(...) returns null) and
show a brief user-visible message (Toast or Snackbar) explaining the share was
ignored; locate the code that handles Intent.ACTION_SEND in handleIncomingIntent
and add a concise Toast/Snackbar path for the null result case so users get
feedback instead of silent no-op.

Comment on lines +45 to +60
private fun handleIncomingIntent(intent: Intent?) {
if (intent == null) return

val uriString = when (intent.action) {
Intent.ACTION_VIEW -> intent.data?.toString()

Intent.ACTION_SEND -> {
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
sharedText?.let { DeepLinkParser.extractSupportedUrl(it) }
}

else -> null
}

uriString?.let { deepLinkUri = it }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Good centralized handling; but re-sharing the same URL is silently ignored.

deepLinkUri is never reset after consumption in App(). If the user shares the exact same URL a second time, the value doesn't change, so LaunchedEffect(deepLinkUri) in Main.kt won't re-fire — the share appears to do nothing.

A simple fix is to reset the URI after navigation and use a wrapper to ensure uniqueness:

Sketch: reset after consumption

In Main.kt, reset deepLinkUri after navigating:

// After navigating in LaunchedEffect:
// signal back to clear the deep link so a repeated share is recognized

Or in MainActivity.kt, wrap the value so each intent is unique:

-    private var deepLinkUri by mutableStateOf<String?>(null)
+    private var deepLinkUri by mutableStateOf<Pair<String, Long>?>(null)
-        uriString?.let { deepLinkUri = it }
+        uriString?.let { deepLinkUri = it to System.currentTimeMillis() }

Then key LaunchedEffect on the pair.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/androidMain/kotlin/zed/rainxch/githubstore/MainActivity.kt`
around lines 45 - 60, The incoming-intent handler (handleIncomingIntent) sets
deepLinkUri but because App/Main.kt never clears it, a repeated identical share
won't retrigger LaunchedEffect(deepLinkUri); change the flow so each consumed
deepLink signals back to clear or produce a unique wrapper: after navigation in
Main.kt's LaunchedEffect that reacts to deepLinkUri, reset deepLinkUri to null
(or call a provided clearDeepLink callback), or alternatively change deepLinkUri
to a wrapper (e.g., a data class/Pair with an incrementing nonce) and update
handleIncomingIntent to set a new unique wrapper on each intent; also update the
LaunchedEffect key to use the wrapper (or pair) and ensure
handleIncomingIntent/DeepLinkParser/App agree on the clear/reset contract.

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.

1 participant