OXYGENHUB

oxygenhub.io links open in the app (closes #36) cccf06468345

Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 3357126ec6a5
When
· 2026-11-04 00:17 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
cccf06468345bc2bf5ce84816e98451830668fa96220fdc0fa4cb45486a9b200
Depends on
144cbf7bf2b5 57c0824300db aa627197ec18 c1b5d82f16cd e0e4a1c48a77 ff65ea456ff1
Depended on by
nothing yet
In bookmarks
main
In tags
v0.1.9
Edits
6 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified CHANGELOG.md +1 −0
…
1 unchanged lines
2 2
3 3
## Unreleased
4 4
5 +
- **oxygenhub.io links open in the app (#36):** a link to an issue, a proposal, a Flow run, a change, a file, a history or a board, tapped anywhere (mail, chat, a browser), opens the app on that screen, once the hub vouches for the app (oxygenhub#370, `/.well-known/assetlinks.json`). One the app cannot show, while signed in to another hub say, goes to the browser. Android › Apps › OxygenHub › Open by default turns it off.
5 6
- **The changelog is checked on the WASM tier:** a `changelog` job runs OxygenFlow's checks module on any runner with the WASM tier, failing when the changelog is out of shape or the version in gradle.properties has no section.
6 7
7 8
## 0.1.8 (2026-11-03)
…
65 unchanged lines
modified app/src/main/AndroidManifest.xml +16 −0
…
33 unchanged lines
34 34
35 35
                <category android:name="android.intent.category.LAUNCHER" />
36 36
            </intent-filter>
37 +
            <!-- App Links (#36): oxygenhub.io's links to what the app shows open
38 +
                 it there; the hub vouches for the app in assetlinks.json
39 +
                 (oxygenhub#370). Only the paths the app has a screen for. -->
40 +
            <intent-filter android:autoVerify="true">
41 +
                <action android:name="android.intent.action.VIEW" />
42 +
                <category android:name="android.intent.category.DEFAULT" />
43 +
                <category android:name="android.intent.category.BROWSABLE" />
44 +
                <data android:scheme="https" android:host="oxygenhub.io" />
45 +
                <data android:pathAdvancedPattern="/[^/]+/[^/]+/issue/[0-9]+" />
46 +
                <data android:pathAdvancedPattern="/[^/]+/[^/]+/proposal/[0-9]+" />
47 +
                <data android:pathAdvancedPattern="/[^/]+/[^/]+/flow/[0-9]+" />
48 +
                <data android:pathAdvancedPattern="/[^/]+/[^/]+/change/.+" />
49 +
                <data android:pathAdvancedPattern="/[^/]+/[^/]+/changes/.+" />
50 +
                <data android:pathAdvancedPattern="/[^/]+/[^/]+/file/.+" />
51 +
                <data android:pathAdvancedPattern="/projects/[^/]+/[0-9]+" />
52 +
            </intent-filter>
37 53
        </activity>
38 54
        <service
39 55
            android:name=".runner.RunnerService"
…
41 unchanged lines
modified app/src/main/java/io/oxygenhub/android/MainActivity.kt +21 −2
…
14 unchanged lines
15 15
import io.oxygenhub.android.ui.nav.ProposalRoute
16 16
import io.oxygenhub.android.ui.nav.SignedInApp
17 17
import io.oxygenhub.android.ui.nav.rememberViewModel
18 +
import io.oxygenhub.android.ui.nav.routeFor
18 19
import io.oxygenhub.android.ui.signin.SignInScreen
19 20
import io.oxygenhub.android.ui.signin.SignInViewModel
20 21
import io.oxygenhub.android.ui.theme.OxygenHubTheme
…
50 unchanged lines
71 72
        routeIn(intent)?.let { opening.value = it }
72 73
    }
73 74
74 -
    /** A land alert's proposal, or the screen a tap on the widget's counts names (#31). */
75 -
    private fun routeIn(intent: Intent?): Any? = proposalIn(intent) ?: WidgetTaps.route(intent)
75 +
    /** A land alert's proposal, the screen a tap on the widget's counts names (#31), or a link's (#36). */
76 +
    private fun routeIn(intent: Intent?): Any? = proposalIn(intent) ?: WidgetTaps.route(intent) ?: linkIn(intent)
76 77
78 +
    /**
79 +
     * An oxygenhub.io link opened in the app (App Links, #36): its screen,
80 +
     * as links inside the app go. One the app cannot show (signed in to
81 +
     * another hub, say) goes to the browser, which cannot hand it back.
82 +
     */
83 +
    private fun linkIn(intent: Intent?): Any? {
84 +
        if (intent?.action != Intent.ACTION_VIEW) return null
85 +
        val url = intent.dataString ?: return null
86 +
        val hub = (application as OxygenHubApp).container.sessions.session.value?.hub ?: DEFAULT_HUB
87 +
        routeFor(url, hub)?.let { return it }
88 +
        val browser = Intent(Intent.ACTION_VIEW, intent.data).apply {
89 +
            selector = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER)
90 +
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
91 +
        }
92 +
        runCatching { startActivity(browser) }
93 +
        return null
94 +
    }
95 +
77 96
    private fun proposalIn(intent: Intent?): ProposalRoute? {
78 97
        val repository = intent?.getStringExtra(LandAlerts.EXTRA_REPOSITORY) ?: return null
79 98
        val number = intent.getLongExtra(LandAlerts.EXTRA_NUMBER, 0)
…
4 unchanged lines