Skip to content

Implement federated sign-in buttons and associated logic for Google and GitHub#3

Open
brionmario wants to merge 1 commit into
thunder-id:mainfrom
brionmario:feat-federated
Open

Implement federated sign-in buttons and associated logic for Google and GitHub#3
brionmario wants to merge 1 commit into
thunder-id:mainfrom
brionmario:feat-federated

Conversation

@brionmario

@brionmario brionmario commented Jul 7, 2026

Copy link
Copy Markdown
Member

Purpose

Approach

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Added browser-based social sign-in support, including redirect handling back into the app.
    • Added dedicated Google and GitHub sign-in buttons with branded styling.
    • Sign-in screens now better recognize and route provider-specific trigger actions.
  • Bug Fixes

    • Improved handling for incomplete sign-in and sign-up responses so flows continue more reliably.
    • Sign-in cancellations from the browser are now handled more cleanly without surfacing unnecessary errors.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a federated/social login flow using a browser-based redirect bridge (FederatedAuthSession), new Google/GitHub sign-in button composables built on a new OutlinedTriggerButton, model fields for redirection data, and updates to SignIn/SignUp response handling to support FlowStatus.INCOMPLETE and browser redirection.

Changes

Federated Authentication Flow

Layer / File(s) Summary
Redirection and event type model fields
src/main/kotlin/dev/thunderid/android/Models.kt
FlowStepData gains redirectURL and additionalData; FlowAction gains eventType.
Browser redirect bridge
src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt, build.gradle.kts
New singleton opens a CustomTabsIntent for a redirect URL and suspends via CompletableDeferred until onRedirect completes it or cancelIfPending cancels it; adds androidx.browser dependency.
Provider button adapters
.../auth/adapters/OutlinedTriggerButton.kt, .../auth/adapters/GoogleButton.kt, .../auth/adapters/GitHubButton.kt
New bordered OutlinedTriggerButton composable, plus GoogleButton/GitHubButton composables rendering brand logos via ImageVector and wired into it.
SignIn UI and redirection handling
.../auth/SignIn.kt
TRIGGER actions route to GoogleButton/GitHubButton/OutlinedTriggerButton based on hint text; lastActionId and Context are captured; handleSignInResponse handles FlowStatus.INCOMPLETE with REDIRECTION by launching FederatedAuthSession, extracting an auth code, and resuming sign-in; cancellations are ignored while other errors set signInState.error.
SignUp INCOMPLETE handling
.../auth/SignUp.kt
FlowStatus.INCOMPLETE now routes through state.update(response) like PROMPT_ONLY.
Import fix
.../organization/LanguageSwitcher.kt
Adds missing Box import.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SignIn as BaseSignIn
  participant Session as FederatedAuthSession
  participant Browser as CustomTabsIntent

  User->>SignIn: taps Google/GitHub button
  SignIn->>SignIn: handleSignInResponse (FlowStatus.INCOMPLETE, REDIRECTION)
  SignIn->>Session: launch(context, redirectURL)
  Session->>Browser: open redirect URL
  Browser-->>User: complete provider login
  User-->>Session: deep-link callback triggers onRedirect(uri)
  Session-->>SignIn: return callback Uri
  SignIn->>SignIn: extract code, build EmbeddedSignInPayload
  SignIn->>SignIn: resume handleSignInResponse with follow-up response
Loading

Related Issues: None found.

Related PRs: None found.

Suggested labels: feature, android

Suggested reviewers: None found.

🐰 A rabbit hops through browser doors,
Redirect URIs it now explores,
Google, GitHub buttons bright,
Guide the user through the light,
Sign-in flows complete once more!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The template is present, but Purpose and Approach are empty and the checklist/security sections are unfilled. Fill in Purpose and Approach with a brief summary, add screenshots if applicable, and complete the checklist/security items, including tests and any breaking-change notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main change: adding federated sign-in buttons and logic for Google and GitHub.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/OutlinedTriggerButton.kt (1)

34-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider setting role = Role.Button for proper a11y semantics.

clickable(onClick = onClick) doesn't set role, so accessibility services may not announce this as a button. Optional polish.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/OutlinedTriggerButton.kt`
around lines 34 - 35, The clickable element in OutlinedTriggerButton should
expose proper button semantics for accessibility. Update the Modifier chain
around clickable(onClick = onClick) and the semantics block so the component
explicitly sets role = Role.Button while keeping the existing contentDescription
based on label. Use the OutlinedTriggerButton composable as the place to make
this a11y fix.
src/main/kotlin/dev/thunderid/compose/components/presentation/auth/SignIn.kt (2)

78-89: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Provider detection via label/ref substring match is fragile.

Matching hint.contains("google"/"github", ignoreCase = true) against a concatenation of ref and localized label is a heuristic. If ref doesn't reliably encode a stable provider id, or label is server-localized, this could misclassify actions and fall back to the generic OutlinedTriggerButton, losing correct provider branding. Prefer a dedicated provider/type field if the backend contract can provide one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/SignIn.kt`
around lines 78 - 89, Provider detection in SignIn.kt is currently based on
substring matching against the concatenated ref/label hint, which is fragile and
can misclassify actions. Update the SignIn composable’s trigger rendering logic
to use a stable provider/type field from the action model instead of deriving
Google/GitHub from localized text, and keep the fallback OutlinedTriggerButton
only for truly unknown providers.

210-211: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Swallowing CancellationException for a business signal is fragile.

Treating "browser dismissed" as a CancellationException conflates it with genuine structured-concurrency cancellation (e.g., the composable leaving composition mid-flow via rememberCoroutineScope()). Silently catching and discarding it here means a real external cancellation (not user dismissal) would also be swallowed without cleanup/propagation. Consider having FederatedAuthSession signal user-dismissal via a dedicated result/sealed type rather than throwing CancellationException, so genuine coroutine cancellation is still cooperatively rethrown.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/SignIn.kt`
around lines 210 - 211, The `catch (e: CancellationException)` in the sign-in
flow is using coroutine cancellation as a business signal, which can hide real
structured-concurrency cancellation. Update the `SignIn` authentication handling
so browser dismissal is represented by a dedicated result/sealed outcome from
`FederatedAuthSession` instead of throwing `CancellationException`, and keep
genuine coroutine cancellations cooperatively propagated/rethrown. Make sure the
branch that currently swallows the exception distinguishes user dismissal from
actual cancellation and preserves any required cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt`:
- Around line 23-32: The launch flow in FederatedAuthSession.launch can suspend
forever because deferred.await() has no fallback if neither onRedirect nor
cancelIfPending runs. Update launch to guard the await with a timeout safety net
(for example around the CompletableDeferred<Uri> await path), so pending is
still cleared in the finally block even when the browser is backgrounded and the
callback never arrives.
- Around line 23-32: The FederatedAuthSession.launch method leaves pending stale
if Uri.parse(redirectUrl) or CustomTabsIntent.Builder().build().launchUrl(...)
throws before the current try/finally runs. Move the parsing and launch steps
inside the try block around deferred.await() so pending is always cleared in the
finally path, and let the exception propagate normally to the caller; use the
launch, pending, and deferred symbols to update the flow safely.
- Around line 20-42: The singleton state in FederatedAuthSession is vulnerable
to overlapping launch() calls because the shared pending deferred can be
overwritten and leave the first coroutine hanging. Update launch() to explicitly
guard against an in-flight session by either rejecting a new call when pending
is already set or cancelling the existing session before assigning the new
CompletableDeferred, and make sure onRedirect() and cancelIfPending() continue
to clear the active session consistently.
- Line 26: The redirect launch in FederatedAuthSession.launchUrl currently
accepts a generic Context, which can fail when called from a non-Activity
context. Update the API to either require an Activity for the context parameter
or add Intent.FLAG_ACTIVITY_NEW_TASK before calling
CustomTabsIntent.Builder().build().launchUrl, and adjust any callers or helper
methods in FederatedAuthSession accordingly.

In
`@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/OutlinedTriggerButton.kt`:
- Around line 22-41: OutlinedTriggerButton currently has no enabled/loading
guard, so trigger actions can still be tapped while a sign-in is in flight. Add
an enabled parameter to OutlinedTriggerButton and pass it through to the
clickable modifier, then thread enabled = !signInState.isLoading from the
SignIn.kt call sites for GoogleButton and GitHubButton so trigger submissions
and Custom Tab launches are blocked during loading; use the existing
OutlinedTriggerButton, GoogleButton, and GitHubButton symbols to update all
relevant call paths consistently.

---

Nitpick comments:
In
`@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/OutlinedTriggerButton.kt`:
- Around line 34-35: The clickable element in OutlinedTriggerButton should
expose proper button semantics for accessibility. Update the Modifier chain
around clickable(onClick = onClick) and the semantics block so the component
explicitly sets role = Role.Button while keeping the existing contentDescription
based on label. Use the OutlinedTriggerButton composable as the place to make
this a11y fix.

In
`@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/SignIn.kt`:
- Around line 78-89: Provider detection in SignIn.kt is currently based on
substring matching against the concatenated ref/label hint, which is fragile and
can misclassify actions. Update the SignIn composable’s trigger rendering logic
to use a stable provider/type field from the action model instead of deriving
Google/GitHub from localized text, and keep the fallback OutlinedTriggerButton
only for truly unknown providers.
- Around line 210-211: The `catch (e: CancellationException)` in the sign-in
flow is using coroutine cancellation as a business signal, which can hide real
structured-concurrency cancellation. Update the `SignIn` authentication handling
so browser dismissal is represented by a dedicated result/sealed outcome from
`FederatedAuthSession` instead of throwing `CancellationException`, and keep
genuine coroutine cancellations cooperatively propagated/rethrown. Make sure the
branch that currently swallows the exception distinguishes user dismissal from
actual cancellation and preserves any required cleanup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 43b6ba38-0c56-4357-b4c7-74decc3103ce

📥 Commits

Reviewing files that changed from the base of the PR and between 77fb460 and 8ebad51.

📒 Files selected for processing (9)
  • build.gradle.kts
  • src/main/kotlin/dev/thunderid/android/Models.kt
  • src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt
  • src/main/kotlin/dev/thunderid/compose/components/presentation/auth/SignIn.kt
  • src/main/kotlin/dev/thunderid/compose/components/presentation/auth/SignUp.kt
  • src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/GitHubButton.kt
  • src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/GoogleButton.kt
  • src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/OutlinedTriggerButton.kt
  • src/main/kotlin/dev/thunderid/compose/components/presentation/organization/LanguageSwitcher.kt

Comment on lines +20 to +42
object FederatedAuthSession {
private var pending: CompletableDeferred<Uri>? = null

suspend fun launch(context: Context, redirectUrl: String): Uri {
val deferred = CompletableDeferred<Uri>()
pending = deferred
CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
return try {
deferred.await()
} finally {
if (pending === deferred) pending = null
}
}

fun onRedirect(uri: Uri) {
pending?.complete(uri)
pending = null
}

fun cancelIfPending() {
pending?.cancel()
pending = null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Singleton pending is not safe against overlapping launch calls.

Since FederatedAuthSession is a singleton with a single mutable pending field, a second concurrent launch() call (e.g. rapid double-tap on a provider button, or launching Google then GitHub before the first redirect returns) silently overwrites pending, orphaning the first deferred forever (it never completes and its awaiting coroutine hangs). Consider guarding against concurrent launches (e.g., cancel any existing pending session before starting a new one, or throw/reject if one is already in flight).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt` around
lines 20 - 42, The singleton state in FederatedAuthSession is vulnerable to
overlapping launch() calls because the shared pending deferred can be
overwritten and leave the first coroutine hanging. Update launch() to explicitly
guard against an in-flight session by either rejecting a new call when pending
is already set or cancelling the existing session before assigning the new
CompletableDeferred, and make sure onRedirect() and cancelIfPending() continue
to clear the active session consistently.

Comment on lines +23 to +32
suspend fun launch(context: Context, redirectUrl: String): Uri {
val deferred = CompletableDeferred<Uri>()
pending = deferred
CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
return try {
deferred.await()
} finally {
if (pending === deferred) pending = null
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No timeout on deferred.await().

If the user backgrounds the browser tab without returning to the app (so neither onRedirect nor cancelIfPending from onResume fires), the suspended coroutine awaits indefinitely, leaking the coroutine and leaving pending set. Relying entirely on the hosting Activity's onResume to call cancelIfPending is fragile; consider adding a timeout (withTimeout) as a safety net.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt` around
lines 23 - 32, The launch flow in FederatedAuthSession.launch can suspend
forever because deferred.await() has no fallback if neither onRedirect nor
cancelIfPending runs. Update launch to guard the await with a timeout safety net
(for example around the CompletableDeferred<Uri> await path), so pending is
still cleared in the finally block even when the browser is backgrounded and the
callback never arrives.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Uncaught launchUrl/Uri.parse failure leaves pending in a stale, leaked state.

If Uri.parse(redirectUrl) throws (malformed URL) or launchUrl(...) throws ActivityNotFoundException (no Custom Tabs–capable browser installed), the exception propagates before the try/finally block is reached. pending remains set to the never-completing deferred forever. A subsequent launch call overwrites it (leaking the deferred silently), and cancelIfPending/onRedirect invoked by the caller in the meantime would operate on the wrong/stale entry. The launch call needs to be inside the try block so pending is always cleared on failure, and the exception should propagate to the caller.

🐛 Proposed fix
     suspend fun launch(context: Context, redirectUrl: String): Uri {
         val deferred = CompletableDeferred<Uri>()
         pending = deferred
-        CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
         return try {
+            CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
             deferred.await()
         } finally {
             if (pending === deferred) pending = null
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
suspend fun launch(context: Context, redirectUrl: String): Uri {
val deferred = CompletableDeferred<Uri>()
pending = deferred
CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
return try {
deferred.await()
} finally {
if (pending === deferred) pending = null
}
}
suspend fun launch(context: Context, redirectUrl: String): Uri {
val deferred = CompletableDeferred<Uri>()
pending = deferred
return try {
CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))
deferred.await()
} finally {
if (pending === deferred) pending = null
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt` around
lines 23 - 32, The FederatedAuthSession.launch method leaves pending stale if
Uri.parse(redirectUrl) or CustomTabsIntent.Builder().build().launchUrl(...)
throws before the current try/finally runs. Move the parsing and launch steps
inside the try block around deferred.await() so pending is always cleared in the
finally path, and let the exception propagate normally to the caller; use the
launch, pending, and deferred symbols to update the flow safely.

suspend fun launch(context: Context, redirectUrl: String): Uri {
val deferred = CompletableDeferred<Uri>()
pending = deferred
CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(redirectUrl))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does CustomTabsIntent.launchUrl require an Activity context or does it add FLAG_ACTIVITY_NEW_TASK internally in androidx.browser 1.8.0?

💡 Result:

The CustomTabsIntent.launchUrl method in androidx.browser 1.8.0 requires an Activity context because it internally calls startActivity on the provided context [1][2][3]. It does not automatically add the FLAG_ACTIVITY_NEW_TASK flag to the intent [3][4]. If you call launchUrl with a non-Activity context (such as an Application context), the system will throw an android.util.AndroidRuntimeException stating: "Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag" [3][4]. To resolve this when you must launch a Custom Tab from a non-Activity context, you can manually add the flag to the CustomTabsIntent's underlying intent before calling launchUrl: customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); customTabsIntent.launchUrl(context, uri); Alternatively, it is recommended to pass an Activity context whenever possible to ensure proper task stack management [3][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and nearby context.
FILE="src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt"

echo "== File outline =="
ast-grep outline "$FILE" --view expanded || true

echo
echo "== Relevant lines =="
nl -ba "$FILE" | sed -n '1,120p'

echo
echo "== Search for FederatedAuthSession call sites / context sources =="
rg -n "FederatedAuthSession|launchUrl\\(|CustomTabsIntent" src main . --glob '!**/build/**' || true

Repository: thunder-id/android-sdks

Length of output: 1969


launchUrl() needs an Activity context or FLAG_ACTIVITY_NEW_TASK.
This accepts any Context, but a non-Activity caller can crash when launchUrl() delegates to startActivity(). Narrow the parameter to Activity, or add Intent.FLAG_ACTIVITY_NEW_TASK before launching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/kotlin/dev/thunderid/android/auth/FederatedAuthSession.kt` at line
26, The redirect launch in FederatedAuthSession.launchUrl currently accepts a
generic Context, which can fail when called from a non-Activity context. Update
the API to either require an Activity for the context parameter or add
Intent.FLAG_ACTIVITY_NEW_TASK before calling
CustomTabsIntent.Builder().build().launchUrl, and adjust any callers or helper
methods in FederatedAuthSession accordingly.

Comment on lines +22 to +41
internal fun OutlinedTriggerButton(
label: String,
modifier: Modifier = Modifier,
icon: (@Composable () -> Unit)? = null,
onClick: () -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
modifier = modifier
.fillMaxWidth()
.border(BorderStroke(1.dp, Color.Gray.copy(alpha = 0.4f)), RoundedCornerShape(8.dp))
.clickable(onClick = onClick)
.semantics { contentDescription = label }
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
icon?.invoke()
BasicText(label)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

No enabled/loading control — risk of duplicate non-idempotent submissions.

OutlinedTriggerButton always accepts clicks; there's no way to disable it while a submission/redirection is in flight. In SignIn.kt, TRIGGER actions render this button (and GoogleButton/GitHubButton, which wrap it) without gating on signInState.isLoading, unlike the BaseSignInButton branch which explicitly passes isLoading. A rapid double-tap can fire two concurrent signIn() calls / launch two Custom Tabs for the same federated action.

💡 Proposed fix: add an `enabled` param
 internal fun OutlinedTriggerButton(
     label: String,
     modifier: Modifier = Modifier,
     icon: (`@Composable` () -> Unit)? = null,
+    enabled: Boolean = true,
     onClick: () -> Unit,
 ) {
     Row(
         verticalAlignment = Alignment.CenterVertically,
         horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
         modifier = modifier
             .fillMaxWidth()
-            .border(BorderStroke(1.dp, Color.Gray.copy(alpha = 0.4f)), RoundedCornerShape(8.dp))
-            .clickable(onClick = onClick)
+            .border(BorderStroke(1.dp, Color.Gray.copy(alpha = 0.4f)), RoundedCornerShape(8.dp))
+            .clickable(enabled = enabled, onClick = onClick)
             .semantics { contentDescription = label }
             .padding(horizontal = 16.dp, vertical = 12.dp),
     ) {

Then thread enabled = !signInState.isLoading through GoogleButton/GitHubButton/OutlinedTriggerButton call sites in SignIn.kt.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
internal fun OutlinedTriggerButton(
label: String,
modifier: Modifier = Modifier,
icon: (@Composable () -> Unit)? = null,
onClick: () -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
modifier = modifier
.fillMaxWidth()
.border(BorderStroke(1.dp, Color.Gray.copy(alpha = 0.4f)), RoundedCornerShape(8.dp))
.clickable(onClick = onClick)
.semantics { contentDescription = label }
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
icon?.invoke()
BasicText(label)
}
}
internal fun OutlinedTriggerButton(
label: String,
modifier: Modifier = Modifier,
icon: (`@Composable` () -> Unit)? = null,
enabled: Boolean = true,
onClick: () -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
modifier = modifier
.fillMaxWidth()
.border(BorderStroke(1.dp, Color.Gray.copy(alpha = 0.4f)), RoundedCornerShape(8.dp))
.clickable(enabled = enabled, onClick = onClick)
.semantics { contentDescription = label }
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
icon?.invoke()
BasicText(label)
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/kotlin/dev/thunderid/compose/components/presentation/auth/adapters/OutlinedTriggerButton.kt`
around lines 22 - 41, OutlinedTriggerButton currently has no enabled/loading
guard, so trigger actions can still be tapped while a sign-in is in flight. Add
an enabled parameter to OutlinedTriggerButton and pass it through to the
clickable modifier, then thread enabled = !signInState.isLoading from the
SignIn.kt call sites for GoogleButton and GitHubButton so trigger submissions
and Custom Tab launches are blocked during loading; use the existing
OutlinedTriggerButton, GoogleButton, and GitHubButton symbols to update all
relevant call paths consistently.

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