Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Binary file modified composeApp/release/baselineProfiles/0/composeApp-release.dm
Binary file not shown.
Binary file modified composeApp/release/baselineProfiles/1/composeApp-release.dm
Binary file not shown.
38 changes: 24 additions & 14 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="RequestInstallPackagesPolicy" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />

<application
android:name=".app.GithubStoreApp"
android:allowBackup="true"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar"
android:usesCleartextTraffic="false">
android:usesCleartextTraffic="false"
tools:targetApi="29">

<activity
android:name=".MainActivity"
Expand Down Expand Up @@ -54,17 +58,22 @@
android:scheme="githubstore" />
</intent-filter>

<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>
Comment on lines +61 to +66
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.


<!-- GitHub repository links: https://github.com/{owner}/{repo} -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="github.com"
android:pathPattern="/.*/..*"
android:scheme="https" />
<data android:scheme="https" />
<data android:host="github.com" />
<data android:pathPattern="/.*/.*" />
</intent-filter>

<intent-filter android:autoVerify="true">
Expand All @@ -73,10 +82,11 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="github-store.org"
android:pathPrefix="/app/"
android:scheme="https" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="github-store.org" />
<data android:pathPrefix="/app/" />

</intent-filter>
</activity>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,24 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.util.Consumer
import zed.rainxch.githubstore.app.deeplink.DeepLinkParser

class MainActivity : ComponentActivity() {

private var deepLinkUri by mutableStateOf<String?>(null)

override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()

enableEdgeToEdge()

super.onCreate(savedInstanceState)

deepLinkUri = intent?.data?.toString()
handleIncomingIntent(intent)

setContent {
DisposableEffect(Unit) {
val listener = Consumer<Intent> { newIntent ->
newIntent.data?.toString()?.let {
deepLinkUri = it
}
handleIncomingIntent(newIntent)
}
addOnNewIntentListener(listener)
onDispose {
Expand All @@ -43,6 +41,23 @@ class MainActivity : ComponentActivity() {
App(deepLinkUri = deepLinkUri)
}
}

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 }
}
Comment on lines +45 to +60
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.

}

@Preview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ fun App(deepLinkUri: String? = null) {
)
}

DeepLinkDestination.None -> { /* ignore unrecognized deep links */
DeepLinkDestination.None -> {
/* ignore unrecognized deep links */
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,9 @@ object DeepLinkParser {
}
return null
}

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